介紹
我試圖使用P/Invoke來註冊回調的結構與本地的dll。當調用一個使原生dll調用回調的函數時發生AccessViolationException。我構建了一個「小」測試用例,演示了由編譯爲可執行文件的native.dll和clr.cs的2個文件native.cpp組成的行爲。爲什麼編組回調代表的結構導致AccessViolationException
native.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不成功。此外,本地代碼顯示在調用cb2之前,其地址已更改。爲什麼會發生?據我所知,沒有一個代表應該被接受。作爲附加信息,編組IntPtr的結構和使用Marshal.GetFunctionPtrForDelegate可以正常工作(即使對於獲取本地ptr來調用的cb3),但是,能夠直接編組代碼更有意義/更具可讀性。
如果可能,我想不必更改本機代碼。有沒有什麼會轉化爲指向結構的指針? – Ryan 2009-10-15 14:46:37
是的,如果您更改set \ _callback的簽名以採用IntPtr而不是ref回調。然後,您可以使用GCHandle類型來獲取指向結構的盒裝副本的指針並傳入該指針。在exec \ _callbacks返回後釋放GCHandle。 – 2009-10-16 10:58:47
有些東西沒有正確翻譯,現在沒有一個回調看起來是在正確的位置,即有一些美觀的改變,我的代碼顯示託管/非託管同意結構在內存中的位置,但似乎不同意在回調的位置 發送回調: 結構:291194 CB1:340A1A CB2:340B62 CB3:340BDA 了回調: 結構:00291194 CB1:02838F34 CB2:02838DD0 CB3:00000000 [本地]在02838F34處執行回撥1 – Ryan 2009-10-16 16:06:50