Por que minha DLL travou?

Caloni, 2007-10-18 computer blog

O resumo da ópera é que o código do Windows chamador do DllMain das DLLs carregadas/descarregadas utiliza um objeto de acesso exclusivo (leia "mutex") para sincronizar as chamadas. O resultado é que, em um processo, apenas um DllMain é chamado em um dado momento. Esse objeto é chamado de loader lock na documentação da Microsoft.

Escrevi um código besta para exemplificar, mas representa o que já vi em muito código-fonte, e muitas vezes não consegui perceber o que estava acontecendo (tanto porque desconhecia a existência desse loader lock quanto o código estava obscuro demais pra entender mesmo).

Update 2026-02-04. Perdi o código que cito acima, mas perguntei para o Chat-GPT para me dar um exemplo didático:

#include <windows.h>
#include <stdio.h>
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
    // Simulate some work
    Sleep(1000);
    // Anything that might touch loader state is deadly here
    // Even something innocent-looking like LoadLibrary
    LoadLibraryA("user32.dll");
    return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL,
                    DWORD fdwReason,
                    LPVOID lpvReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
    {
        HANDLE hThread;
        DWORD threadId;
        hThread = CreateThread(
            NULL,
            0,
            WorkerThread,
            NULL,
            0,
            &threadId
        );
        if (hThread)
        {
            // 🚨 DEADLOCK HERE 🚨
            // Loader lock is held, thread cannot safely execute
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
        }
        break;
    }
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Uma simples vítima disso pode ser um pobre executável usando uma pobremente escrita DLL, assim como no código abaixo:

   int main()
   {
     printf("load dll");
     HMODULE lockDll = LoadLibrary(_T("dll_lock.dll"));
   
     if( lockDll )
     {
       Sleep(2000);
       printf("free dll");
       FreeLibrary(lockDll), lockDll  = NULL;
       printf("done");
     }
   }

É importante sempre lembrar que a Microsoft acha feio, muito feio você ficar dependendo do DllMain pra fazer alguma coisa, mas admite que em alguns casos o único lugar onde podemos rodar código é no DllMain. Nesses casos -- e em alguns outros -- utilize uma comunicação paralela com sua thread travadona, por meio de um evento ou algo do gênero, antes que ela realmente saia. Com isso a thread pode ainda não ter saído, mas pode avisar a thread principal que o que ela precisava fazer já foi feito.

Entre os clássicos e inestimáveis artigos de Matt Pietrek no Microsoft Journal há na edição de setembro de 1999 um bem curto a respeito da inicialização de DLLs. Essa é a leitura mais sucinta, didática e esclarecedora sobre a questão.

[guia_basico_para_programadores_de_primeiro_breakpoint] [guia_basico_para_programadores_de_primeiro_int_main]