[英]How to safely pass string reference from c# to c++?
我正在使用C ++ dll項目開發C#dll項目。
假設C ++ dll登錄到某個網站,並在Web服務器上進行一些查詢。
C ++ dll必須返回網站的html代碼。
同時,C ++ dll必須保存網站上的cookie數據。
因此,我將StringBuilder對象傳遞給C ++函數。
我已經知道如何使用C#中的HttpWebRequest和HttpWebResponse從網站獲取html代碼,但是不幸的是,我必須在C ++ dll項目中進行操作。
因此請記住,我不需要任何C#代碼。
我試過在ac#dll和c ++ dll之間傳遞字符串變量作為ref 。
從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,我可以繼續該網站的下一個URL。
(我在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();
但是沒有運氣,我仍然遇到堆錯誤。
您的問題是標題詢問有關將字符串引用從c#傳遞到c ++的問題,但是稍后在文本中,您將詢問如何將字符串從c + +返回給c#。 另外,您告訴您C ++的新手。 考慮到這一點,我將告訴我上一次我必須如何進行這種交互。 我只是讓C ++端分配並釋放了所有內存,僅將IntPtr
傳遞給C#作為Marshal.PtrToStringAnsi(p)
ed。 就我而言,在C ++中以線程局部存儲recerences並在每次函數調用時釋放它們就足夠了,但是您可以使C ++函數釋放給出的所有引用。 雖然不是很聰明,也不一定是最有效的方法,但是它確實有效。
upd:確實按照他們所說的去做。 一些快速谷歌搜索又發表了這篇文章 。 我認為它非常好,因此您可以參考它而不是我的建議。 如果不能自行釋放指針(例如,像舊的Delphi / C ++ Builder樣式的字符串一樣),並且希望在托管方面而不是在本機方面更麻煩,則傳遞raw IntPtr
很好。
例如,一段代碼進行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;
}
看來您的問題很簡單。 您正在創建一個StringBuilder
,它最多可以容納1024個字符。 如果您的C ++函數返回的值超過該值,則您的應用程序將崩潰(早或晚)。
因此,要解決您的問題,請將StringBuilder
的大小增加到最大可能的輸出長度。 更多詳細信息:將StringBuilder傳遞給PInvoke函數 ,該函數引用:
唯一的警告是,必須為StringBuilder分配足夠的空間用於返回值,否則文本將溢出,從而導致P / Invoke引發異常。
對於動態字符串長度,在C ++函數中使用BSTR參數實際上可能更好。 然后,您可以在C#中使用[MarshalAs(UnmanagedType.AnsiBStr), Out] ref string ...
在C ++中使用BSTR*
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.