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 的特性不能用。
关于脚本后端的选择,可以参见我的另一篇博客:
在编译时不会有什么问题,但是在运行时会发生异常(如果你去捕捉,或者用 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;
}
}
当然上述代码不是线程安全的。所以如果你希望在多线程环境下使用,请自行修改为线程安全的版本。
参考资料
本文会经常更新,请阅读原文: https://blog.walterlv.com/post/unity3d-marshal-callback-must-be-static.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected]) 。