调用SysFreeString()时出现堆损坏错误

时间:2013-08-20 17:25:44

标签: c# c++ marshalling

// --------------------------- 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 ++中使用什么语法?

2 个答案:

答案 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);
}

然而,必须猜测缓冲区的正确大小是非常详细的。