0

I learned that when a C++ caller used another C++ DLL, the caller had to allocate a string with a certain max size, and pass that pointer and the max size to a C++ callee/DLL.

So the C++ caller would do something like:

MyCPPDllFunction(char *out, int maxOutSize);

Now I learned that when the C++ caller uses a C# DLL, it can look like this:

char *output = MyCSharpDllFunction();

I don't understand how the C# callee is able to allocate the memory, where the C++ callee wasn't able to?

Hugh Myron
  • 187
  • 1
  • 1
  • 13
  • No it's a C++ caller in both cases, and either a C++ DLL or a C# DLL. Sorry if that wasn't clear – Hugh Myron Jul 13 '18 at 07:02
  • 1
    Life is quite simple when you have a garbage collector. Just allocate memory and never worry about having to release it again. C++ does not have that convenience. Your intended C++ code has a quite a nasty bug, it either has a dangling pointer bug or is leaking memory. Hard to guess which when we can't see the C# declaration. Beware that the DllExport library is hard to use correctly. – Hans Passant Jul 13 '18 at 08:28

3 Answers3

2

I don't understand how the C# callee is able to allocate the memory, where the C++ callee wasn't able to?

You are looking for the wrong problem. The problem isn't "how the C# is able to allocate the memory". Anyone can allocate the memory... And in too many ways :-) The problem is always "how will you free the memory?", because freeing the memory is as much important as allocating the memory (not freeing the memory will cause memory leaks, that will make your program memory occupation go balloon, while freeing it with the wrong allocator won't free it at least (see then previous point), and will crash the program at worst).

Now, the OS (Windows for example) gives you some allocators (CoTaskMemAlloc for example) that anyone can use, but the problem is that normally in C/C++ you'll use malloc/new. And the problem is here: these allocators could be "different" (distinct) between two dlls: two dlls compiled in C/C++ (but for example with different versions of Visual C++, or in debug vs release mode, or one using the runtime as a dll and the other linking directly the runtime, or compiled with different C compilers) will have distinct malloc (and distinct new in C++). Having distinct malloc/new they'll have distinct free/delete, so that the free of one dll can't free the memory of the other dll.

Now... Using this signature:

void MyCPPDllFunction(char *out, int maxOutSize);

the caller has to allocate the memory... So the caller knows how to free the memory... No memory leak :-)

With this signature:

char *MyCPPDllFunction();

the callee has to allocate the memory... And now how can the caller free it? The callee could export another method:

void FreeMemoryAllocatedByMe(void *ptr)
{
    free(ptr);
}

then the caller would call it and everything would be solved, because that free would be the callee free.

Now... In general, when C# marshals objects (and strings), it uses CoTaskMemAlloc to allocate the memory, and it expects that if it receives memory, it has to free it with CoTaskMemFree. So in you C++->C# example, the C++ should

CoTaskMemFree(output);
xanatos
  • 109,618
  • 12
  • 197
  • 280
1

I guess the reason is the following:

When you call c# api from c++ you call the code managed by CLR virual machine, therefore the memory allocation is managed automatically by virtual machine itself, allowing you not to bother with manual buffer allocation.

Nicklaus Brain
  • 884
  • 6
  • 15
1

How the value is returned is irrespective of the language in use. It's defined only by the calling signature of the function. So I could write the following DLL source in C++:

#include <windows.h>

BOOL WINAPI DllMain(
  _In_ HINSTANCE hinstDLL,
  _In_ DWORD     fdwReason,
  _In_ LPVOID    lpvReserved
)
{
    return TRUE;
}

__declspec(dllexport) char *function1()
{
    char *foo = new char[100];
    // do stuff to fill in foo
    return foo;
}

__declspec(dllexport) void function2(char *out, int maxOutSize)
{
    // do stuff to fill in out
}

which is completely legal, and defines C++ entry points with signatures that exactly match both the functions listed in the OP's question.

Note that this uses C++ new[] to allocate the memory for function1() so it will be necessary to release it at some point using delete[].

In a similar vein, if the memory is allocated by the CLR in the C# case, then you'd either need to pass it back to the C# DLL to be properly released, or find a safe way of destroying it in C++, assuming such an operation is even possible.

I'll be 100% honest here and say I have no clue at all if the C++ runtime and C# CLR will play nicely together with memory allocation. So it may be that the only option is to pass it back to C# for deallocation.

-- Edit --

I'm going to link this question as well for further reference, since it deals with a different view of the same issue, and in particular the accepted answer has some extremely useful information in it.

Passing strings from C# to C++ DLL and back -- minimal example

dgnuff
  • 3,195
  • 2
  • 18
  • 32
  • 1
    I just read that one pattern people use is that C++ (or any caller) would call some function in C# (or any DLL) to free the memory. Thanks for the answer, I'm going to read it again in the morning to digest some more – Hugh Myron Jul 13 '18 at 07:05
  • 1
    @HughMyron Yes. Based on much experience, that will be the wisest thing to do: use the language that allocated the memory to deallocate it. With the exception of calling C's `free()` from C++, I'd be very suspicious of any attempt that uses one language's runtime to allocate memory, and another language's runtime to deallocate it. – dgnuff Jul 13 '18 at 07:09