简体   繁体   中英

How to safely pass string reference from c# to c++?

I am developing a C# dll project with C++ dll project.

Let's say that C++ dll logins to a certain web site, and do some query on the web server.

And C++ dll has to return that html code of a web site.

In the same time, C++ dll must save the cookie data from the web site.

So, I passed StringBuilder object to C++ function.

I already know how to get html code from a web site using HttpWebRequest and HttpWebResponse classed in C#, but unfortunately I have to do it in C++ dll project.

So bear in mind, I don't need any C# codes.

I have tried Passing a string variable as a ref between ac# dll and c++ dll .

Passing StringBuilder from C# and get it as LPTSTR.

It works fine, but some strings were missing from the result.

I couldn't find out the reason.

Anyway, here is my code.

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

It works fine.

With the StringBuilder as cookie, I can carry on the next url of the web site.

(I am using libcurl in C++ project.)

But the problem is that I have about 100 ids.

When it runs about 3~40, it returns heap error.

Debug Assertion Failed!
Program: ~~~~mics\dbgheap.c
Line: 1424

Expression: _pFirstBlock == pHead

在此处输入图片说明

I cannot click the abort, retry or ignore button.

It looks like C# application hangs.

I read so many articles about debug assertion failed with dbgheap.

Mostly like free memory object from another heap.

I am newbie to C++.

I read Edson's question on http://bytes.com/topic/c-sharp/answers/812465-best-way-interop-c-system-string-c-std-string .

But the error does not always comes out in certain time.

So I came across with a guess, that it happens when .NET runs garbage collector.

.NET garbage collector tries to free some memory which created from C++ dll and I get the heap error.

Am I right?

I think he's suffering same problem as mine.

What is the best way to avoid heap error and return correct string from C++ dll to C# dll?

P/S: And the heap error occurs only when I run it debug mode. I don't get the error when I run release mode.

EDIT

According to WhozCraig answer, I changed my code as below.

//return ::SysAllocString(CComBSTR(html.c_str()).Detach());
CComBSTR res(html.c_str());
return res.Detach();

But no luck, I still get the heap error.

Your question is title asks about passing a string reference from c# to c++, but later in the text you ask how to return a string from C++ to C#. Also, you tell you are new to C++. With this in mind, I'll tell how I did this sort of interaction last time I had to do that. I just made C++ side allocate and free all the memory, passing out to C# only IntPtr s to be Marshal.PtrToStringAnsi(p) ed. In my case, storing recerences thread-local in C++ and freeing them on each function call was enough, but you can make a C++ function that frees whatever ref it is given. Not very intellectual and not necessarily the most efficient way, but it works.

upd: It does just what they say it does. Some quick googling comes up with this article . I think it's pretty good, so you can refer to it instead of my suggestion. Passing raw IntPtr s is good if the pointer is not okay to be freed by itself (like old Delphi/C++Builder style strings, for example) and you prefer to be bothered more on the managed side than on the native side.

As an example, piece of code doing Delphi interaction (good for C++ Builder as well):

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

It looks like your problem is rather simple. You are creating a StringBuilder that can hold as much as 1024 chars. If your C++ function returns more than that, your application will crash (sooner or later).

So to fix your problem, increase the StringBuilder 's size to the maximum possible output length. More details: Passing StringBuilder to PInvoke function which quotes:

The only caveat is that the StringBuilder must be allocated enough space for the return value, or the text will overflow, causing an exception to be thrown by P/Invoke.

It might actually be better in your case with dynamic string lengths to use a BSTR parameter in the C++ function. You can then use [MarshalAs(UnmanagedType.AnsiBStr), Out] ref string ... in C# and BSTR* in C++.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM