views:

646

answers:

4

I tried different things but i'm getting mad with Interop.

(here the word string is not referred to a variabile type but "a collection of char"): I have an unmanaged C++ function, defined in a dll, that i'm trying to access from C#, this function has a string parameter and a string return value like this:

string myFunction(string inputString)
{
}

What should be string in C++ side? and C# one? and what parameters need DllImport for this?

+1  A: 

The only good way that I know of doing this is to write a .NET C++ wrapper class using Managed C++ Extensions, and within the .NET C++ object call your native C++ code. There are functions in the managed extensions to convert a System.String to a char* or any other type of unmanaged string.

Basically you create a .NET class using C++ and expose it from an assembly, and internally to that assembly you can call your native C++ code. The other way is to add a pure C function to your C++ code using P/Invoke and then call your C code from C# and have your C function call your C++ code. This will work, but I tend to try to use managed code as much as possible.

mjmarsh
+1  A: 

The biggest problem with passing strings from C++ back to C# is the memory allocation. The GC should be able to know how to cleanup the memory allocated for this string. Since C# has extensive COm interop support, it does know about COM BSTRs and how to allocate and deallocate these. Thus the easiest way to do this would be to use BSTR on the C++ side and string on the C# side.

Note, using BSTRs does not imply that your function has to be expose through COM.

Franci Penov
+2  A: 

What I've found to work best is to be more explicit about what's going on here. Having a string as return type is probably not recommended in this situation.

A common approach is to have the C++ side be passed the buffer and buffer size. If it's not big enough for what GetString has to put in it, the bufferSize variable is modified to indicate what an appropriate size would be. The calling program (C#) would then increase the size of the buffer to the appropriate size.

If this is your exported dll function (C++):

extern "C" __declspec void GetString( char* buffer, int* bufferSize );

Matching C# would be the following:

void GetString( StringBuilder buffer, ref int bufferSize );

So to use this in C# you would then do something like the following:

int bufferSize = 512;
StringBuilder buffer = new StringBuilder( bufferSize );
GetString( buffer, ref bufferSize );
Scott Saad
This is the correct approach. Note that you don't have to pass the buffer size by reference. The C/C++ should terminate the string, buffer.ToString() produces the return value.
Hans Passant
Thank you all for the answers.I read somewhere that StringBuilder is used for output string, but i need also to pass to C++ function an input string.. what type should i use? In the C++ side i've put char* and C# side string, but it doesn't work.Finally, can i define the output "string" (C++ side) as a const char*?Or i must have a char*?
Smjert
@Smjert: Using a const char* is a good way to go if you're not changing it. This should map just fine to a string on the C# side. What type of problems are you seeing?
Scott Saad
@nobugz: I'll check that the bufferSize does not need to be passed by reference and update the answer as I find it. That would be a great tip if indeed it doesn't have to be a reference, yet I'm not sure I understand why that's the case.
Scott Saad
@Scott - the P/Invoke marshaller has explicit support for zero terminated strings. It knows how to stuff a char* into a StringBuilder properly. There would only be a problem if the char* wasn't zero terminated like 'C' strings normally are.
Hans Passant
Well my current problem now is to debug the unmanaged DLL since the string returns with nothing in it..The problem is that i'm on a 64bit OS and i'm trying to understand how to compile both dll and C# with 32bit.Also i can't understand which platform Visual Studio is compiling with since i've put platform target x86 in the C# project properties page but the configuration is Debug|Mixed Platforms..and is the only working, since if i put x86 i have tons of Assert errors...
Smjert
+1  A: 

The "string" return value is the problem. The P/Invoke marshaller is going to call CoTaskMemFree() on the pointer you return. That's not going to work well unless you used CoTaskMemAlloc() in your C/C++ code to allocate the string buffer. Which is a fairly unusual thing to do.

The best solution is to allow the caller of your code to pass a pointer to a buffer and the buffer length to you as arguments. That way all memory allocation happens on one side. Scott showed you how to do this.

Hans Passant