使用Microsoft Visual C#2010,我最近注意到您可以通过引用将对象传递给非托管代码。所以我的任务是尝试编写一些非托管代码,使用回调托管代码将C ++ char *转换为C#字符串。我做了两次尝试。
尝试1:调用存储ref参数的非托管函数。然后,一旦该函数返回到托管代码,调用另一个非托管函数,该函数调用将char *转换为托管字符串的回调函数。
C++
typedef void (_stdcall* CallbackFunc)(void* ManagedString, char* UnmanagedString);
CallbackFunc UnmanagedToManaged = 0;
void* ManagedString = 0;
extern "C" __declspec(dllexport) void __stdcall StoreCallback(CallbackFunc X) {
UnmanagedToManaged = X;
}
extern "C" __declspec(dllexport) void __stdcall StoreManagedStringRef(void* X) {
ManagedString = X;
}
extern "C" __declspec(dllexport) void __stdcall CallCallback() {
UnmanagedToManaged(ManagedString, "This is an unmanaged string produced by unmanaged code");
}
C#
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void StoreCallback(CallbackFunc X);
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void StoreManagedStringRef(ref string X);
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void CallCallback();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void CallbackFunc(ref string Managed, IntPtr Native);
static void Main(string[] args) {
string a = "This string should be replaced";
StoreCallback(UnmanagedToManaged);
StoreManagedStringRef(ref a);
CallCallback();
}
static void UnmanagedToManaged(ref string Managed, IntPtr Unmanaged) {
Managed = Marshal.PtrToStringAnsi(Unmanaged);
}
尝试2:将字符串ref传递给将字符串ref传递给托管回调的非托管函数。
C++
typedef void (_stdcall* CallbackFunc)(void* ManagedString, char* UnmanagedString);
CallbackFunc UnmanagedToManaged = 0;
extern "C" __declspec(dllexport) void __stdcall StoreCallback(CallbackFunc X) {
UnmanagedToManaged = X;
}
extern "C" __declspec(dllexport) void __stdcall DoEverything(void* X) {
UnmanagedToManaged(X, "This is an unmanaged string produced by unmanaged code");
}
C#
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void StoreCallback(CallbackFunc X);
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void DoEverything(ref string X);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void CallbackFunc(ref string Managed, IntPtr Unmanaged);
static void Main(string[] args) {
string a = "This string should be replaced";
StoreCallback(UnmanagedToManaged);
DoEverything(ref a);
}
static void UnmanagedToManaged(ref string Managed, IntPtr Unmanaged) {
Managed = Marshal.PtrToStringAnsi(Unmanaged);
}
尝试1不起作用,但尝试2起作用。在尝试1中,似乎只要在存储ref之后非托管代码返回,ref就变为无效。为什么会这样?
鉴于尝试1的结果,我怀疑尝试2是否可行。那么,当与非托管代码一起使用时,代码的非托管端是否安全?或者换句话说,在使用ref时,什么在非托管代码中不起作用?
我想知道的是:
使用ref将代码传递给非托管代码时究竟会发生什么?
当在非托管代码中使用ref时,它是否保证对象将保留在内存中的当前位置?
在非托管代码中,ref(我不能用ref做什么)有什么限制?
答案 0 :(得分:1)
关于p / invoke如何工作的完整讨论超出了Stack Overflow Q& A的适当范围。但简单地说:
在您的两个示例中,您实际上都是将托管变量的地址传递给非托管代码。 p / invoke层包括编组逻辑,用于将托管数据转换为非托管代码可用的内容,然后在非托管代码返回时进行转换。
在这两个示例中,p / invoke层必须创建一个中间对象以用于编组。在第一个示例中,当您再次调用非托管代码时,此对象已消失。当然,在第二个例子中,它并非如此,因为所有的工作都是一次性完成的。
我相信你的第二个例子应该是安全的。也就是说,p / invoke层足够智能,可以在这种情况下正确处理ref
。第一个例子是不可靠的,因为p / invoke被滥用,而不是因为ref
参数的任何基本限制。
还有几点:
unsafe
关键字有关。我在代码示例中看不到任何实际使用unsafe
。GC.KeepAlive()
。 (您可以通过在调用GC.Collect()
和稍后调用将使用函数指针的非托管代码之间插入对StoreCallback()
的调用来重现崩溃。