33

I am calling into a statically linked .dll, and I see this error:

enter image description here

I wrote both the .dll and the calling code. This error should not be occurring. I am wondering if anyone else has encountered it before? The .dll only contains about 10 lines of code, its just a test .dll to see how dlls work in general. It blows up when I pass a std::string back out of the .dll.

I am using Visual Studio 2012 and C++.

What I will try next

From Debug assertion... _pFirstBlock == pHead:

This problem can occur if one uses the single-threading libraries in a multithreaded module.

Tomorrow, I'll try recompiling the Boost static libraries in multi-threaded mode (my .dll is set to multi-threaded static mode).

What I will try next

See Using strings in an object exported from a DLL causes runtime error:

You need to do one of two things

  1. Make both the DLL and the client that use it both link to the DLL version of the CRT (e.g. not statically).
  2. OR You need to make sure you don't pass dynamically allocated memory (such as is contained in string objects) across DLL boundaries. In other words, don't have DLL-exported functions that return string objects.

Joe

This seems to match whats going on, it blows up at the precise point where I pass a string back across a .dll boundary. The problem only occurs when everything is linked in static mode. Now that's fixable.

See Passing reference to STL vector over dll boundary.

What I will try next

See Unable to pass std::wstring across DLL.

Solution

I have a nice solution, see the answer below.

Community
  • 1
  • 1
Contango
  • 76,540
  • 58
  • 260
  • 305
  • 3
    And what specifically is your question? – ChrisWue Sep 18 '13 at 21:37
  • 2
    It indicates you have heap corruption. What can cause heap corruption? At least a million different things. If you post some code maybe someone can help. – john Sep 18 '13 at 21:43
  • I wrote both the .dll and the calling code. This error should not be occurring. I am wondering if anyone else has encountered it before? – Contango Sep 18 '13 at 21:44
  • 3
    But it clearly is happening. Are you claiming you are incapable of writing code which corrupts the heap? – john Sep 18 '13 at 21:46
  • 1
    The .dll only contains about 10 lines of code, its just a test .dll to see how it works. – Contango Sep 18 '13 at 21:47
  • 2
    Even if you think it shouldnt happen, it clearly is. So there is a 99.9% chance there is something wrong in your code... – SinisterMJ Sep 18 '13 at 21:47
  • 2
    Then show the code, and the calling code, if possible everything – SinisterMJ Sep 18 '13 at 21:48
  • 1
    @Gravitas If you want to research things yourself, try looking at line 1424 of dbgheap.c. It might give you some clue. – john Sep 18 '13 at 21:50
  • 6
    just typed the error into a search engine. Some interesting hits, e.g. http://casablanca.codeplex.com/discussions/442262, "That stack trace is all too familiar to me -- the layout of standard strings vary between runtime binaries for different configurations. If I were a betting man, I'd bet that you have a mix of release and debug binaries in your execution folder." Also http://stackoverflow.com/questions/15524591/debug-assertion-failed-error-when-passing-stdstring-from-c-cli-dll-to-native. Essentially it looks like you might have a mismatch between the way your exe and dll are built. – TooTone Sep 18 '13 at 21:59
  • @TooTone I think you're probably correct. I'll check that the settings in my .exe project and .dll project are not different. – Contango Sep 18 '13 at 22:10
  • This also happened when moving to Visual Studio 2015 and since Qt doesn't provide the binaries Qt has to be built again with the correct options (Visual Studio's compiler)... – Contango Apr 05 '19 at 07:57
  • Feedback from @Vincent TR: "Same here... It happened when moving to Visual Studio 2015 and since Qt doesn't provide the binaries Qt has to be built again with the correct options (Visual Studio's compiler)..." – Contango Jun 14 '20 at 14:35

4 Answers4

40

In this case, the problem is that I was passing a std::string back across a .dll boundary.

Runtime Library config

  • If the MSVC Runtime library is set to Multi-threaded Debug DLL (/MDd), then this is no problem (it works fine).

  • If the MSVC Runtime library is set to Multi-threaded Debug (/MTd), then it will throw this error, which can be fixed with the following instructions.

Memory allocated in Memory Manager A and freed in Memory Manager B ...

The problem is that memory is allocated on the .dll side, then that same memory is freed on the application side. This means that memory manager A is allocating memory, and memory manager B is releasing that same memory, which generates errors.

The solution is to make sure that all memory passed back is not allocated in the DLL. In other words, the memory is always allocated on the application side, and freed on the application side.

Of course, the DLL can allocate/free memory internally - but it can't allocate memory that is later freed by the application.

Examples

This will not work:

