// --------------------------- C#代码---------------- --------------
[DllImport("MarshallStringsWin32.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static void PassStringOut([MarshalAs(UnmanagedType.BStr)] out String str);
[DllImport("MarshallStringsWin32.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static void FreeString([MarshalAs(UnmanagedType.BStr)] String str);
static void Main(string[] args)
{
String str;
PassStringOut(out str);
FreeString(str);
}
// --------------------------- C +代码---------------- --------------
void PassStringOut(__out BSTR* str)
{
const std::string stdStr = "The quick brown fox jumps over the lazy dog";
_bstr_t bstrStr = stdStr.c_str();
*str = bstrStr.copy();
}
void FreeString(BSTR str)
{
SysFreeString(str);
}
PassStringOut()和FreeString()中'str'指针的值是不同的,我在调用SysFreeString()时遇到堆损坏错误。我应该通过引用FreeString()传递'str'吗?如果是这样,我应该在C#和C ++中使用什么语法?
答案 0 :(得分:6)
编组层将在托管内存中分配字符串的副本。该副本将被垃圾收集器释放。您不必在C#中SysFreeString
String
,事实上,正如您所发现的那样,尝试这样做是破坏堆的好方法。
我应该认为在字符串上会有2个副本吗?
*str = bstrStr.copy();
然后是编组层?
让我更详细地描述这里发生的事情。
您的Main
方法调用非托管代码,传递类型为String
的局部变量的托管地址。编组层创建自己的适当大小的存储来容纳BSTR
并将对该存储的引用传递给您的非托管代码。
非托管代码分配一个string
对象,该对象引用与文字关联的存储,然后分配BSTR
并将原始字符串的第一个副本放入堆分配的{{1} }。然后它生成BSTR
的第二个副本,并使用对该存储的引用填充out参数。 BSTR
对象超出范围,其析构函数释放原始bstrStr
。
编组层然后创建适当大小的托管字符串,第三次复制字符串。然后它释放传递给它的BSTR
。控制权返回到您的C#代码,该代码现在具有托管字符串。
该字符串传递给BSTR
。编组层分配FreeString
,并且第四次将字符串的副本复制到BSTR
,并传递给您的非托管代码。然后它释放它不拥有的BSTR
并返回。编组层释放它分配的BSTR
,破坏堆。
托管堆仍未损坏;在垃圾收集器选择时,垃圾收集器将释放托管字符串。
我应该通过' str'通过引用FreeString()?
没有。相反,您应该停止编写互操作代码,直到您有彻底和深入了解编组的所有方面是如何工作的。
即使专家得到纠正,托管和非托管代码之间的数据编组也很困难。我的建议是,你需要退后一步,获得专家的服务,如果你需要,可以教你如何安全,正确地编写互操作代码。
答案 1 :(得分:2)
这并不像你认为的那样有效。 pinvoke marshaller已经自动释放了BSTR。它发生在你调用PassStringOut()时,marshaller将它转换为System.String并释放了BSTR。这是在本机代码和托管代码之间传递BSTR的常规和必要协议。
FreeString()出了什么问题,因为pinvoke marshaller分配了 new BSTR。它已经发布两次。首先是你的本地代码,再由pinvoke marshaller。来自调试堆的Kaboom,当您使用附加的调试器运行代码时使用它。
你只是在帮助太多,不要打电话给FreeString()。
您可以让pinvoke marshaller为您处理ANSI字符串,它实际上是默认行为,因为它们在传统C代码中非常常见。您的C ++函数可能如下所示:
extern "C" __declspec(dllexport)
void __stdcall PassStringOut(char* buffer, size_t bufferLen)
{
const std::string stdStr = "The quick brown fox jumps over the lazy dog";
strcpy_s(buffer, bufferLen, stdStr.c_str());
}
使用匹配的C#代码:
class Program {
static void Main(string[] args) {
var buffer = new StringBuilder(666);
PassStringOut(buffer, buffer.Capacity);
Console.WriteLine(buffer.ToString());
Console.ReadLine();
}
[DllImport("Example.dll")]
private static extern bool PassStringOut(StringBuilder buffer, int capacity);
}
然而,必须猜测缓冲区的正确大小是非常详细的。