Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing versions of the MSVCRT

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?

like image 957
Earlz Avatar asked Nov 14 '13 15:11

Earlz


People also ask

What is the difference between UCRT and Msvcrt?

MSVCRT vs UCRT These are two variants of the C standard library on Microsoft Windows. MSVCRT (Microsoft Visual C++ Runtime) is available by default on all Microsoft Windows versions, but due to backwards compatibility issues is stuck in the past, not C99 compatible and is missing some features.

What is UCRT?

The Universal CRT (UCRT) contains the functions and globals exported by the standard C99 CRT library. The UCRT is now a Windows component, and ships as part of Windows 10 and later versions. The static library, DLL import library, and header files for the UCRT are now found in the Windows SDK.

What is _DllMainCRTStartup?

The _DllMainCRTStartup function performs essential tasks such as stack buffer security set up, C run-time library (CRT) initialization and termination, and calls to constructors and destructors for static and global objects.

What is Libcmt lib?

LIBCMT. LIB is a statically linked library that supports multithreaded programs. CRTDLL. LIB is an import library for CRTDLL. DLL that also supports multithreaded programs.


1 Answers

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.

like image 190
Hans Passant Avatar answered Sep 17 '22 18:09

Hans Passant