// Memory is allocated on the .dll side, and freed on the app side, which throws error.
DLL std::string GetString(); 

This will work:

// Memory is allocated/freed on the application side, and never allocated in the .dll.
DLL int GetString(std::string& text); 

However, this is not quite enough.

On the application side, the string has to be pre-allocated:

std::string text("");
text.reserve(1024);     // Reserves 1024 bytes in the string "text".

On the .dll side, the text must be copied into the original buffer (rather than overwritten with memory that is allocated on the .dll side):

text.assign("hello");

Sometimes, C++ will insist on allocating memory anyway. Double check that the pre-allocation is still the same as it was:

if (text.capacity < 1024)
{
   cout << "Memory was allocated on the .dll side. This will eventually throw an error.";
}

Another way that works is to use std::shared_ptr<std::string>, so even though memory is allocated in the .dll, it is released by the .dll (rather than the application side).

Yet another way is to accept a char * and a length which indicates the amount of pre-allocated memory. If the text that we want to pass back is longer than the length of pre-allocated memory, return an error.

Community
  • 1
  • 1
Contango
  • 76,540
  • 58
  • 260
  • 305
  • 1
    THANK YOU for following up with the solution. Helped me a lot. –  May 29 '14 at 21:19
  • 1
    Thank you, had this bug (could ignore without issue) that I just couldn't figure out. I was linking to multiple hot swappable DLLs I was building with MTd instead of MDd – Mebourne Jun 14 '20 at 03:38
  • @Melbourne You're welcome! This particularly issue so thoroughly spooked me 7 years ago that I dropped C++ about two weeks in and went for C# instead. I should have persevered, I now have 3 years of C++ experience and wish I had more. I've run into issues since then, but none as horrific as this. – Contango Jun 14 '20 at 14:31
  • 1
    Switching to /MDd solved our debug stability issues. We had no release build stability issues but switching to /MD reduced boot time by 30 seconds which was completely unexpected. We have multiple processes and suspect that 30 seconds was the additional overhead to load statically linked runtime libraries for each process versus loading the runtime DLL just once. – pixelgrease Mar 25 '21 at 04:22
4

This is what assert() looks like when its expression argument evaluates to false. This assert exists in the Debug build of the C runtime library, designed to check for allocation problems. The free() function in your case. The Debug build add extra checks to make sure you are writing your code correctly. And tell you when it detects a problem. Like calling free() on an allocation that was already freed, the simple case. Or calling free() passing the wrong pointer value, the trickier case. Or calling free() when the heap was corrupted by earlier code, the much harder case.

This is only as far as they can take it, they don't actually know why your code got it wrong. There is not any way they can put a Big Red arrow on the code that corrupted the heap for example. The easy case is covered by the Debug + Windows + Call Stack debugger window, it takes you to the code in your program that called free(). Or std::operator delete for a C++ program. The harder case is very, very hard indeed, heap corruption is often a Heisenbug. Getting the assert to be repeatable so you can set a data breakpoint on the reported address is the core strategy. Crossing fingers for the easy case, good luck with it!


After edit: yes, having cross-module problems with a C++ class like std::string is certainly one of the problems it can catch. Not a Heisenbug, good kind of problem to have. Two basic issues with that:

  • The modules might each have their own copy of the CRT, objects allocated by one copy of the CRT cannot be released by another copy of the CRT. They each have their own heap they allocate from. A problem that got addressed in VS2012, the CRT now allocates from a process-global heap.
  • The modules might not use the same implementation of std::string. With an object layout that does not match. Easily induced by having the modules compiled with different C++ library versions, particularly an issue with C++11 changes. Or different build settings, the _HAS_ITERATOR_DEBUGGING macro is quite notorious.

The only cure for that problem is to make sure that you build all of the modules in your program with the exact same compiler version using the exact same build settings. Using /MD is mandatory, it ensures that the CRT is shared so there's only one in the program.

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

The likely cause: binding to wrong version of the Qt DLLs, especially when moving a project from VS2010 to VS2012.

This is due to different versions of the standard library and associated dynamic allocation issues.

Pavel Radzivilovsky
  • 18,794
  • 5
  • 57
  • 67
  • What do you mean by this exactly? Multi-threaded/single-threaded, and debug/release? Or the Qt version number? – johsin18 Jun 30 '15 at 06:08
1

I had the same problem after a Windows reinstallation. My Runtime library build was Multi-threaded Debug DLL (/MDd).

My solution was to remove *.user file of the visual studio project, remove the debug folder and rebuild the project.

Melebius
  • 6,183
  • 4
  • 39
  • 52