4

I have a function made in C++ that calls a COM interface's function Its signature:

BOOL func(LPWSTR strIn, __out LPWSTR strOut)
{
  //initcom
  //do something
  // release pointers
}

In C#:

[DllImport("funcdll.dll")]
static extern bool func(String strIn, ref String strOut);

// use it

for(int i=0;i<10;i++)
{
   if(func(strin, strout))
   {
      //do something with strout
   }
}

I have tested my dll in a C++ console application, it works, but in C# it crashes with an unknown error.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Aussay Marshal
  • 157
  • 2
  • 10

5 Answers5

7

You've got three problems that I can see.

  1. The calling conventions don't match. Your C++ code is cdecl and your C# code is stdcall.
  2. The C++ code uses wide strings, but the C# code marshals ANSI strings.
  3. The second parameter doesn't match. Your C# code assumes that the C++ code returns a new pointer to a C string which the C# code then deallocates with the COM allocator. Your C++ code doesn't do this.

Now, dealing with these in more detail.

Calling conventions

This is pretty easy to fix. Simple change the C++ code to stdcall, or the C# code to cdecl. But don't do both. I'd change the C# code:

[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl]

Unicode/ANSI strings

I presume you are wanting to use Unicode strings since you have explicitly selected them in the C++ code. But P/invoke defaults to marshalling ANSI strings. You can change this again in the DllImport like so:

[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl, 
    CharSet=CharSet.Unicode]

Returning a string from C++ to C#

Your current C++ function declaration is so:

BOOL func(LPWSTR strIn, __out LPWSTR strOut)

The __out decorator has no real effect, other than documenting that you want to modify the buffer pointed to by strOut and have those modifications returned to the caller.

Your C# declaration is:

static extern bool func(String strIn, ref String strOut);

Now, ref String strOut simply does not match. A ref string parameter matches this in C++:

BOOL func(LPWSTR strIn, LPWSTR *strOut)

In other words the C# code is expecting you to return a new pointer. In fact it will then proceed to deallocate the buffer you returned in strOut by calling CoTaskMemFree. I'm confident that's not what you want.

Your original C++ code can only return a string to the C# code by modifying the buffer that was passed to it. That code would look like this:

BOOL func(LPWSTR strIn, __out LPWSTR strOut)
{
    ...
    wcscpy(strOut, L"the returned string");
    ...
}

If this is what you want then you should allocate a sufficient buffer in C# in a StringBuilder object.

[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl, 
    CharSet=CharSet.Unicode]
static extern bool func(string strIn, StringBuilder strOut);
...
StringBuilder strOutBuffer = new StringBuilder(128);
bool res = func("input string", strOutBuffer);
string strOut = StringBuilder.ToString();

If you simply cannot decide in the C# code how big a buffer you need then your best bet is to use a BSTR to marshal strOut. See this answer for details.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks, I can't imagine what you write in your 3rd point. I expect the C++ function to return a bool and a reference to the argument which is passed in once it is called. If it doesn't do it, then I issue an alert message for being unable to get what I want. I am now looking for another solution. – Aussay Marshal Oct 24 '11 at 07:57
  • I'll elaborate with some code now that I am at a real machine. But basically the function signatures simply do not match. – David Heffernan Oct 24 '11 at 08:03
3

It's hard for me to tell without seeing the details of your C++ method... but, I've never had much luck using String with P/Invoke.

Try using IntPtr instead of String, and use Marshal.PtrToStringUni for the outgoing string, and marshal your managed string into unmanaged land with Marshal.StringToHGlobalUni and, after the function call, make sure to free the unmanaged string with Marshal.FreeHGlobal.

Also, coming from C99 land, my bools don't work with .NET bools... I don't know why. I have to use byte and check == 1. Don't know if you'll run into this with C++... but, if you want my advice on a place to start which seems least-breakable in my experience, here ya go:

[DllImport("funcdll.dll")]
static extern byte func(IntPtr strIn, out IntPtr strOut);

// use it
string myString = "Testing";

IntPtr stringOut;
IntPtr stringIn = Marshal.StringToHGlobalUni(myString);

   if(func(stringIn, out stringOut) == 1)
   {
      //do something with strout
      string stringOutValue = Marshal.PtrToStringUni(stringOut);
      // Depending on how you dealt with stringOut in your
      // unmanaged code, you may have to: Marshal.FreeCoTaskMem(stringOut);
      // or Marshal.FreeHGlobal(stringOut) if you're returning
      // an owning reference to a copied string.
   }

