1

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 a c# 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

enter image description here

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.

Community
  • 1
  • 1
Joshua Son
  • 1,839
  • 6
  • 31
  • 51
  • 2
    Related, this: `return ::SysAllocString(CComBSTR(html.c_str()).Detach());` is a memory leak. You're detaching the CComBSTR (which means its now a raw BSTR) then returning a SysAllocString of *that*, forever leaking the detached BSTR. Just do this: `CComBSTR res(html.c_str()); return res.Detach();` – WhozCraig Apr 05 '14 at 04:24
  • Is there a reason you do not just put C++ into C++/CLR mode and pass in a system.string and deal with this in the C++ level? ;) This makes thigns a lot easier to work with. The interop you use is designed to integrate with "legacy pure C" intefaces - if you own the C++ code, just turn on integration. – TomTom Apr 05 '14 at 08:45
  • Your sprintf() function call will corrupt the heap when there are too many cookies. The *Cookies* argument serves no useful purpose, other than crashing your program. Remove it. And note that it doesn't do anything in the C++ code either so just remove the sprintf() call as well. Thoroughly unit-test C++ code before you attempt to pinvoke it. – Hans Passant Apr 05 '14 at 09:47
  • @WhozCraig I will try to change my code. – Joshua Son Apr 05 '14 at 11:37
  • @TomTom Is C++/CLR possible to decompile with such decompilers like JustDecompile, and so on? If so, I cannot use it. – Joshua Son Apr 05 '14 at 11:39
  • @HansPassant Cookies is very important variable for me. I have to get the cookie strings back to C#, so that I can go on the next procedure. – Joshua Son Apr 05 '14 at 11:40
  • @HansPassant I have to return the html code of the certain web site as well as the cookies. That's why I am returning the html code and getting reference of cookies as StringBuilder. Could you show me any article that I can do unit-testing on c++? – Joshua Son Apr 05 '14 at 11:56
  • @WhozCraig Just changing the returning type didn't solve the problem. – Joshua Son Apr 05 '14 at 11:57

2 Answers2

1

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 IntPtrs 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 IntPtrs 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;
    }
Eugene Ryabtsev
  • 2,232
  • 1
  • 23
  • 37
  • Could you show me an article of Marshal.PtrToStringAnsi? – Joshua Son Apr 05 '14 at 11:56
  • Thanks for good information. I have read that article. Now, I wonder how I get the "out IntPtr" from C++ function. I received as "const char*" type, and I get IntPtr.Zero in c#. No example from that web site shows me to pass "out IntPtr". – Joshua Son Apr 06 '14 at 00:24
  • Hmm... I think you just reinterpret. My example is in Delpni, which has `var` keyword meaninc C#'s `ref` and it thinks it deals with `PAnsiChar`s. In your C++ function... I'd say you return `char *` and have a `char **` parameter. In your C# `extern` declaration you say your C++ function returns `IntPtr` and has an `out IntPtr` parameter. If this works, you might want something more Unicode (wchar_t * and Marshal.PtrToStringUni), but the idea's the same. – Eugene Ryabtsev Apr 06 '14 at 06:12
0

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++.

Community
  • 1
  • 1
floele
  • 3,668
  • 4
  • 35
  • 51
  • When StringBuilder size is too small, then it throws buffer overflow exception. Not heap error. So, there is nothing to do with StringBuilder. In fact, the size of cookie(StringBuilder) is only for 20 characters. – Joshua Son Apr 05 '14 at 11:37
  • "In fact, the size of cookie(StringBuilder) is only for 20 characters." - what do you mean? If you are talking about "100 ids" I don't think 20 chars will suffice to hold them. – floele Apr 05 '14 at 12:00
  • @JoshuaSon Also, I doubt you'll get a BufferOverFlowException. sprintf() certainly won't throw an exception when writing to a too small buffer, it will fail miserably. Please double-check that. – floele Apr 05 '14 at 12:41
  • If you see void SomeThing function, I always re-create StringBuilder with new keyword. – Joshua Son Apr 05 '14 at 12:44
  • @JoshuaSon That may be, but I still don't see how this buffer is large enough. – floele Apr 05 '14 at 14:03