在 DllImport 中指定 Charset.Unicode 是否分配字符串?
Does specifying Charset.Unicode in a DllImport allocate a string?
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
static extern void Process_utf16(string text, int text_length);
本机函数被编写为接收utf-16数据(这也是.net字符串使用的。)它不使用它之后的字符串数据returns。因此,我试图确保直接传递指向字符串缓冲区的指针,而不进行任何不必要的分配或复制。
在此声明中,是否传递了指向字符串缓冲区的指针而没有分配?还是分配了临时缓冲区并将字符串复制到其中?如果发生分配,是分配给本机堆还是托管堆?谁负责解除分配?
请注意,上面的代码已经过测试并且有效,我只是想看看它是否会导致分配和复制,如果是,如何避免。
"Default Marshaling for Strings" 中描述了编组字符串的规则。对于本机功能(平台互操作),文档指定:
Platform invoke copies string arguments, converting from the .NET
Framework format (Unicode) to the platform unmanaged format. Strings
are immutable and are not copied back from unmanaged memory to managed
memory when the call returns.
但是,可以通过实验简单地确定,如果根本不需要转换,则 不正确 ,也就是说,该方法用 CharSet.Unicode
或修饰该字符串明确标记为 MarshalAs(UnmanagedType.LPWStr)
。在这种情况下,直接传递指向字符串内容的指针。这听起来非常有效,确实如此,但它也很危险,因为没有什么可以阻止非托管函数修改传递给它的字符串。这很糟糕,因为 .NET 字符串应该是不可变的,并且代码可能依赖于此。如果这最终会覆盖字符串实习生池,那就特别糟糕了。
trample.c
:
__declspec(dllexport) void __stdcall Trample(wchar_t* text) {
memcpy(text, L"Adios", (sizeof L"Adios") - 2);
}
Program.cs
:
static class NativeMethods {
[DllImport("trample.dll", CharSet = CharSet.Unicode)]
public static extern void Trample(string text);
}
class Program {
static void Main(string[] args) {
Console.WriteLine("Hello, world!");
NativeMethods.Trample("Hello, world!");
Console.WriteLine("Hello, world!");
}
}
输出:
Hello, world!
Adios, world!
因为 "Hello, world!"
是一个字符串文字,它的所有实例最终都在字符串实习池中,每次使用它时我们都使用 "the same" 字符串。我们的非托管函数覆盖了它,所以现在每当我们认为我们在我们的托管代码中编写 "Hello, world!"
时,我们最终会得到其他东西。哎呀
如果您知道非托管函数会更改字符串,避免这种情况的方法是改为传递 StringBuilder
。在这里您甚至可以选择是否要复制到内部、外部或两者(InAttribute
/OutAttribute
)。这确实涉及复制 to/from 缓冲区——具体来说,CoTaskMemAlloc
将用于为非托管代码分配内存(并且 CoTaskMemFree
将在调用完成时调用)。此代码作为调用的一部分由封送拆收器调用;托管调用者和非托管调用者都不需要关心这个。
调用需要 ANSI 字符串的函数也涉及分配缓冲区,但在这种情况下,缓冲区是使用 localloc
指令分配的,而不是 CoTaskMemAlloc
,后者效率更高。也就是说——如果您实际上是为了提高效率,那么您要做的是尽可能完全消除调用非托管代码,而不仅仅是优化字符串传递。即使忽略复制内存,managed/unmanaged 转换也涉及相当多的开销。如果您发现自己在循环中调用非托管代码,那么查看该代码是否可以移植到托管代码是值得的。
来源:coreclr/src/vm/ilmarshalers.cpp
, specifically ILWSTRMarshaler::EmitConvertSpaceAndContentsCLRToNativeTemp
.
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
static extern void Process_utf16(string text, int text_length);
本机函数被编写为接收utf-16数据(这也是.net字符串使用的。)它不使用它之后的字符串数据returns。因此,我试图确保直接传递指向字符串缓冲区的指针,而不进行任何不必要的分配或复制。
在此声明中,是否传递了指向字符串缓冲区的指针而没有分配?还是分配了临时缓冲区并将字符串复制到其中?如果发生分配,是分配给本机堆还是托管堆?谁负责解除分配?
请注意,上面的代码已经过测试并且有效,我只是想看看它是否会导致分配和复制,如果是,如何避免。
"Default Marshaling for Strings" 中描述了编组字符串的规则。对于本机功能(平台互操作),文档指定:
Platform invoke copies string arguments, converting from the .NET Framework format (Unicode) to the platform unmanaged format. Strings are immutable and are not copied back from unmanaged memory to managed memory when the call returns.
但是,可以通过实验简单地确定,如果根本不需要转换,则 不正确 ,也就是说,该方法用 CharSet.Unicode
或修饰该字符串明确标记为 MarshalAs(UnmanagedType.LPWStr)
。在这种情况下,直接传递指向字符串内容的指针。这听起来非常有效,确实如此,但它也很危险,因为没有什么可以阻止非托管函数修改传递给它的字符串。这很糟糕,因为 .NET 字符串应该是不可变的,并且代码可能依赖于此。如果这最终会覆盖字符串实习生池,那就特别糟糕了。
trample.c
:
__declspec(dllexport) void __stdcall Trample(wchar_t* text) {
memcpy(text, L"Adios", (sizeof L"Adios") - 2);
}
Program.cs
:
static class NativeMethods {
[DllImport("trample.dll", CharSet = CharSet.Unicode)]
public static extern void Trample(string text);
}
class Program {
static void Main(string[] args) {
Console.WriteLine("Hello, world!");
NativeMethods.Trample("Hello, world!");
Console.WriteLine("Hello, world!");
}
}
输出:
Hello, world!
Adios, world!
因为 "Hello, world!"
是一个字符串文字,它的所有实例最终都在字符串实习池中,每次使用它时我们都使用 "the same" 字符串。我们的非托管函数覆盖了它,所以现在每当我们认为我们在我们的托管代码中编写 "Hello, world!"
时,我们最终会得到其他东西。哎呀
如果您知道非托管函数会更改字符串,避免这种情况的方法是改为传递 StringBuilder
。在这里您甚至可以选择是否要复制到内部、外部或两者(InAttribute
/OutAttribute
)。这确实涉及复制 to/from 缓冲区——具体来说,CoTaskMemAlloc
将用于为非托管代码分配内存(并且 CoTaskMemFree
将在调用完成时调用)。此代码作为调用的一部分由封送拆收器调用;托管调用者和非托管调用者都不需要关心这个。
调用需要 ANSI 字符串的函数也涉及分配缓冲区,但在这种情况下,缓冲区是使用 localloc
指令分配的,而不是 CoTaskMemAlloc
,后者效率更高。也就是说——如果您实际上是为了提高效率,那么您要做的是尽可能完全消除调用非托管代码,而不仅仅是优化字符串传递。即使忽略复制内存,managed/unmanaged 转换也涉及相当多的开销。如果您发现自己在循环中调用非托管代码,那么查看该代码是否可以移植到托管代码是值得的。
来源:coreclr/src/vm/ilmarshalers.cpp
, specifically ILWSTRMarshaler::EmitConvertSpaceAndContentsCLRToNativeTemp
.