我正在尝试使用P / Invoke来注册一个带有本机dll的回调结构。当调用使本机dll调用回调的函数时,会发生AccessViolationException。我构建了一个“小”测试用例,演示了由2个文件组成的行为,native.cpp编译成native.dll,clr.cs编译成可执行文件。
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);
}
}
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未成功调用。此外,本机代码显示在调用cb2之前,其地址已更改。为什么会这样?据我所知,没有一个代表应该是gc'ed。作为额外的信息,编组IntPtr的结构并使用Marshal.GetFunctionPtrForDelegate正常工作(即使对于获取本机ptr来调用的cb3),但是,能够直接封送代理更有意义/更具可读性。
答案 0 :(得分:1)
问题是cb1,cb2和cb3是堆分配的,即使它们的存储(结构)不是。因此它们每个都受GC(压缩/重定位,因此使最初传入的指针无效)。
在传入结构之前,应该固定cb1,cb2和cb3中的每一个,理想情况是在它们被新建之后立即固定。否则他们可能会重新安置在记忆中。
是否决定使用结构来构建经典功能映射以避免此重定位?如果是这样,它最终没有帮助。
答案 1 :(得分:0)
首先,发布明确的repro代码的荣誉。现在你的代码有几个问题:
最重要的是,你在set_callback中接收的cb指针(来自C#ref参数)并存储在cbStruct中是不安全的。在set_callback返回后,无法保证它指向的结构将保持不变。如果您更改代码以便通过值传递结构的副本,我认为您的错误将会消失。
所有三个Marshal.GetFunctionPointerForDelegate调用都传递给第一个委托。
如果您想确定委托仍然有效,请在调用exec_callbacks之后插入对GC.KeepAlive(cb.c_cb1)等的调用。
答案 2 :(得分:0)
几天前我遇到了类似的问题。虽然指向结构的指针仍然有效,但是从第一次回调返回后,结构中的函数指针发生了变化。我的方法完全如示例代码中所示。而不是通过val传递结构的副本,我决定传递一个指针并复制它在set_callback中指向的结构,它可以工作。
__declspec(dllexport) void __stdcall set_callback(Callback *cb) {
cbStruct = new Callback(*cb);
// TODO: Clean up memory e.g. in release_callback()
}