tags:

views:

157

answers:

5

Due to how Microsoft implements the heap in their non-DLL versions of the runtime, returning a C++ object from a DLL can cause problems:

// dll.h
DLL_EXPORT std::string somefunc();

and:

// app.c - not part of DLL but in the main executable
void doit()
{
    std::string str(somefunc());
}

The above code runs fine provided both the DLL and the EXE are built with the Multi-threaded DLL runtime library.

But if the DLL and EXE are built without the DLL runtime library (either the single or multi-threaded versions), the code above fails (with a debug runtime, the code aborts immediately due to the assertion _CrtIsValidHeapPointer(pUserData) failing; with a non-debug runtime the heap gets corrupted and the program eventually fails elsewhere).

Two questions:

  1. Is there a way to solve this other then requiring that all code use the DLL runtime?
  2. For people who distribute their libraries to third parties, how do you handle this? Do you not use C++ objects in your API? Do you require users of your library to use the DLL runtime? Something else?
+1  A: 

Is there a way to solve this other then requiring that all code use the DLL runtime?

Not that I know of.

For people who distribute their libraries to third parties, how do you handle this? Do you not use C++ objects in your API? Do you require users of your library to use the DLL runtime? Something else?

In the past I distributed an SDK w/ dlls but it was COM based. With COM all the marshalling of parameters and IPC is done for you automatically. Users can also integrate in with any language that way.

Brian R. Bondy
Thanks for the suggestion about COM. Unfortunately we also need version of the library for various Posix OS's so I need to avoid MS specific technologies.
R Samuel Klatchko
A: 

There is a way to deal with this, but it's somewhat non-trivial. Like most of the rest of the library, std::string doesn't allocate memory directly with new -- instead, it uses an allocator (std::allocator<char>, by default).

You can provide your own allocator that uses your own heap allocation routines that are common to the DLL and the executable, such as by using HeapAlloc to obtain memory, and suballocate blocks from there.

Jerry Coffin
@Jerry - accepting your answer as you provided a viable alternate solution.
R Samuel Klatchko
A: 

If you have a DLL that you want to distribute and you don't want to bind your callers to a specific version to the C-Runtime, do any of the following:

I. Link the DLL to the static version of the C-Runtime library. From the Visual Studio Project Properties page, select the tab for Configuration Properties-> C/C++ -> Code Generation. These an option for selecting the "Runtime library". Select "Multithread" or "Multithreaded Debug" instead of the DLL versions. (Command line equilvalent is /MT or /MTd)

There are a couple of different drawbacks to this approach:

a. If Microsoft ever releases a security patch of the CRT, your shipped components may be vulnerable until your recompile and redist your binary.

b. Heap pointers allocated by "malloc" or "new" in the DLL can not be "free"d or "delete"d by the EXE or other binary. You'll crash otherwise. The same holds true for FILE handles created by fopen. You can't call fopen in teh DLL and expect the EXE to be able to fclose on it. Again, crash if you do. You will need to build the interface to your DLL to be resilient to all of these issues. For starters, a function that returns an instance to std::string is likely going to be an issue. Provide functions exported by your DLL to handle the releasing of resources as needed.

Other options:

II. Ship with no c-runtime depedency. This is a bit harder. You first have to remove all calls to the CRT out of your code, provide some stub functions to get the DLL to link, and specify "no default libraries" linking option. Can be done.

III. C++ classes can be exported cleanly from a DLL by using COM interface pointers. You'll still need to address the issues in 1a above, but ATL classes are a great way to get the overhead of COM out of the way.

selbie
A: 

Your code has two potential problems: you addressed the first one - CRT runtime. You have another problem here: the std::string could change among VC++ versions. In fact, it did change in the past.

The safe way to deal with is to export only C basic types. And exports both create and release functions from the DLL. Instead of export a std::string, export a pointer.

__declspec(export)  void* createObject()
{
     std::string* p = __impl_createObject();
     return (void*)p;
 }

__declspec(export)  void releasePSTRING(void* pObj)
{   
     delete ((std::string*)(pObj));
}
Sherwood Hu
A: 

The simple fact here is, Microsofts implementation aside, C++ is NOT an ABI. You cannot export C++ objects, on any platform, from a dynamic module, and expect them to work with a different compiler or language.

Exporting c++ classes from Dlls is a largely pointless excercise - because of the name mangling, and lack of support in c++ for dynamically loaded classes - the dlls have to be loaded statically - so you loose the biggest benefit of splitting a project into dlls - the ability to only load functionality as needed.

Chris Becke
"the ability to only load functionality as needed" is a benefit indeed. But putting code into DLLs (even if they are all loaded all the time) eases product maintenance and support, so it is even more widely used pattern.
Andrey
How so? seriously? In the context of C++ development Dll usage is particularly restricted :- to actually export C++ classes from Dlls in a first-class-can-inherit-from kind of way requires that the Dlls are always statically linked, and all Dlls (and exes) are always rebuilt together in response to a change in any class definition.All you have added is failure modes: Breaking a pure C++ implementation into Dlls gives no benefits, but introduces the chance of version mismatch that now needs to be managed.
Chris Becke