IL2CPP does not support marshaling delegates that point to instance methods to native code.
你可能平时在 .NET Core / Framework 的代码中写得很正常的托管代码的委托调用,在 Unity3D 中变得不可行。
本文举个例子,并且将其改正。
举例:查找所有可见窗口
本文的例子会使用到 NuGet 包 Lsj.Util.Win32
,这是个非常棒的 Win32 调用的 API 包装,可以免去大量自己可能写不对的 [DllImport]
。
引入命名空间:
1 2 | using Lsj.Util.Win32; using Lsj.Util.Win32.BaseTypes; |
---|
然后查找所有的可见窗口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static IReadOnlyList<HWND> FindVisibleWindows() { var found = new List<HWND>(); User32.EnumWindows(OnWindowEnum, IntPtr.Zero); return found; BOOL OnWindowEnum(HWND hWnd, LPARAM lparam) { if (User32.GetParent(hWnd) == IntPtr.Zero && User32.IsWindowVisible(hWnd)) { found.Add(HWND); } return true; } } |
---|
Mono / IL2CPP
Unity 编译的时候可以选择脚本后端是 Mono 还是 IL2CPP。不幸的是,没有 .NET Core 或者未来的 .NET 5/6,因此很多 .NET Core 的特性不能用。
关于脚本后端的选择,可以参见我的另一篇博客:
- Unity3D 入门:为 Unity 的 C# 项目添加 dll 引用或安装 NuGet 包 - walterlv
在编译时不会有什么问题,但是在运行时会发生异常(如果你去捕捉,或者用 VS 调试就可以看到):
1 2 3 4 5 | NotSupportedException: IL2CPP does not support marshaling delegates that point to instance methods to native code. The method we're attempting to marshal is: Win32WindowExtensions <>c__DisplayClass0_0::<FindVisibleWindows>g__OnWindowEnum|0 at Lsj.Util.Win32.User32.EnumWindows (Lsj.Util.Win32.User32 WNDENUMPROC lpEnumFunc, Lsj.Util.Win32.BaseTypes.LPARAM lParam) at Win32WindowExtensions.FindVisibleWindows () |
---|
“IL2CPP 不支持封送实例方法到本机代码”。
修正代码
Mono/IL2CPP 要求封送到本机的代码必须是静态方法,且必须标 MonoPInvokeCallback
特性。
因此,我们不得不把上面的代码改成这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using System; using System.Collections.Generic; using System.Text; using AOT; using Lsj.Util.Win32; using Lsj.Util.Win32.BaseTypes; public class WindowsEnumerator { private static List<HWND> _currentList; public IReadOnlyList<HWND> FindVisibleWindows() { _currentList = new List<HWND>(); User32.EnumWindows(OnWindowEnum, IntPtr.Zero); return _currentList; } [MonoPInvokeCallback(typeof(User32.WNDENUMPROC))] static BOOL OnWindowEnum(HWND hWnd, LPARAM lparam) { if (User32.GetParent(hWnd) == IntPtr.Zero && User32.IsWindowVisible(hWnd)) { _currentList?.Add(HWND); } return true; } } |
---|
当然上述代码不是线程安全的。所以如果你希望在多线程环境下使用,请自行修改为线程安全的版本。
参考资料
- MonoPInvokeCallbackAttribute Class (ObjCRuntime) - Microsoft Docs
本文会经常更新,请阅读原文: https://blog.walterlv.com/post/unity3d-marshal-callback-must-be-static.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected]) 。