Domanda

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?

È stato utile?

Soluzione

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.

Altri suggerimenti

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.

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

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top