I am writing wrappers using C++/CLR. The managed C# class has a function signature as below
//C#
int WriteToInstrument(string command, ref string response, int stage);
I have to write a C++ wrapper to this function in something like the following signature
//C++
int WriteToInstrumentWrap(const char * command, char * response, int stage);
My question is: how can I handle the conversion from "ref string" in C# to char* in C++? Or how can I handle the situation that requires to take a ref string from C# that can be used in C/C++? Many thanks in advance.
I'll add some examples of code I've written this morning. In general, when speaking of returning objects (in the broad meaning where even a char*
string is an object), the big questions in C/C++ are:
One last optional question is if the memory must be really freed: a method could return a pointer to an internal object that has a lifetime equal to the lifetime of the program and that mustn't be freed. For example:
const char* Message()
{
return "OK";
}
You mustn't free the memory returned by Message()
!
This questions get even more complex when you are writing a library (a dll) that will be used by other programs: the malloc
and the new
that are used in a dll can be different/distinct from the malloc
and the new
used by the main program (or by another dll), so that you shouldn't free
with your (main program) free the memory that is malloc
(ed) by a dll.
There are three possible solutions to this particular problem:
LocalAlloc
and CoTaskMemAlloc
. They are even accessible from .NET (Marshal.AllocHGlobal
and Marshal.AllocCoTaskMem
). In this way the main application can free the memory allocated by the dllFree()
method that must be used to free the memory allocated by the dllSetAllocator(void *(*allocator)(size_t))
and SetFree(void (*free)(void*))
, so methods that store a function pointer, that the main application can use to set the allocator and free to be used by the dll, so that they are shared between the main application and the dll. The dll will use those allocators. Note that SetAllocator(malloc); SetFree(free)
if done by the main application is perfectly legal: now the dll will use the main application's malloc
, and not the dll's malloc
!As an important sidenote: we are in 2018. It is at least 15 years that you should have forgotten of char*
for strings in C for Windows. Use wchar_t
. Always.
And finally some code :-)
Now... given (C# code):
int WriteToInstrument(string command, ref string response, int stage)
{
response = "The quick brown fox jumps over the lazy dog";
return 0;
}
Simple method that calls WriteToInstrument
and then copies the response
result to an ansi string (char*
). The buffer is allocated by the caller, and is of size length
. After the method is executed, length
contains the number of characters used (including the terminating \0
). The response
is always \0
terminated. The problem here is that the response
could get truncated and/or the caller could allocate a buffer too much big (that won't really protect it from the truncation problem, if it is unlucky :-) ). I'll repeat myself here: using char*
for strings in 2018 is ancient technology.
// Utility method to copy a unicode string to a fixed size buffer
size_t Utf16ToAnsi(const wchar_t *wstr, char *str, size_t length)
{
if (length == 0)
{
return 0;
}
// This whole piece of code can be moved to a method
size_t length2 = WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, (int)length, nullptr, nullptr);
// WideCharToMultiByte will try to write up to *length characters, but
// if the buffer is too much small, it will return 0,
// **and the tring won't be 0-terminated**
if (length2 != 0)
{
return length2;
}
// Buffer too much small
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// We add a terminating 0
str[length - 1] = 0;
return length;
}
// Big bad error, shouldn't happen. Return 0 but terminate the string
str[0] = 0;
return 0;
}
Example of use:
char response[16];
size_t length = sizeof(response) / sizeof(char); // useless / sizeof(char) == / 1 by definition
WriteToInstrumentWrap1("cmd1", response, &length, 1);
std::cout << "fixed buffer char[]: " << response << ", used length: " << length << std::endl;
or (using std::vector<>
/std::array<>
)
//Alternative: std::array<char, 16> response;
std::vector<char> response(16);
size_t length = response.size();
WriteToInstrumentWrap1("cmd1", response.data(), &length, 1);
std::cout << "fixed buffer vector<char>: " << response.data() << ", used length: " << length << std::endl;
Simple method that calls WriteToInstrument
and then copies the response
result to an unicode string (wchar_t*
). The buffer is allocated by the caller, and is of size length
. After the method is executed, length
contains the number of characters used (including the terminating \0
). The response
is always \0
terminated.
// in input length is the size of response, in output the number of characters (not bytes!) written to response
// (INCLUDING THE \0!). The string is always correctly terminated.
int WriteToInstrumentWrap2(const wchar_t *command, wchar_t *response, size_t *length, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
*length = (size_t)str2->Length < *length ? str2->Length : *length - 1;
memcpy(response, pch, *length * sizeof(wchar_t));
response[*length] = '\0';
*length++;
return res;
}
Example of use:
wchar_t response[16];
size_t length = sizeof(response) / sizeof(wchar_t);
WriteToInstrumentWrap2(L"cmd1", response, &length, 1);
std::wcout << L"fixed buffer wchar_t[]: " << response << L", used length: " << length << std::endl;
or (using std::vector<>
/std::array<char, 16>
)
//Alternative: std::array<wchar_t, 16> response;
std::vector<wchar_t> response(16);
size_t length = response.size();
WriteToInstrumentWrap2(L"cmd1", response.data(), &length, 1);
std::wcout << L"fixed buffer vector<wchar_t>: " << response.data() << ", used length: " << length << std::endl;
All the next examples will use char
instead of wchar_t
. It is quite easy to convert them. I'll repeat myself here: using char*
for strings in 2018 is ancient technology. It is like using ArrayList
instead of List<>
Simple method that calls WriteToInstrument
, allocates the response
buffer using CoTaskMemAlloc
and copies the result to an ansi string (char*
). The caller must CoTaskMemFree
the allocated memory. The response
is always \0
terminated.
// Memory allocated with CoTaskMemAlloc. Remember to CoTaskMemFree!
int WriteToInstrumentWrap3(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)CoTaskMemAlloc(length * sizeof(char));
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Example of use:
char *response;
WriteToInstrumentWrap3("cmd1", &response, 1);
std::cout << "CoTaskMemFree char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with CoTaskMemFree!
CoTaskMemFree(response);
Simple method that calls WriteToInstrument
, allocates the response
buffer using a "private" "library" allocator and copies the result to an ansi string (char*
). The caller must use the library deallocator MyLibraryFree
to free the allocated memory. The response
is always \0
terminated.
// Free method used by users of the library
void MyLibraryFree(void *p)
{
free(p);
}
// The memory is allocated through a proprietary allocator of the library. Use MyLibraryFree() to free it.
int WriteToInstrumentWrap4(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)malloc(length);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Example of use:
char *response;
WriteToInstrumentWrap4("cmd1", &response, 1);
std::cout << "Simple MyLibraryFree char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with the MyLibraryFree() method
MyLibraryFree(response);
Simple method that calls WriteToInstrument
, allocates the response
buffer using a settable (through the SetLibraryAllocator
/SetLibraryFree
methods) allocator (there is a default that is used if no special allocator is selected) and copies the result to an ansi string (char*
). The caller must use the library deallocator LibraryFree
(that uses the allocator selected by SetLibraryFree
) to free the allocated memory or if it has setted a different allocator, it can directly use that deallocator. The response
is always \0
terminated.
void *(*libraryAllocator)(size_t length) = malloc;
void (*libraryFree)(void *p) = free;
// Free method used by library
void SetLibraryAllocator(void *(*allocator)(size_t length))
{
libraryAllocator = allocator;
}
// Free method used by library
void SetLibraryFree(void (*free)(void *p))
{
libraryFree = free;
}
// Free method used by library
void LibraryFree(void *p)
{
libraryFree(p);
}
// The memory is allocated through the allocator specified by SetLibraryAllocator (default the malloc of the dll)
// You can use LibraryFree to free it, or change the SetLibraryAllocator and the SetLibraryFree with an allocator
// of your choosing and then use your free.
int WriteToInstrumentWrap5(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)libraryAllocator(length);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Example of use:
void* MyLocalAlloc(size_t size)
{
return LocalAlloc(0, size);
}
void MyLocalFree(void *p)
{
LocalFree(p);
}
and then:
// Using the main program malloc/free
SetLibraryAllocator(malloc);
SetLibraryFree(free);
char *response;
WriteToInstrumentWrap5("cmd1", &response, 1);
std::cout << "SetLibraryAllocator(malloc) char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Here I'm using the main program free, because the allocator has been set to malloc
free(response);
or
// Using the Windows LocalAlloc/LocalFree. Note that we need to use an intermediate method to call them because
// they have a different signature (stdcall instead of cdecl and an additional parameter for LocalAlloc)
SetLibraryAllocator(MyLocalAlloc);
SetLibraryFree(MyLocalFree);
char *response;
WriteToInstrumentWrap5("cmd1", &response, 1);
std::cout << "SetLibraryAllocator(LocalAlloc) char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Here I'm using diretly the Windows API LocalFree
LocalFree(response);
More complex method that calls WriteToInstrument
but has as a parameter an allocator
that will be used to allocate the response
buffer. There is an addition parameter par
that will be passed to the allocator
. The method then will copy the result as an ansi string (char*
). The caller must free the memory using a specific deallocator based on the allocator
used. The response
is always \0
terminated.
// allocator is a function that will be used for allocating the memory. par will be passed as a parameter to allocator(length, par)
// the length of allocator is in number of elements, *not in bytes!*
int WriteToInstrumentWrap6(const char *command, char **response, char *(*allocator)(size_t length, void *par), void *par, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = allocator(length, par);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Examples of use (multiple allocator showed: vector<>
, malloc
, new[]
, unique_ptr<>
):
Note the use of the par
parameter.
template<typename T>
T* vector_allocator(size_t length, void *par)
{
std::vector<T> *pvector = static_cast<std::vector<T>*>(par);
pvector->resize(length);
return pvector->data();
}
template<typename T>
T* malloc_allocator(size_t length, void *par)
{
return (T*)malloc(length * sizeof(T));
}
template<typename T>
T* new_allocator(size_t length, void *par)
{
return new T[length];
}
template<typename T>
T* uniqueptr_allocator(size_t length, void *par)
{
std::unique_ptr<T[]> *pp = static_cast<std::unique_ptr<T[]>*>(par);
pp->reset(new T[length]);
return pp->get();
}
and then (note the fact that sometimes one of the parameter passed to WriteToInstrumentWrap6
is useless
because we already have a pointer to the buffer):
{
std::vector<char> response;
char *useless;
WriteToInstrumentWrap6("cmd1", &useless, vector_allocator<char>, &response, 1);
std::cout << "vector char: " << response.data() << ", used length: " << response.size() << std::endl;
// The memory is automatically freed by std::vector<>
}
{
char *response;
WriteToInstrumentWrap6("cmd1", &response, malloc_allocator<char>, nullptr, 1);
std::cout << "malloc char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with free
free(response);
}
{
char *response;
WriteToInstrumentWrap6("cmd1", &response, new_allocator<char>, nullptr, 1);
std::cout << "new[] char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with delete[]
delete[] response;
}
{
std::unique_ptr<char[]> response;
char *useless;
WriteToInstrumentWrap6("cmd1", &useless, uniqueptr_allocator<char>, &response, 1);
std::cout << "unique_ptr<> char: " << response.get() << ", used length: " << strlen(response.get()) + 1 << std::endl;
// The memory is automatically freed by std::unique_ptr<>
}