简介
我正在尝试使用 P/Invoke 向 native dll 注册回调结构。当调用使 native dll 调用回调的函数时,会发生 AccessViolationException。我构建了一个“小型”测试用例来演示由 2 个文件组成的行为,native.cpp 编译成 native.dll 和 clr.cs 编译成可执行文件。
原生.cpp
extern "C" {
typedef void (*returncb)(int i);
typedef struct _Callback {
int (*cb1)();
int (*cb2)(const char *str);
void (*cb3)(returncb cb, int i);
} Callback;
static Callback *cbStruct;
__declspec(dllexport) void set_callback(Callback *cb) {
cbStruct = cb;
std::cout << "Got callbacks: " << std::endl <<
"cb1: " << std::hex << cb->cb1 << std::endl <<
"cb2: " << std::hex << cb->cb2 << std::endl <<
"cb3: " << std::hex << cb->cb3 << std::endl;
}
void return_callback(int i) {
std::cout << "[Native] Callback from callback 3 with input: " << i << std::endl;
}
__declspec(dllexport) void exec_callbacks() {
std::cout << "[Native] Executing callback 1 at " << std::hex << cbStruct->cb1 << std::endl;
std::cout << "[Native] Result: " << cbStruct->cb1() << std::endl;
std::cout << "[Native] Executing callback 2 at " << std::hex << cbStruct->cb2 << std::endl;
std::cout << "[Native] Result: " << cbStruct->cb2("2") << std::endl;
std::cout << "[Native] Executing callback 3 with input 3 at " << std::hex << cbStruct->cb3 << std::endl;
cbStruct->cb3(return_callback, 3);
std::cout << "[Native] Executing callback 3 with input 4 at " << std::hex << cbStruct->cb3 << std::endl;
cbStruct->cb3(return_callback, 4);
}
}
clr.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace clr {
public delegate void returncb(Int32 i);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int cb1();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int cb2(string str);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void cb3(returncb cb, Int32 i);
[StructLayout(LayoutKind.Sequential)]
struct Callback {
[MarshalAs(UnmanagedType.FunctionPtr)]
public cb1 c_cb1;
[MarshalAs(UnmanagedType.FunctionPtr)]
public cb2 c_cb2;
[MarshalAs(UnmanagedType.FunctionPtr)]
public cb3 c_cb3;
}
class Program {
static int cb1Impl() {
Console.WriteLine("[Managed] callback 1");
return 1;
}
static int cb2Impl(string c) {
Console.WriteLine("[Managed] callback 2");
return int.Parse(c);
}
static void cb3Impl(returncb cb, Int32 i) {
Console.WriteLine("[Managed] callback 3");
Console.WriteLine("[Managed] Executing callback to native.");
cb(i);
}
[DllImport("native.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void set_callback(ref Callback cb);
[DllImport("native.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void exec_callbacks();
static void Main(string[] args) {
Callback cb;
cb.c_cb1 = new cb1(cb1Impl);
cb.c_cb2 = new cb2(cb2Impl);
cb.c_cb3 = new cb3(cb3Impl);
Console.WriteLine("Beginning test.");
Console.WriteLine("Sending callbacks: ");
Console.WriteLine("cb1: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1));
Console.WriteLine("cb2: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1));
Console.WriteLine("cb3: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1));
set_callback(ref cb);
exec_callbacks();
Console.ReadLine();
}
}
}
结果
调用它会导致 exec_callbacks() 抛出 AccessViolationException。 cb1 被成功调用,但 cb2 没有。此外, native 代码显示在调用 cb2 之前,其地址已更改。为什么会发生这种情况?据我所知,没有一个委托(delegate)应该被 gc'ed。作为附加信息,编码 IntPtr 的结构并使用 Marshal.GetFunctionPtrForDelegate 工作正常(即使对于获取本地 ptr 来调用的 cb3),但是,能够直接编码委托(delegate)更有意义/更具可读性。
最佳答案
问题是 cb1、cb2 和 cb3 是堆分配的,即使它们的存储(结构)不是。因此它们都受到 GC(压缩/重定位,从而使最初传入的指针无效)。
在传入结构之前,cb1、cb2 和 cb3 中的每一个都应该固定,最好是在它们被新建之后立即固定。否则它们可能会在内存中重新定位。
是否决定使用结构来构建经典函数映射以避免这种重定位?如果是这样,那最终是没有帮助的。
关于c# - 为什么编码回调委托(delegate)结构会导致 AccessViolationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1569153/