2

// --------------------------- C# Code ------------------------------

    [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+ Code ------------------------------

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

The value of 'str' pointer in PassStringOut() and FreeString() are different, and I am getting a heap corruption error when calling SysFreeString(). Should I pass 'str' by reference to FreeString()? If so, what is the syntax I should use in C# and C++?

4

2 回答 2

6

封送层将在托管内存中分配字符串的副本。该副本将由垃圾收集器释放。您不必SysFreeString使用StringC#,事实上,正如您所发现的,尝试这样做是破坏堆的好方法。

我应该认为将在字符串上执行 2 个副本吗?然后*str = bstrStr.copy();由编组层?

让我更详细地描述这里发生的事情。

您的Main方法调用非托管代码,传递类型为 的局部变量的托管地址String。封送层创建自己的大小合适的存储来保存 aBSTR并将对该存储的引用传递给您的非托管代码。

非托管代码分配一个string引用与文字关联的存储的对象,然后分配 aBSTR并将原始字符串的第一个副本放入 heap-allocated BSTR。然后它制作第二个副本,BSTR并使用对该存储的引用填充 out 参数。bstrStr对象超出范围,其析构函数释放原始BSTR.

然后,封送层生成适当大小的托管字符串,第三次复制该字符串。然后它释放BSTR传递给它的那个。控制权返回到您的 C# 代码,该代码现在有一个托管字符串。

该字符串被传递给FreeString. 封送层分配 aBSTR并且第四次将字符串复制到 中BSTR,然后将其传递给您的非托管代码。然后它释放BSTR它不拥有的 a 并返回。封送层释放BSTR它分配的,破坏堆。

托管堆保持未损坏;垃圾收集器将在垃圾收集器选择时释放托管字符串。

我应该通过引用 FreeString() 来传递“str”吗?

不。相反,您应该停止编写互操作代码,直到您对编组的各个方面的工作方式有透彻深入的了解。

即使专家也很难在托管代码和非托管代码之间编组数据。我的建议是,您应该退后一步并获得专家的服务,如果您需要,他们可以教您如何安全、正确地编写互操作代码。

于 2013-08-20T17:32:04.527 回答
2

这不像你认为的那样工作。pinvoke marshaller 已经自动释放了 BSTR。它发生在您调用 PassStringOut() 时,编组器将其转换为 System.String 并释放 BSTR。这是在本机代码和托管代码之间传递 BSTR 的正常且必要的协议。

FreeString() 中的问题在于 pinvoke 编组器分配了一个的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);
}

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

于 2013-08-20T18:56:06.720 回答