Another option is to specify your own dll entry point function so you can initialize the dlls Win32 private heap before the CRT initializes. In that case your dll globals will be allocated from the private heap.
Dll entry point function
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <intrin.h>
#include <new>
#include <stdexcept>
typedef int(__cdecl* NTVSPRINTFS)(char* szDest, size_t sizeOfBuffer, const char* szFormat, va_list argptr);
extern "C" BOOL __stdcall _DllMainCRTStartup(HMODULE, DWORD, LPVOID);
BOOL(__stdcall* dllstartup)(HMODULE, DWORD, LPVOID) = _DllMainCRTStartup;
HANDLE g_DllHeap;
NTVSPRINTFS ntvsprintf_s;
void Report(LPCSTR pszFormat, ...);
EXTERN_C BOOL __stdcall RawDllEntry(HMODULE p1, DWORD dw, PVOID p2)
{
switch (dw)
{
case DLL_PROCESS_ATTACH:
if (!g_DllHeap)
{
ntvsprintf_s = (NTVSPRINTFS)GetProcAddress(GetModuleHandleA("ntdll.dll"), "vsprintf_s");
g_DllHeap = HeapCreate(0, 65536, 0);
if (!g_DllHeap)
{
Report("HeapCreate failed with error %d\n", GetLastError());
return FALSE;
}
Report("Basic2Lib.dll private heap created at 0x%p\n", g_DllHeap);
}
return dllstartup(p1, dw, p2);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
return dllstartup(p1, dw, p2);
case DLL_PROCESS_DETACH:
Report("In DLL_PROCESS_DETACH due to %s\n", p2 != nullptr ? "terminating" : "unloading");
BOOL result = dllstartup(p1, dw, p2);
HeapDestroy(g_DllHeap);
return result;
}
return TRUE;
}
// Override global new
void* operator new(size_t size) {
if (!g_DllHeap)
__fastfail(FAST_FAIL_FATAL_APP_EXIT);
void* ptr = HeapAlloc(g_DllHeap, 0, size);
if (ptr)
{
Report("operator new allocated %Iu bytes at 0x%p\n", size, ptr);
return ptr;
}
else
{
Report("HeapAlloc failed with error %d\n", GetLastError());
throw std::bad_alloc();
}
}
void* operator new[](size_t size) {
if (!g_DllHeap)
__fastfail(FAST_FAIL_FATAL_APP_EXIT);
void* ptr = HeapAlloc(g_DllHeap, 0, size);
if (ptr)
{
Report("operator new allocated %Iu bytes at 0x%p\n", size, ptr);
return ptr;
}
else
{
Report("HeapAlloc failed with error %d\n", GetLastError());
throw std::bad_alloc();
}
}
// Override global delete
void operator delete(void* ptr) noexcept {
if (!g_DllHeap)
__fastfail(FAST_FAIL_FATAL_APP_EXIT);
if (HeapFree(g_DllHeap, 0, ptr)) // HeapFree documented to accept null pointer.
{
Report("operator delete deallocation of 0x%p\n", ptr);
}
else
{
Report("operator delete (HeapFree) failed with error %d\n", GetLastError());
}
}
void operator delete[](void* ptr) noexcept {
if (!g_DllHeap)
__fastfail(FAST_FAIL_FATAL_APP_EXIT);
if (HeapFree(g_DllHeap, 0, ptr)) // HeapFree documented to accept null pointer.
{
Report("operator delete deallocation of 0x%p\n", ptr);
}
else
{
Report("operator delete (HeapFree) failed with error %d\n", GetLastError());
}
}
void Report(LPCSTR pszFormat, ...)
{
char szMsg[512]{};
va_list pArg;
va_start(pArg, pszFormat);
ntvsprintf_s(szMsg, ARRAYSIZE(szMsg), pszFormat, pArg);
va_end(pArg);
OutputDebugStringA(szMsg);
}
The version of vsprintf_s from ntdll.dll is used to avoid reliance on the CRT.
Dll code to be called by applications
#include <Windows.h>
#include <cstdio>
#include <string>
#include <memory>
typedef struct {
std::string str;
int x;
} PODSTRUCT, *PPODSTRUCT;
std::string strglobal("This is a global std::string");
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
EXTERN_C __declspec(dllexport) void Test1(size_t size)
{
printf_s("%s allocating and deleting %Id bytes\n", __FUNCTION__, size);
char* p = new char[size];
delete[] p;
}
EXTERN_C __declspec(dllexport) void Test2()
{
printf_s("%s allocating and deleting a structure\n", __FUNCTION__);
PPODSTRUCT p = new PODSTRUCT{"Test struct", 42};
delete p;
}
EXTERN_C __declspec(dllexport) void Test3(const char* text)
{
printf_s("%s allocating and deleting std::string\n", __FUNCTION__);
std::string* s = new std::string(text);
delete s;
}
EXTERN_C __declspec(dllexport) void Test4(const char* replacement)
{
printf_s("Current dll global is %s\n", strglobal.c_str());
strglobal = replacement;
printf_s("Replacement value of global is %s\n", strglobal.c_str());
}
EXTERN_C __declspec(dllexport) void Test5(size_t arraysize)
{
printf_s("create std::unique_ptr array - BYTE[%Id]\n", arraysize);
std::unique_ptr<BYTE[]> p = std::make_unique<BYTE[]>(arraysize);
}
A console application to exercise the dll
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <cstdio>
#include <tchar.h>
#include <string>
#include <thread>
std::wstring str(L"This is a test");
EXTERN_C __declspec(dllimport) void Test1(size_t size);
EXTERN_C __declspec(dllimport) void Test2();
EXTERN_C __declspec(dllimport) void Test3(const char* text);
EXTERN_C __declspec(dllimport) void Test4(const char* text);
EXTERN_C __declspec(dllimport) void Test5(size_t arraysize);
typedef decltype(Test1) *T1;
typedef decltype(Test2)* T2;
typedef decltype(Test3)* T3;
typedef decltype(Test4)* T4;
typedef decltype(Test5)* T5;
int _tmain(int argc, TCHAR *argv[])
{
HMODULE hmod = LoadLibrary(_T("Basic2Lib.dll"));
T1 test1 = (T1)GetProcAddress(hmod, "Test1");
T2 test2 = (T2)GetProcAddress(hmod, "Test2");
T3 test3 = (T3)GetProcAddress(hmod, "Test3");
T4 test4 = (T4)GetProcAddress(hmod, "Test4");
T5 test5 = (T5)GetProcAddress(hmod, "Test5");
HANDLE aHeaps[8]{};
auto nHeaps = GetProcessHeaps(ARRAYSIZE(aHeaps), aHeaps);
printf_s("Process heap is at 0x%p\n", GetProcessHeap());
printf_s("Process has %i Win32 heaps\n", nHeaps);
for (DWORD i = 0; i < nHeaps; i++)
{
printf_s("Heap %i is at 0x%p\n", i, aHeaps[i]);
}
test1(4096);
test2();
test3("this is test 3");
test4("Replacement text for dll global variable");
test5(16384);
std::thread t = std::thread([&](){
int x = printf_s("Lambda executing in std::thread\n");
test3("lambda test");
});
t.join();
FreeLibrary(hmod);
return 0;
}