我正在使用C ++ dll项目开发一个C#dll项目。
让我们说C ++ dll登录到某个网站,并在网络服务器上进行一些查询。
C ++ dll必须返回网站的html代码。
同时,C ++ dll必须从网站保存cookie数据。
所以,我将StringBuilder对象传递给了C ++函数。
我已经知道如何使用C#中的HttpWebRequest和HttpWebResponse从网站获取HTML代码,但遗憾的是我必须在C ++ dll项目中进行。
所以请记住,我不需要任何C#代码。
我试过Passing a string variable as a ref between a c# dll and c++ dll。
从C#传递StringBuilder并将其作为LPTSTR。
它工作正常,但结果中缺少一些字符串。
我无法找出原因。
无论如何,这是我的代码。
C ++
extern "C" __declspec(dllexport) BSTR LoginQuery(const char* UserID, const char* UserPW, char Cookies[])
{
std::string html;
try
{
std::map<std::string, std::string> cookies;
MyClass *myclass = new MyClass();
html = myclass->LoginQuery(UserID, UserPW, cookies);
// Response cookies
std::string cookieData;
for (std::map<std::string, std::string>::iterator iterator = cookies.begin(); iterator != cookies.end(); iterator++)
{
cookieData += iterator->first;
cookieData += "=";
cookieData += iterator->second;
cookieData += ";";
}
sprintf(Cookies, cookieData.c_str(), 0);
delete myclass;
}
catch (...)
{
}
return ::SysAllocString(CComBSTR(html.c_str()).Detach());
}
C#
[DllImport(@"MyDll.dll", EntryPoint="LoginQuery", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern void LoginQuery(string UserID, string UserPW, StringBuilder Cookies);
void SomeThing()
{
StringBuilder _cookies = new StringBuilder(1024);
string result = LoginQuery("test", "1234", _cookies);
}
工作正常。
使用StringBuilder作为cookie,我可以继续使用网站的下一个网址。
(我在C ++项目中使用libcurl。)
但问题是我有大约100个ID。
当它运行大约3~40时,它会返回堆错误。
Debug Assertion Failed!
Program: ~~~~mics\dbgheap.c
Line: 1424
Expression: _pFirstBlock == pHead
我无法点击中止,重试或忽略按钮。
看起来C#应用程序挂起。
我读了很多关于调试断言的文章,但是dbgheap失败了。
大多数情况下来自另一个堆的空闲内存对象。
我是C ++的新手。
我在http://bytes.com/topic/c-sharp/answers/812465-best-way-interop-c-system-string-c-std-string上阅读了Edson的问题。
但错误并不总是在一定时间内出现。
所以我想到了,当.NET运行垃圾收集器时会发生这种情况。
.NET垃圾收集器试图释放一些从C ++ dll创建的内存,我得到了堆错误。
我是对的吗?
我认为他和我的一样有同样的问题。
避免堆错误并将正确的字符串从C ++ dll返回到C#dll的最佳方法是什么?
P / S:只有在我运行调试模式时才会发生堆错误。我在运行发布模式时没有收到错误。
修改
根据WhozCraig的回答,我改变了我的代码如下。
//return ::SysAllocString(CComBSTR(html.c_str()).Detach());
CComBSTR res(html.c_str());
return res.Detach();
但没有运气,我仍然得到堆错误。
答案 0 :(得分:1)
你的问题是标题要求将字符串引用从c#传递给c ++,但是在文本后面你会问如何将字符串从C ++返回到C#。另外,你告诉你是C ++的新手。考虑到这一点,我将告诉我上次必须这样做时是如何进行这种互动的。我只是让C ++端分配并释放所有内存,只传递给C#IntPtr
来Marshal.PtrToStringAnsi(p)
。在我的例子中,在C ++中存储线程局部接收并在每次函数调用时释放它们就足够了,但你可以创建一个C ++函数来释放它给出的任何ref。不是很有智慧,也不一定是最有效的方式,但它有效。
<强> UPD:强>
它完成they say它的作用。一些快速的谷歌搜索提出了this article。我认为它非常好,所以你可以参考它而不是我的建议。传递原始IntPtr
是好的,如果指针不能自行释放(例如旧的Delphi / C ++ Builder样式字符串),并且您更喜欢在管理端比在本地方。
作为一个例子,一段代码执行Delphi交互(也适用于C ++ Builder):
// function Get_TI_TC(AuraFileName:PAnsiChar; var TI,TC:PAnsiChar):Boolean; stdcall; external 'AuraToIec104.dll' name 'Get_TI_TC';
[DllImport("AuraToIec104")]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool Get_TI_TC(string AuraFileName, out IntPtr TI, out IntPtr TC);
public static bool Get_TI_TC(string AuraFileName, out string TI, out string TC)
{
IntPtr pTI, pTC;
bool result = Get_TI_TC(AuraFileName, out pTI, out pTC);
TI = Marshal.PtrToStringAnsi(pTI);
TC = Marshal.PtrToStringAnsi(pTC);
return result;
}
答案 1 :(得分:0)
看起来你的问题很简单。您正在创建一个StringBuilder
,可以容纳多达1024个字符。如果您的C ++函数返回的不止于此,那么您的应用程序将会崩溃(迟早)。
因此,要解决您的问题,请将StringBuilder
的大小增加到可能的最大输出长度。更多详情:Passing StringBuilder to PInvoke function引用:
唯一的 警告是StringBuilder必须 为...分配足够的空间 返回值,或文本将 溢出,导致异常 由P / Invoke抛出。
在动态字符串长度的情况下,在C ++函数中使用BSTR参数实际上可能更好。然后,您可以在C#中使用[MarshalAs(UnmanagedType.AnsiBStr), Out] ref string ...
,在C ++中使用BSTR*
。