C# 当与不安全代码一起使用时,ref的安全性如何?
使用Microsoft Visual C#2010,我最近注意到可以通过引用将对象传递给非托管代码。因此,我尝试编写一些非托管代码,这些代码使用回调到托管代码将C++ Car *转换为C字符串。我试了两次 尝试1:调用存储ref参数的非托管函数。然后,在该函数返回托管代码后,调用另一个非托管函数,该函数调用将char*转换为托管字符串的回调函数C# 当与不安全代码一起使用时,ref的安全性如何?,c#,unsafe,C#,Unsafe,使用Microsoft Visual C#2010,我最近注意到可以通过引用将对象传递给非托管代码。因此,我尝试编写一些非托管代码,这些代码使用回调到托管代码将C++ Car *转换为C字符串。我试了两次 尝试1:调用存储ref参数的非托管函数。然后,在该函数返回托管代码后,调用另一个非托管函数,该函数调用将char*转换为托管字符串的回调函数 C++ typedef void (_stdcall* CallbackFunc)(void* ManagedString, char* Unmanag
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:将字符串引用传递给将字符串引用传递给托管回调的非托管函数
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(我不能用ref做什么)的限制是什么?关于p/invoke如何工作的完整讨论超出了堆栈溢出的适当范围问与答。但简单地说: 在这两个示例中,您都没有将托管变量的地址真正传递给非托管代码。p/invoke层包括封送逻辑,该逻辑将托管数据转换为非托管代码可用的数据,然后在非托管代码返回时转换回 在这两个示例中,p/invoke层都必须创建一个中间对象以进行封送处理。在第一个示例中,当您再次调用非托管代码时,该对象已消失。当然,在第二个例子中,情况并非如此,因为所有的工作同时发生 我相信你的第二个例子应该是安全的。也就是说,在这种情况下,p/invoke层足够智能,可以正确处理
ref
。第一个例子是不可靠的,因为p/invoke被误用,而不是因为ref
参数的任何基本限制
还有几点:
- 我不会在这里用“不安全”这个词。是的,调用非托管代码在某些方面是不安全的,但在C#中,“不安全”有一个非常具体的含义,与
关键字的使用有关。我在您的代码示例中没有看到任何实际使用unsafe
不安全的
- 在这两个示例中,您都有一个与使用传递给非托管代码的委托相关的bug。特别是,尽管p/invoke层可以将托管委托引用转换为非托管代码可以使用的函数指针,但它对委托对象的生存期一无所知。它将使对象保持足够长的生存时间,以使p/invoked方法调用得以完成,但是如果您需要它的生存时间更长(这里就是这种情况),那么您需要自己完成。例如,对存储了引用的变量使用
。(通过在调用GC.KeepAlive()
和稍后调用将使用函数指针的非托管代码之间插入对StoreCallback()
的调用,可能会重现崩溃)GC.Collect()