Unity3D (Mono/IL2CPP) 中 P/Invoke 平台调用代码应该如何传委托

2023-10-22 11:27:23 浏览数 (3)

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]) 。

1 人点赞