Marshal.FreeHGlobal(stringIn);
Steve
  • 31,144
  • 19
  • 99
  • 122
  • 3
    This is largely bad advice. use of IntPtr here just creates needless complexity on the C# side. That complexity is easy to avoid. – David Heffernan Oct 24 '11 at 07:45
  • Hrm... it works consistently for me.. and coming from unmanaged land it's very easy to understand what's going on as opposed to some of the other "magic" possibilities. I think calling it "bad advice" might be going a bit far. – Steve Oct 24 '11 at 07:47
  • Of course it works but it's needless complexity. It's very easy to marshal strings without IntPtr. The fact that you refer to that as magic merely indicates that you haven't yet mastered doing so. That's on you. – David Heffernan Oct 24 '11 at 07:49
  • If it works then it's definitely not "bad advice". "Sub-optimal" maybe... but far from "bad". Why are you so cranky? – Steve Oct 24 '11 at 07:51
  • No, I'd say it was bad advice. To do your code right you really need try/finally which you missed. Your treatment of the second parameter is very incomplete. There's simply no need whatsoever for this complexity. And what you say about `bool` is wrong. Default marshalling of C# `bool` is to `BOOL`. There's no C99 `bool` here. I'm not cranky. I just believe that it's important to give the best advice possible. I don't think this answers does this. – David Heffernan Oct 24 '11 at 08:20
  • Sample code always skips try/catch for brevity and clarity. It's always up to the consumer of sample code to make sure they understand and apply the concepts with the standard rigor. When I answered no one else had answered - so I was simply trying to help out... if you've got a problem with that, too bad - I'm glad you did what you did, and submitted your own answer. That seems the appropriate response. I, for one, am perfectly happy with using my IntPtrs. I don't think they add much complexity at all - and it's always contained within one method in an interop layer anyways. Big deal. – Steve Oct 24 '11 at 09:38
2

I beleive you're having a calling convention mismatch. The default calling convention in C++ is cdecl while for .NET it's stdcall. try

[DllImport("funcdll.dll", CallingConvention = CallingConvention.Cdecl)] 
static extern bool func(String strIn, ref String strOut); 

Also you might have to specifically tell the marshallar that you want to marshal the strings as LPWSTR using [MarshalAs(UnmanagedType.LPWStr)] attributes.

[DllImport("funcdll.dll", CallingConvention = CallingConvention.Cdecl)] 
static extern bool func([MarshalAs(UnmanagedType.LPWStr)]String strIn
                       ,[MarshalAs(UnmanagedType.LPWStr)]ref String strOut);

have a look at http://msdn.microsoft.com/en-us/library/s9ts558h.aspx

Phyx
  • 2,697
  • 1
  • 20
  • 35
  • Much simpler to use CharSet in DllImport attribute rather than individual MarshalAs. – David Heffernan Oct 24 '11 at 07:42
  • 2
    I don't see the two as being equal, Setting Charset to Unicode only tells it that the string is Unicode, Bstr and LPWStr are both unicode and you still don't know which one it is. Explicitly setting it using MarshalAs is better imho – Phyx Oct 24 '11 at 07:48
  • The two options are equivalent. Defaults to LPTSTR when you just use CharSet. What's more, a ref LPWSTR is effectively a BSTR since that's what the marshaller is going to do with the returned memory. And that doesn't match the C++ declaration in the code. – David Heffernan Oct 24 '11 at 07:51
  • http://msdn.microsoft.com/en-us/library/aa719692(v=VS.71).aspx Doesn't mention anything about CharSet defaulting to anything. In the absence of any explicit documentation I don't trust the implicit. – Phyx Oct 24 '11 at 08:03
  • 1
    And as for the second part, yes That is an issue, I didn't see that when i first read the question. BUt since when are LPWSTR (possibly null terminated, possibly length prefixed) and BStr (always null terminated and length prefixed) equal? – Phyx Oct 24 '11 at 08:05
  • Thanks Phyx for your help, it still doesn't work. I get same crash, and undebuggable problem. "Your ...exe has encountered a problem and needs to be stopped....send a message to MS ? Yes or No ? etc" – Aussay Marshal Oct 24 '11 at 08:09
  • @Assay That's because David is right about the ref variable, The marshaller will crash on this while trying to decallocate the string. That's a bit harder to solve depending on where you want to do the allocation as he said. If you allocate everything in C# then you could try a StringBuilder instead. – Phyx Oct 24 '11 at 08:13
  • The link you gave contains this test "If you omit the character-set keyword, as is done in the first declaration statement, the DllImportAttribute.CharSet field defaults to the ANSI character set. " – David Heffernan Oct 24 '11 at 08:22
2

In C++ make sure you have something like this (at least for the second argument)

extern "C" BOOL __stdcall func( BSTR * pBstr )
{
    *pBstr = SysAllocString( L"Foobar" );
    return 0;
}

In C# write something like this (for the second argument) :

static extern bool func( [MarshalAs(UnmanagedType.BStr)] ref String strOut); 
1

Sorry I do not have a big answer but just remember something from my experience.. did you try to use StringBuilder eg changing your c# import function signature as

   [System.Runtime.InteropServices.DllImport("funcdll.dll")]
static extern bool func(String strIn, System.Text.StringBuilder strOut);
Surjit Samra
  • 4,614
  • 1
  • 26
  • 36