Do you always marshal “const char *” to string in C# when calling native C++ class method from C# ?
Most of time, yes, we would simply marshal “const char *” to string in C#/.NET and it just works. but it does not always work as expected from the native C++ DLL because “const char*” can also represent an array of binary data in bytes as read-only array. The reason of my writing this post is that I read a question from here, and I realized how NGen++ is superior than SWIG on generating C# wrapper for native C++ DLLs because SWIG has to reply on complicated macro called typemap. With all the respects to SWIG which is open source and brings the bridge between C++ and all other various languages with huge efforts, but I would say this is exactly the main reason I would not want to use SWIG even before I wrote NGen++, I would want an automated tool which only requires me to do a few minor configuration to generate the C# wrapper class automatically without writing any type of scripts. I guess that is one of the big reasons I have been writing my own C# wrapper generator for native C++ DLL.
When a parameter of “const char*” or “char *” contains binary data, it can also include zero(0) which represents the ending of a C/C++ string, so marshaling const char * or “char*” to a string definitely does not work as expected from the native C++ DLL because the string will get cut in the middle when being passed from C# code to the native C++ class, It must be marshaled to an array of bytes with a length if it is not a fixed-length array.
Since 90+% of the time, “const char*” does represent a string in C++, so NGen++ marshals “const char*” to string by default, but it will also allow developers to marshal “const char*” to an array of byte by using the C++ to C# type marshaling designer. You would probably think that using a type marshaling designer would be as inconvenient as using typemap scripting, but the fact is that you would only need to spend a few seconds to configure the type mapping using the type marshaling designer without any further knowledge of anything else beyond using the C++ to C# type marshaling designer, especially involving writing scripted typemap which would be also cause additional errors since it is a another type of language and very often developers would probably introduce new errors.
Here is the C++ implementation of C++ class of Buffer I made up. The function of SetData is what we will be discussing.
class DLLAPI Buffer { char * pBuffer; int nLength; public: Buffer() { pBuffer = NULL; } int GetLength() const { return nLength; } int SetData(const char* data, int length) { if (pBuffer != NULL) { free(pBuffer); pBuffer = NULL; length = 0; } if (data != NULL && length > 0) { pBuffer = (char *)malloc(length); memmove(pBuffer, data, length); nLength = length; return length; } return 0; }; };
By default, the following wrapper method of SetData will be generated by NGen++,
public int SetData(string data, int length) { return LibPerfEvaluationWin32.Buffer_SetData(this.ptrObject, data, length); } // Explicit P/Invoke Signature for calling C++ class method from C# .NET [HandleProcessCorruptedStateExceptions] [SuppressUnmanagedCodeSecurity] [DllImport("LibPerfEvaluation.dll", EntryPoint="#115", CallingConvention=CallingConvention.ThisCall)] public extern static int Buffer_SetData(IntPtr thisObject, string data, int length);
By using the C++ to C# type marshaling designer shown below, I was able to quickly convert the implementation of the wrapper method to an array of bytes.
The implementation of SetData wrapper method will look like exactly as expected from the native C++ class of Buffer as shown below. As you see, the signature of the wrapper method changed, so did the P/Invoke signature. Since array parameter of the wrapper method already has the length of the array, it does not need another parameter to tell the length of the array.
// C# Wrapper Method calling C++ method via Explicit P/Invoke public int SetData(sbyte[] data) { int length = (int)data.Length; var localReturnValue = LibPerfEvaluationWin32.Buffer_SetData(this.ptrObject, data, length); return localReturnValue; } // Explicit P/Invoke Signature for calling C++ class method from C# .NET [HandleProcessCorruptedStateExceptions] [SuppressUnmanagedCodeSecurity] [DllImport("LibPerfEvaluation.dll", EntryPoint="#115", CallingConvention=CallingConvention.ThisCall)] public extern static int Buffer_SetData(IntPtr thisObject, sbyte[] data, int length);