3

I'm calling C# method from C++ and passing char** as argument. It has to be char** because I need to return value through parameter.

C# code:

[ExportDll("test", System.Runtime.InteropServices.CallingConvention.StdCall)]

public static int test([MarshalAs(UnmanagedType.AnsiBStr)] ref string p)
{
    Console.WriteLine(p);
}

C++ code to invoke function:

typedef int (__stdcall *MYPROC)(char **); 

VOID main(VOID) 
{ 
    HINSTANCE hinstLib; 
    MYPROC MyProc; 
    BOOL fFreeResult, fRunTimeLinkSuccess = FALSE; 

    hinstLib = LoadLibrary(TEXT("mydll.dll")); 

    if (hinstLib != NULL) 
    { 
        ProcAdd = (MYPROC) GetProcAddress(hinstLib, "test"); 

        if (NULL != ProcAdd) 
        {
            fRunTimeLinkSuccess = TRUE;
            char s1[] = "test"; 
            char *s2 = s1;
            char **s3 = &s2;    
            (MyProc) (s3); 
            cout << s3;
        }
        fFreeResult = FreeLibrary(hinstLib); 
    } 
}

It's simple to pass char* (remove ref in c#, and use char* in c++), but when trying to pass char** i get a runtime error on line where I call the function :(

in c#, Console.WriteLine prints out correct value, but after that, I get an error:

Windows has triggered a breakpoint in COMDynamicLoad.exe.

This may be due to a corruption of the heap, which indicates a bug in COMDynamicLoad.exe or any of the DLLs it has loaded.

This may also be due to the user pressing F12 while COMDynamicLoad.exe has focus.

The output window may have more diagnostic information.

How should I do this?

AbdelAziz AbdelLatef
  • 3,650
  • 6
  • 24
  • 52
bkovacic
  • 371
  • 1
  • 5
  • 16

4 Answers4

3

This is likely because you took a char* pointing to a string literal- which is bad because modifying that memory is undefined behaviour.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • I changed that and updated the question, because it's still now working. Can you please take a look now? – bkovacic Jun 24 '11 at 17:51
3

You declare ref UnmanagedType.AnsiBStr but you expect a char**. This cannot work, since a ref to a BSTR is not a char**. See Default Marshaling for Strings for examples of marshaling declarations. These are possible declarations for an input-output string:

PassStringRef2([in, out] BSTR *s);
PassStringRef3([in, out] LPStr *s);
PassStringRef4([in, out] LPWStr *s);

and the equivalent C# marshaling declarations are:

PassStringRef2([MarshalAs(UnmanagedType.BStr)]ref String s);
PassStringRef3([MarshalAs(UnmanagedType.LPStr)]ref String s);
PassStringRef4([MarshalAs(UnmanagedType.LPWStr)]ref String s);

