StringBuilder 编组导致 PInvokeStackImbalance 异常
StringBuilder marshalling leads to PInvokeStackImbalance exception
我正在尝试了解 .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
异常。
实际输出为:
Initial string was: Hello from C#
Changing its value...
BANG - Exception is thrown here
如何解决?我试图更改 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++ 函数以获取 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个问题。 MDA 可能只是介入了你的最后一次尝试,那次尝试让你放弃了。当然,您已经知道为什么 CallingConvention.StdCall 是错误的。
您不能使用 StringBuilder,它必须在没有 ref 的情况下传递,旨在允许被调用方复制缓冲区中的字符串内容。您需要一个额外的参数,bufferLength,以确保本机代码无法破坏 GC 堆。传递容量值。使用wcscpy_s()复制字符串内容。
但是您 return 是一个指针。那并没有让pinvoke marshaller很高兴,这是一个麻烦的内存管理问题。它假设 somebody 必须清理字符串缓冲区。当你让编组器执行它时,它会调用 CoTaskMemFree(),这很少有好结果。
您将不得不欺骗它并将参数声明为 ref IntPtr
。然后在 C# 代码中使用 Marshal.PtrToStringUni() 来检索字符串。否则,如果您不 return 指向字符串文字的指针而是在堆上分配或悬挂 wchar[] 指针,则会出现严重的故障模式。复制是安全的方法。
只是一个最终的工作解决方案(作为遗产)
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);
}
我正在尝试了解 .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
异常。
实际输出为:
Initial string was: Hello from C#
Changing its value...
BANG - Exception is thrown here
如何解决?我试图更改 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++ 函数以获取 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个问题。 MDA 可能只是介入了你的最后一次尝试,那次尝试让你放弃了。当然,您已经知道为什么 CallingConvention.StdCall 是错误的。
您不能使用 StringBuilder,它必须在没有 ref 的情况下传递,旨在允许被调用方复制缓冲区中的字符串内容。您需要一个额外的参数,bufferLength,以确保本机代码无法破坏 GC 堆。传递容量值。使用wcscpy_s()复制字符串内容。
但是您 return 是一个指针。那并没有让pinvoke marshaller很高兴,这是一个麻烦的内存管理问题。它假设 somebody 必须清理字符串缓冲区。当你让编组器执行它时,它会调用 CoTaskMemFree(),这很少有好结果。
您将不得不欺骗它并将参数声明为 ref IntPtr
。然后在 C# 代码中使用 Marshal.PtrToStringUni() 来检索字符串。否则,如果您不 return 指向字符串文字的指针而是在堆上分配或悬挂 wchar[] 指针,则会出现严重的故障模式。复制是安全的方法。
只是一个最终的工作解决方案(作为遗产)
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);
}