StringBuilder编组导致PInvokeStackImbalance异常

时间:2015-09-16 12:39:46

标签: c# c++ .net pinvoke

我试图理解.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例外。

实际输出为:

  

初始字符串是:来自C#的Hello:

     

改变其价值......

     

BANG - 此处抛出异常

如何解决?我试图改变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;
}

3 个答案:

答案 0 :(得分:1)

尝试更改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++");
}

答案 1 :(得分:1)

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

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

但是你正在返回一个指针。这并不能使pinvoke marshaller非常高兴,这是一个麻烦的内存管理问题。它假定某人必须清理字符串缓冲区。当你让marshaller这样做时,它会调用CoTaskMemFree(),这很少会达到目的。

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

答案 2 :(得分:1)

只是最终的解决方案(作为遗产)

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#side

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