3
votes

I have a dll written in C#, and exposed to COM. I am using the dll in builder... I can instantiate the class, but am having problems with marshalling the return value from the C# method calls.

C#

public string GetValue([MarshalAs(UnmanagedType.LPWStr)] string key)
{
   return "value";
}

The translated function as it's imported into builder:

virtual HRESULT STDMETHODCALLTYPE GetValue(LPWSTR key/*[in]*/, 
                                           BSTR* pRetVal/*[out,retval]*/) = 0;

I know very little about C++. The 'key' parameter gets passed in fine because I'm able to use the 'MarshalAs' attribute on the parameter, but I either don't know how to declare it for the return value, or don't know how to call the function on the C++ side (I've tried several things, just guessing).

UPDATE: Okay, I was just able to solve the issue by taking Anton's example and trying modifications based on Hans' comments. Antons answer works precisely as he shows, but because of the concerns expressed about the memory management issue, I ended up not applying the return attribute in C#, and the C++ code calls the function as follows:

BSTR result;
obj->GetValue(key, &result);
SysFreeString(key);
SysFreeString(result);

I wish I could give credit to both answers for helping me with this, they were both necessary to supplying me with the information I needed.

2

2 Answers

7
votes

You can apply a [return:] attribute but it is a Really Bad Idea. The problem with this function signature is that the callee must allocate the buffer for the string and the caller must release it. That requires both to use the same heap. This will not be the case when you force it to use LPWSTR, the CLR uses its own heap and you cannot get to it from your native code, you can't get the required heap handle.

Both pieces of code must use the same heap. And there is one especially for this purpose, the COM heap. The BSTR is a string type that uses that heap, the CLR automatically uses it as you could tell from the signature. To use it, simply access the pRetVal pointer after the call, it is a wchar_t* under the hood. And you must release it afterwards, call SysFreeString().

4
votes

To apply an attribute to the return value:

[return: MarshalAs(UnmanagedType.LPWStr)]
public string GetValue([MarshalAs(UnmanagedType.LPWStr)] string key)
{
   return "value";
}

You will need to free the string manually using (I think) CoTaskMemFree:

LPWSTR result ;
if (SUCCEEDED (obj->GetValue (key, &result)))
{
    // use result and free it when no longer needed
    CoTaskMemFree (result) ;
}