7

So, I have a C++ library with a statically linked copy of the MSVCRT. I want for anyone to be able to use my library with any version of the MSVC Runtime. What is the best way to accomplish this goal?

I'm already being quite careful with how things are done.

  1. Memory never passes the DLL barrier to be freed
  2. Runtime C++ objects aren't passed across barriers (ie, vectors, maps, etc.. unless they were created on that side of the barrier)
  3. No file handles or resource handles are passed between barriers

Yet, I still have some simple code that causes heap corruption.

I have an object like so in my library:

class Foos
{
public: //There is an Add method, but it's not used, so not relevant here
    DLL_API Foos();
    DLL_API ~Foos();

private:
    std::map<std::wstring, Foo*> map;
};

Foos::~Foos()
{
    // start at the begining and go to the end deleting the data object
    for(std::map<std::wstring, Foo*>::iterator it = map.begin(); it != map.end(); it++)
    {
        delete it->second;
    }
    map.clear();
}

And then I use it from my application like so:

void bar() {
    Foos list;
}

After I call this function from anywhere, I get a debug warning about stack corruption. And If I actually let it run out, it actually does corrupt the stack and segfault.

My calling application is compiled with Visual Studio 2012 platform tools. The library is compiled using Visual Studio 2010 platform tools.

Is this just something I should absolutely not be doing, or am I actually violating the rules for using multiple runtimes?

Earlz
  • 62,085
  • 98
  • 303
  • 499
  • Does the error still happen if you remove code from the destructor? (I am asking because this code should be a no-op) – anatolyg Nov 14 '13 at 15:45
  • @anatolyg yes, I still get stack corruption when removing all of the code from the destructor – Earlz Nov 14 '13 at 15:52
  • All it does it creates the `map` field (because it's not a pointer) and then destructs it. That apparently causes stack corruption. If I down-target to VS2010 and build my app that way, it works fine – Earlz Nov 14 '13 at 15:53

4 Answers4

9

Memory never passes the DLL barrier

But, it does. Many times in fact. Your application created the storage for the class object, in this case on the stack. And then passes a pointer to the methods in the library. Starting with the constructor call. That pointer is this inside the library code.

What goes wrong in a scenario like this one is that it didn't create the correct amount of storage. You got the VS2012 compiler to look at your class declaration. It uses the VS2012 implementation of std::map. Your library however was compiled with VS2010, it uses a completely different implementation of std::map. With an entirely different size. Huge changes thanks to C++11.

This is just complete memory corruption at work, code in your application that writes stack variables will corrupt the std::map. And the other way around.

Exposing C++ classes across module boundaries are filled with traps like that. Only ever consider it when you can guarantee that everything is compiled with the exact same compiler version and the exact same settings. No shortcuts on that, you can't mix Debug and Release build code either. Crafting the library so no implementation details are exposed is certainly possible, you have to abide by these rules:

  • Only expose pure interfaces with virtual methods, argument types must be simple types or interface pointers.
  • Use a class factory to create the interface instance
  • Use reference counting for memory management so it is always the library that releases.
  • Nail down core details like packing and calling convention with hard rules.
  • Never allow exceptions to cross the module boundary, only use error codes.

You'd be well on you way to write COM code by then, also the style you see used in for example DirectX.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
3

map member variable is still created by application with some internal data allocated by application and not DLL (and they may use different implementations of map). As a rule of thumb don't use stack objects from DLLs, add something like Foos * CreateFoos() in your DLL.

Alex Watson
  • 519
  • 3
  • 13
  • 1
    Well, it's basically wrap all allocations then. I tried `new Foos()` and then there would be heap corruption at `delete list` – Earlz Nov 14 '13 at 16:08
  • 2
    @Earlz: it's more complicated than that, in general you don't want to expose the internals of any data structure that may have a different binary layout or may allocate memory via CRT features (and STL subobjects will both have different binary layout *and* allocate memory using `new`). In practice, it means that you have to resort to the PIMPL idiom almost everywhere. – Matteo Italia Nov 14 '13 at 16:13
  • @MatteoItalia Actually that (your comment) is the best answer! –  Nov 14 '13 at 16:31
3

Runtime C++ objects aren't passed across barriers (ie, vectors, maps, etc.. unless they were created on that side of the barrier)

You are doing exactly that. Your Foos object is being created by the main program on the stack and then being used in the library. The object contains a map as a part of it...

When you compile the main program it looks at the header files etc to determine how much stack space to allocate for the Foos object. And the calls the constructor which is defined in the library... Which might have been expecting an entirely different layout/size of the object

jcoder
  • 29,554
  • 19
  • 87
  • 130
  • 1
    Notice that this depends not only on the CRT version, but even from the compilation flags. STL objects do have a different memory layout when compiled in debug or release mode. – Matteo Italia Nov 14 '13 at 16:18
  • So, if I were to change the `map` bit, so that it was a pointer, and was allocated and deallocated in the constructor/destructor, it would probably be ok, because then the DLL would be in control of allocation... I thought even listing `private` fields in header files was optional, guess it's required for allocations though. – Earlz Nov 14 '13 at 16:25
  • Yes that would likely work. No listing private fields is not optional :) – jcoder Nov 14 '13 at 17:01
0

It may not fit your needs, but don't forgot that implementing the whole thing in header files simplifies the problem (sort of) :-)

manuell
  • 7,528
  • 5
  • 31
  • 58