Your char** declaration is the equivalent of LPStr *s, so the correct marshaling is [MarshalAs(UnmanagedType.LPStr)]ref String s. But a better option is to use BSTR because of the explicit length declaration, and manipulate it in C++ with the BSTR helpers.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • OK, I tried 'UnmanagedType.LPStr' and c# prints correct value, but after that I get error: 'Access violation reading location 0xbd9c6242.' Do I need to change something in C++ code also? – bkovacic Jun 24 '11 at 18:04
  • I guess I need to put [in, out] somewhere, but I don't now where. In examples on the link, that's in the interface declaration. Since I'm calling procedure in other direction, I don't have interface... – bkovacic Jun 24 '11 at 18:13
  • 1
    There still are plenty of errors in your code. `(MyProc)(&s3)` passes in a `char***` (address of a `char**`), not a `char**` as expected. You use basic types (`char*`) w/o any proper memory management across the call boundary. As I said, use `BSTR`, on the C# side they'll be managed automatically. On the C++ side, you need to free them properly. – Remus Rusanu Jun 24 '11 at 18:53
  • Why do you do naked DLL load and call GetProcAddress? Why not go down the properly supported way of COM Interrop, register your managed assembly with COM and call it in C++ from COM? You can even do registration free COM http://msdn.microsoft.com/en-us/library/ms973913.aspx – Remus Rusanu Jun 24 '11 at 18:58
  • i made mistake while changing code here `(MyProc)(s3)` was in code... I must do it this way because c++ application is part of bigger system and I can't change the way it loads plugins. I must check whether they can implement it using BSTR. Is there any chance to solve it using char**? – bkovacic Jun 24 '11 at 19:14
  • i got it working with `BSTR*` in my stub application. but sill, I need to make it work with `char**` somehow :( – bkovacic Jun 24 '11 at 19:47
  • 1
    When crossing pointers across the managed/unmanaged boundary you must use proper memory management, like `CoTaskMemAlloc`, because the interop marhaler will dellocate the memory passed in using `CoTaskMemFree`, see http://msdn.microsoft.com/en-us/magazine/cc164193.aspx#S6. Passing in stack variable address (like `&s1`, `&s2` etc) will cause corruption and access violation. – Remus Rusanu Jun 24 '11 at 20:02
  • I just got some new information about c++ system. I don't even get `char**`, I get just `char*` that is allocated on c++ side. I can get this parameter in c# as `string`, but not as reference. So, if I understand it right, there's no chance to get `ref string` in c# if I get only `char*` from c++? could you please confirm this? unfortunately, I'm not able to do any changes in c++ code :( – bkovacic Jun 25 '11 at 10:22
  • same as code above, just changes: `typedef int (__stdcall *MYPROC)(char *, char *);` and I call with two char* (for example `s2` in code). I should be able to return value thru second parameter. I successfully pass parameters to C#, but as `string`, not `ref string`, using UnmanagedType.LPStr marshaling. – bkovacic Jun 27 '11 at 12:23
  • Solved it! I've put IntPtr in C# as second parameter and then wrote byte by byte! – bkovacic Jun 27 '11 at 14:21
0

Would this work?

public static int test([In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr)] string[] p)
Marino Šimić
  • 7,318
  • 1
  • 31
  • 61
0

In many cases and as seems to be the situation here (see accepted answer by Remus Rusanu), using the proper marshaling attributes in the API declarations is all that is needed to have the interop "glue" on both ends of the interface start "playing nice with one another" and result in...

  • the proper data values being sent/delivered [good!]
  • Windows not triggering automatic breakpoint upon various suspected
    ... corruption of the heap ... [better! ;-)]

I am adding this answer, 5 months after the original post, because this question and its responses were very useful in fixing a recent interop bug of mine, but failed to mention directly information about the very plausible cause of many interop issues, namely:

Mismatch in the Memory Ownership conventions
(and/or in the memory allocation and freeing methods).

The January 2008 article on MSDN Magazine titled Marshaling between Managed and Unmanaged Code provided me with the info I needed in regards to memory ownership conventions. Indeed this article provides a complete overview of the marshaling process. It covers in specific details and examples the issues of

  • [InAttribute] and [OutAttribute] aka [In] and [Out]
  • Keywords Out and Ref and passing by reference
  • Return values
  • StringBuilder and marshaling
  • Copying and pinning
    (Pinning = optiimization by CLR when it deems it safe to lock data area in the CLR heap and pass corresponding pointers to unmanaged code)
  • Memory Ownership:
    No change allowed vs. In-place change vs. Reference change
    Also, on the need of using CoTaskMemFree() and CoTaskMemAlloc() to free or allocate memory respectively received from or sent to the Managed code.
  • Reverse P/Invoke and delegate lifetime
  • P/Invoke Interop Assistant

This article is very useful because it gathers in a single document and in an accessible fashion information otherwise disseminated across dozen of technically authoritative but dry [and oft' confusing] reference documents (cf. the old but on-point joke about Microsoft's documentation at large).
In short, it makes it a good primer or refresher for casual implementers of interop solutions.

mjv
  • 73,152
  • 14
  • 113
  • 156
  • Since the joke link died, here's a [way-back link](https://web.archive.org/web/20170103094456/http://www.duxcw.com/dcforum/DCForumID7/818.html) to it. In case this link also dies eventually, it's the Microsoft/Helicopter joke. – Zoey Nov 29 '20 at 23:24