通过使用 SuppressGCTransition 修饰的 P/Invoke 调用将 UnmanagedCallersOnly 函数指针传递给 C++ 时发生崩溃
Crash when passing an UnmanagedCallersOnly function pointer to C++ via P/Invoke call decorated with SuppressGCTransition
假设我们有以下 C++ 代码:
typedef int (*getIntPtr)(void);
extern "C" __declspec(dllexport) void InvokeFuncPtr(getIntPtr funcPtr) {
std::wcout << funcPtr();
}
我们可以在C#中匹配这个定义:
[DllImport("NativeLib.dll", CallingConvention = CallingConvention.Cdecl), SuppressGCTransition]
public static unsafe extern void InvokeFuncPtr(delegate* unmanaged[Cdecl]<int> funcPtr);
然后我们可以像这样使用这个函数:
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
public static int ReturnInt() => 123;
// ... Elsewhere:
unsafe {
InvokeFuncPtr(&ReturnInt);
}
当 InvokeFuncPtr
标记为 SuppressGCTransition
时,这会导致程序崩溃并出现错误 “致命错误。无效的程序:试图从托管代码调用 UnmanagedCallersOnly 方法。 “。如果我们删除 SuppressGCTransition
属性,它会按预期工作,并且 123
会打印到控制台。
这是预期的行为吗?我想这相当于将 ReturnInt()
视为从托管代码调用的运行时,只需几个额外的间接步骤。如果是这样,有什么办法解决这个问题,还是我应该简单地关闭 SuppressGCTransition
属性?
是的,你应该放弃 SuppressGCTransition
,因为堆栈是运行时用来识别调用者是托管还是非托管的。
如果堆栈中没有转换,运行时就无法判断堆栈是否已转换为非托管。
或者,您可以停止 UnmanagedCallersOnly
,而是用 Marshal.GetFunctionPointerForDelegate
编组委托。
假设我们有以下 C++ 代码:
typedef int (*getIntPtr)(void);
extern "C" __declspec(dllexport) void InvokeFuncPtr(getIntPtr funcPtr) {
std::wcout << funcPtr();
}
我们可以在C#中匹配这个定义:
[DllImport("NativeLib.dll", CallingConvention = CallingConvention.Cdecl), SuppressGCTransition]
public static unsafe extern void InvokeFuncPtr(delegate* unmanaged[Cdecl]<int> funcPtr);
然后我们可以像这样使用这个函数:
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
public static int ReturnInt() => 123;
// ... Elsewhere:
unsafe {
InvokeFuncPtr(&ReturnInt);
}
当 InvokeFuncPtr
标记为 SuppressGCTransition
时,这会导致程序崩溃并出现错误 “致命错误。无效的程序:试图从托管代码调用 UnmanagedCallersOnly 方法。 “。如果我们删除 SuppressGCTransition
属性,它会按预期工作,并且 123
会打印到控制台。
这是预期的行为吗?我想这相当于将 ReturnInt()
视为从托管代码调用的运行时,只需几个额外的间接步骤。如果是这样,有什么办法解决这个问题,还是我应该简单地关闭 SuppressGCTransition
属性?
是的,你应该放弃 SuppressGCTransition
,因为堆栈是运行时用来识别调用者是托管还是非托管的。
如果堆栈中没有转换,运行时就无法判断堆栈是否已转换为非托管。
或者,您可以停止 UnmanagedCallersOnly
,而是用 Marshal.GetFunctionPointerForDelegate
编组委托。