StringBuilder 编组导致 PInvokeStackImbalance 异常

Posted

技术标签:

【中文标题】StringBuilder 编组导致 PInvokeStackImbalance 异常【英文标题】:StringBuilder marshalling leads to PInvokeStackImbalance exception 【发布时间】:2015-09-16 12:39:46 【问题描述】:

我正在尝试了解 .Net 编组,现在我正在使用字符串。我写了一个应用程序,但它没有按预期工作。我在这里做错了什么?

C++

EXPORT void GetString(wchar_t **pBuff)

    std::wcout << "Initial string was: " << *pBuff << std::endl << "Changing its value..." << std::endl;
    *pBuff = L"Hello from C++";

C#:

const string DLLNAME = "CppLib.dll";
static void Main(string[] args)

    var sb = new StringBuilder(256).Append("Hello from C#");
    GetString(ref sb);
    Console.WriteLine("String from Dll is 0", sb);


[DllImport(DLLNAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern void GetString(ref StringBuilder pBuff);

但是当我运行它时,我得到了PInvokeStackImbalance 异常。

实际输出为:

初始字符串是:Hello from C#

改变它的值...

砰——这里抛出异常

如何解决?我试图改变CallingConvention - 当然它没有帮助,因为我在这里使用StdCall。但我没有更多的想法。

在纯 C++ 中,这段代码可以正常工作:

#include <iostream>
using namespace std;

void GetString(wchar_t **pBuff)

    std::wcout << "Initial string was: " << *pBuff << std::endl << "Changing its value..." << std::endl;
    *pBuff = L"Hello from C++!";


int main() 
    wchar_t *pBuff = L"blablablablablablabla";
    GetString(&pBuff);
    std::wcout << pBuff;
    return 0;

【问题讨论】:

另外,您不应该 覆盖字符串文字,除非您正在寻找守护进程。 另外,我不是 C++ 专家。但是,如果您使用指针并更改这样的字符串(就像在纯 C++ 代码中一样),它只会替换您分配的字节数。所以你那里也有内存泄漏。 你需要做的是先“删除”内存。然后allocate 再次使用new。不过,我会在你的情况下使用 std::string。 @AlexZhukovskiy 这里我做了一个例子,使用 std::string link @KevinKal .Net marshaller 不支持 std::string 所以我可以与 char/wchar_t 字符串互操作。而且我知道内存泄漏和其他问题,但这只是一个原型。我明确表示不会释放任何内存以确保它不是某些副作用的原因。 【参考方案1】:

这里有 3 个问题。 MDA 可能刚刚介入了你的最后一次尝试,那是让你放弃的尝试。当然,您已经知道为什么 CallingConvention.StdCall 是错误的。

您不能使用 StringBuilder,它必须在没有 ref 的情况下传递,并且旨在允许被调用者复制缓冲区中的字符串内容。您需要一个额外的参数 bufferLength,以确保本机代码不会破坏 GC 堆。传递容量值。使用 wcscpy_s() 复制字符串内容。

但是你正在返回一个指针。这不会让 pinvoke marshaller 很高兴,这是一个麻烦的内存管理问题。它假定 somebody 必须清理字符串缓冲区。当你让 marshaller 做这件事时,它会调用 CoTaskMemFree(),这很少有好的结果。

您将不得不欺骗它并将参数声明为ref IntPtr。然后在 C# 代码中使用 Marshal.PtrToStringUni() 来检索字符串。否则,如果您不返回指向字符串文字的指针而是在堆上分配或悬挂 wchar[] 指针,则会出现令人讨厌的故障模式。复制是安全的方式。

【讨论】:

是的,我知道在您回答之前我需要使用 wcscpy_s。但无论如何,谢谢,尤其是你回答的后半部分。【参考方案2】:

尝试将 C++ 函数更改为采用BSTR*。像这样的东西(未经测试):

EXPORT void GetString(BSTR*pBuff)

    std::wcout << "Initial string was: " << *pBuff << std::endl << "Changing its value..." << std::endl;
    SysFreeString(*pBuff);
    *pBuff = SysAllocString(L"Hello from C++");

【讨论】:

【参考方案3】:

只是一个最终可行的解决方案(作为旧版)

h:

#define EXPORT extern "C" __declspec(dllexport)

EXPORT void __stdcall GetString(wchar_t *pBuff, int size);

cpp:

#include "main.h"
#include <iostream>

EXPORT void __stdcall GetString(wchar_t *pBuff, int size)

    std::wcout << "Initial string was: " << pBuff << std::endl << "Changing its value..." << std::endl;
    const wchar_t *src = L"Hello from C++";
    wcscpy_s(pBuff, size, src);

C#端

class Program

    const string DLLNAME = "CppLib.dll";
    static void Main(string[] args)
    
        var sb = new StringBuilder(256).Append("Hello from C#");
        GetString(sb, sb.Capacity);
        Console.WriteLine("String from Dll is 0", sb);
    

    [DllImport(DLLNAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    private static extern void GetString(StringBuilder pBuff, int size);

【讨论】:

以上是关于StringBuilder 编组导致 PInvokeStackImbalance 异常的主要内容,如果未能解决你的问题,请参考以下文章

编组包含 c 字符串的结构

将 std::vector 转换为数组然后 p/调用它会在编组期间导致 mscorlib.dll 中的访问冲突异常

P/Invoke 有时会导致 Win32 1008 错误与 StringBuilder 参数

008-Java的StringBuilder和StringBuffer

使用SOAP编组的Spring 5 Web服务客户端问题

为什么StringBuilder是线程不安全的?