简体   繁体   English

与不安全的代码一起使用时,ref有多安全?

[英]How safe is ref when used with unsafe code?

Using Microsoft Visual C# 2010, I recently noticed that you can pass objects by ref to unmanaged code. 使用Microsoft Visual C#2010,我最近注意到您可以通过引用将对象传递给非托管代码。 So I tasked myself with attempting to write some unmanaged code that converts a C++ char* to aa C# string using a callback to managed code. 所以我的任务是尝试编写一些非托管代码,使用回调托管代码将C ++ char *转换为C#字符串。 I made two attempts. 我做了两次尝试。

Attempt 1: Call unmanaged function that stores a ref parameter. 尝试1:调用存储ref参数的非托管函数。 Then, once that function has returned to managed code, call a another unmanaged function that calls a callback function that converts the char* to a managed string. 然后,一旦该函数返回到托管代码,调用另一个非托管函数,该函数调用将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);
}

Attempt 2: Pass string ref to unmanaged function that passes the string ref to the managed callback. 尝试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);
}

Attempt 1 doesn't work but attempt 2 does. 尝试1不起作用但尝试2起作用。 In attempt 1 it seems that as soon as the unmanaged code returns after storing the ref, the ref becomes invalid. 在尝试1中,似乎只要在存储ref之后非托管代码返回,ref就变为无效。 Why is this happening? 为什么会这样?

Given the outcomes of attempt 1, I have doubts that attempt 2 will work reliably. 鉴于尝试1的结果,我怀疑尝试2是否可行。 So, how safe is ref on the unmanaged side of code when used with unmanaged code? 那么,当与非托管代码一起使用时,代码的非托管端是否安全? Or in other words, what won't work in unmanaged code when using ref? 或者换句话说,在使用ref时,什么在非托管代码中不起作用?

Things I'd like to know are are: 我想知道的是:

What exactly happens when objects are passed using ref to unmanaged code? 使用ref将代码传递给非托管代码时究竟会发生什么?

Does it guarantee that the objects will stay at their current position in memory while the ref is being used in unmanaged code? 在非托管代码中使用ref时,它是否保证对象将保留在内存中的当前位置?

What are the limitations of ref (what can't I do with a ref) in unmanaged code? 在非托管代码中,ref(我不能用ref做什么)有什么限制?

A complete discussion of how p/invoke works is beyond the proper scope of a Stack Overflow Q&A. 关于p / invoke如何工作的完整讨论超出了Stack Overflow Q&A的适当范围。 But briefly: 但简单地说:

In neither of your examples are you really passing the address of your managed variable to the unmanaged code. 在您的示例中,您都没有将托管变量的地址传递给非托管代码。 The p/invoke layer includes marshaling logic that translates your managed data to something usable by the unmanaged code, and then translates back when the unmanaged code returns. p / invoke层包括编组逻辑,用于将托管数据转换为非托管代码可用的内容,然后在非托管代码返回时进行转换。

In both examples, the p/invoke layer has to create an intermediate object for the purpose of marshaling. 在这两个示例中,p / invoke层必须创建一个中间对象以用于编组。 In the first example, this object is gone by the time you call the unmanaged code again. 在第一个示例中,当您再次调用非托管代码时,此对象已消失。 Of course in the second example, it's not, since all of the work happens all at once. 当然,在第二个例子中,它并非如此,因为所有的工作都是同时发生的。

I believe that your second example should be safe to use. 我相信你的第二个例子应该是安全的。 That is, the p/invoke layer is smart enough to handle ref correctly in that case. 也就是说,p / invoke层足够聪明,可以在这种情况下正确处理ref The first example is unreliable because p/invoke is being misused, not because of any fundamental limitation of ref parameters. 第一个例子是不可靠的,因为p / invoke被滥用,而不是因为ref参数的任何基本限制。


A couple of additional points: 还有几点要点:

  • I wouldn't use the word "unsafe" here. 我不会在这里使用“不安全”这个词。 Yes, calling out to unmanaged code is in some ways unsafe, but in C# "unsafe" has a very specific meaning, related to the use of the unsafe keyword. 是的,调用非托管代码在某些方面是不安全的,但在C#中,“unsafe”具有非常特定的含义,与使用unsafe关键字有关。 I don't see anything in your code example that actually uses unsafe . 我的代码示例中没有看到任何实际使用unsafe
  • In both examples, you have a bug related to your use of the delegate passed to unmanaged code. 在这两个示例中,您都有一个与使用传递给非托管代码的委托相关的错误。 In particular, while the p/invoke layer can translate your managed delegate reference to a function pointer that unmanaged code can use, it doesn't know anything about the lifetime of the delegate object. 特别是,虽然p / invoke层可以将托管委托引用转换为非托管代码可以使用的函数指针,但它并不知道委托对象的生命周期 It will keep the object alive long enough for the p/invoked method call to complete, but if you need it to live longer than that (as would be the case here), you need to do that yourself. 它会使对象保持足够长的时间以使p /被调用的方法调用完成,但是如果你需要它比它更长寿(就像这里的情况那样),你需要自己做。 For example, use GC.KeepAlive() on a variable in which you've stored the reference. 例如,对存储引用的变量使用GC.KeepAlive() (You likely can reproduce a crash by inserting a call to GC.Collect() between the call to StoreCallback() and the later call to unmanaged code where the function pointer would be used). (您可能通过在调用StoreCallback()和稍后调用非托管代码(将使用函数指针GC.Collect()之间插入对GC.Collect()的调用来重现崩溃。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM