DllMain dan loader lock — alasan sebenarnya di balik peringatan "jangan lakukan apa pun saat inisialisasi DLL"

· Diperbarui pada: · · Windows, DLL, Pengembangan Windows, C++, Pemecahan masalah, Multithreading, Win32 API

Riwayat revisi (1 pembaruan, terakhir pada 31 Aug 2026)

Catatan perubahan yang dilakukan pada artikel ini. Jika versi sebelumnya telah diarsipkan, versi itu tetap dapat dibaca melalui tautan permanen dengan DOI.

Diterjemahkan ulang sebagai terjemahan lengkap dari naskah Jepang. Versi bahasa Indonesia sebelumnya adalah ringkasan yang hanya memindahkan sebagian naskah, sehingga bagian, tabel, gambar Mermaid, keterangan gambar, dan FAQ tidak ada. Semuanya dipulihkan sesuai naskah Jepang, dan klaim teknisnya sama dengan versi Jepang.
Publikasi pertama
Mengutip artikel ini(DOI (arsip terdaftar): 10.5281/zenodo.22176709)

DOI di bawah mengarah ke versi yang telah diarsipkan sebelumnya dan mungkin berbeda dari teks saat ini. Gunakan URL halaman ini untuk merujuk teks saat ini.

Go Komura (2026). DllMain dan loader lock — alasan sebenarnya di balik peringatan "jangan lakukan apa pun saat inisialisasi DLL". KomuraSoft LLC. https://comcomponent.com/id/blog/dllmain-loader-lock/

DOI (arsip terdaftar)
10.5281/zenodo.22176709
DOI (versi terakhir yang didaftarkan)
10.5281/zenodo.22176710

“Hanya di lingkungan tertentu, aplikasi hang saat start.” “Saat memuat DLL buatan sendiri, LoadLibrary kadang tidak pernah kembali.” “Deadlock hanya terjadi pada waktu start layanan.” — Investigasi semacam ini, jika ditelusuri cukup jauh, sering berujung di tempat yang sama. Kode inisialisasi DLL, yaitu DllMain.

Dokumentasi Microsoft memperingatkan tentang DllMain dengan nada yang luar biasa tegas. Jangan panggil LoadLibrary. Jangan sinkronkan dengan thread lain. Jangan panggil fungsi User, Shell, atau COM. DllMain yang ideal adalah stub kosong — mengapa peringatannya sekuat itu? Alasannya terkumpul pada satu mekanisme internal: loader lock. Artikel ini ditujukan kepada pengembang yang menulis DLL, plug-in, atau wrapper C++/CLI di Windows. Berdasarkan sumber primer, ia menjelaskan cara kerja loader lock, struktur yang membuat deadlock terbentuk, serta desain yang aman dan prosedur investigasi.

1. Kesimpulan dulu

  • DllMain dipanggil sambil memegang loader lock, kunci bersama yang hanya ada satu per proses. Karena itu, memanggil dari DllMain pekerjaan yang mencoba mengambil loader lock (langsung maupun tidak langsung) membuka kemungkinan deadlock, atau crash karena menyentuh DLL yang belum diinisialisasi.1
  • Pemanggilan LoadLibrary / FreeLibrary dilarang. Ketergantungan urutan muat menjadi sirkular, dan fungsi DLL dapat dipakai sebelum kode inisialisasinya sendiri dijalankan.2
  • Sinkronisasi dengan thread lain juga dilarang. Notifikasi DLL diserialisasi, jadi menunggu thread mulai atau berakhir di dalam DllMain membuat thread itu sendiri berhenti menunggu loader lock, lalu deadlock.23
  • Yang aman dipanggil, pada praktiknya, hanyalah sebagian fungsi Kernel32.dll. Dan dokumentasi resmi menyatakan dengan gamblang bahwa “daftar lengkap fungsi yang aman tidak ada”. Fungsi User, Shell, dan COM memuat komponen lain sehingga dapat memicu access violation.2
  • Pada DLL yang ditautkan dengan CRT, konstruktor dan destruktor variabel global terkena batasan yang sama. Mereka dijalankan secara de facto sebagai bagian dari DllMain.2
  • Desain yang benar adalah “tunda”. Inisialisasi yang bisa diselesaikan pada waktu kompilasi (statis) kerjakan di situ; yang tidak, tunda sampai pemakaian pertama. Itu praktik terbaik resmi.1
  • DLL campuran C++/CLI sangat berbahaya. Agar MSIL tidak dijalankan di bawah loader lock, DllMain dan pohon pemanggilannya wajib dikompilasi native.4

2. Kapan dan bagaimana DllMain dipanggil

DllMain adalah entry point yang dipanggil loader OS ketika DLL masuk atau keluar dari proses atau thread. Ada empat jenis notifikasi.

Notifikasi Waktu
DLL_PROCESS_ATTACH Ketika DLL dimuat ke proses
DLL_THREAD_ATTACH Ketika thread baru dimulai di dalam proses
DLL_THREAD_DETACH Ketika thread berakhir secara normal
DLL_PROCESS_DETACH Ketika DLL di-unload, atau ketika proses berakhir

Dua fakta mudah terlewat. Pertama, setiap kali satu thread dibuat, DllMain semua DLL yang sudah termuat dipanggil dengan DLL_THREAD_ATTACH. Jadi DllMain bukan “sesuatu yang berjalan sekali saat DLL sendiri dimuat”; itu kode yang terus dipanggil setiap kali ada aktivitas thread di proses. Jika tidak dibutuhkan, hentikan dengan memanggil DisableThreadLibraryCalls di dalam DLL_PROCESS_ATTACH (jangan panggil dari DLL yang ditautkan dengan CRT statis).5

Kedua, pada DLL yang ditautkan dengan CRT (runtime C/C++), konstruktor dan destruktor objek C++ global serta statis dijalankan, lewat entry point CRT, sebagai bagian dari DllMain.2 Meski terasa “DllMain kita kosong, jadi aman”, objek global dengan inisialisasi yang rumit sama saja dengan mengerjakan pekerjaan itu di DllMain.

Empat waktu DllMain dipanggilSaat DLL dimuat DLL_PROCESS_ATTACH berjalan; setiap mulai dan berakhirnya thread di proses, DLL_THREAD_ATTACH dan DETACH dipanggil ke semua DLL yang sudah termuat; saat unload atau proses berakhir DLL_PROCESS_DETACH berjalan; dan lewat CRT, konstruktor objek statis juga dijalankan di dalamnyaMuat DLLDLL_PROCESS_ATTACHDLL_THREAD_ATTACH (setiap kali thread mulai)DLL_THREAD_DETACH (setiap kali thread berakhir)DLL_PROCESS_DETACH (saat unload atau berakhir)Pembangunan objek statis juga dijalankan di sini

Gambar 1: DllMain dipanggil bukan hanya saat muat, melainkan setiap kali thread mulai atau berakhir, dan inisialisasi objek statis juga berjalan sebagai bagian dari itu.

3. Loader lock — satu kunci yang menserialisasi semua notifikasi

Mengapa hanya DllMain yang dikenai batasan seketat ini? Jawabannya ada pada struktur loader.

Loader OS menserialisasi rangkaian pekerjaan muat, unload, dan berbagai notifikasi DLL dengan loader lock yang hanya ada satu per proses, agar konsistensinya terjaga. Yang penting: DllMain dipanggil sambil memegang loader lock itu.1 Selama berada di dalam DllMain, di proses itu pemuatan DLL lain maupun notifikasi mulai thread baru semuanya menunggu kunci ini dilepas.

Dari struktur itu, alasan larangan-larangan berikut mengikuti secara berantai.

  • LoadLibrary tidak boleh dipanggil karena dapat memicu rekursi loader lock atau ketergantungan sirkular pada urutan muat. Fungsi DLL yang inisialisasinya belum selesai pun dapat terpanggil.2
  • Sinkronisasi dengan thread lain berbahaya karena thread yang ditunggu mungkin sedang membutuhkan loader lock (notifikasi saat mulai atau berakhir, pemanggilan API keluarga GetModuleHandle, dan sebagainya). Sendiri memegang loader lock sambil menunggu lawan, lawan menunggu loader lock — inversi urutan kunci yang klasik.6
  • Fungsi User, Shell, dan COM berbahaya karena mereka memuat komponen sistem lain di dalamnya. Menyentuh komponen sebelum inisialisasi atau setelah pelepasan memicu access violation.2
Struktur deadlock jika DllMain menunggu threadDllMain yang memegang loader lock menunggu worker thread berakhir, tetapi worker yang hendak berakhir menunggu loader lock dilepas untuk notifikasi DLL_THREAD_DETACH, sehingga keduanya saling menunggu dan deadlockWorker threadDllMainLoader (memegang kunci)Worker threadDllMainLoader (memegang kunci)Notifikasi keluar membutuhkan loader lockDllMain menunggu sambil memegang kunci, W menunggu kuncimemberitahu DLL_PROCESS_DETACHmeminta berakhir dan menunggu selesaimenyelesaikan pekerjaan lalu menuju keluar thread

Gambar 2: “DllMain menunggu thread berakhir” secara struktural deadlock, karena keluarnya thread itu sendiri membutuhkan loader lock.

Intinya, ini bukan jenis masalah yang “terjadi jika sedang sial”, melainkan terbentuk secara struktural. Dokumentasi meminta loader lock diperlakukan sebagai puncak (yang diambil paling awal) dalam hierarki kunci yang didefinisikan aplikasi. Di dalam DllMain, kunci puncak itu sudah dipegang, jadi tindakan pergi menunggu sesuatu dari situ secara umum berbahaya — kerangka itu memudahkan mengingatnya.6

Inversi urutan loader lock dan kunci privatDllMain pergi mengambil kunci privat sambil memegang loader lock, sementara worker pergi mengambil loader lock sambil memegang kunci privat, misalnya untuk GetModuleHandle, sehingga urutan perolehan terbalik dan deadlockDllMain: sedang memegang loader lockpergi mengambil kunci privat GWorker: sedang memegang kunci privat Gpergi mengambil loader lockDeadlock karena inversi urutan perolehanGetModuleHandle dan sejenisnya memintanya di dalam

Gambar 3: API yang tampak biasa seperti GetModuleHandle pun meminta loader lock di dalamnya, sehingga inversi urutan dengan kunci privat dapat terbentuk.

Selain itu, memanggil CreateThread di dalam DllMain sendiri pun tidak dianjurkan. Thread yang dibuat membutuhkan loader lock untuk memproses notifikasi DLL_THREAD_ATTACH, sehingga tidak dapat mulai berjalan sampai DllMain yang sedang berjalan kembali dan melepaskan kunci. Karena itu, menunggu thread itu mulai atau selesai di dalam DllMain langsung deadlock di tempat. Ada juga masalah masa hidup — jika setelah DllMain kembali DLL di-unload sementara thread yang belum sempat berjalan masih tertinggal, alamat mulai thread menunjuk ke kode yang sudah dibebaskan, lalu crash.3

4. Dua ranjau yang mudah diinjak pengembang C++

Ranjau 1: inisialisasi dinamis objek global. Seperti dibahas di bab 2, konstruktor objek statis berjalan di bawah batasan DllMain. Membaca berkas konfigurasi, menyalakan mekanisme log, menginisialisasi COM, memulai thread — begitu variabel global dengan pekerjaan semacam itu diletakkan di DLL, itu menjadi eksekusi “hal yang tidak boleh dikerjakan di DllMain”. Inisialisasi konstanta yang diputuskan pada waktu kompilasi (yang bisa dijadikan constexpr) aman, tetapi inisialisasi yang melibatkan pemanggilan fungsi harus ditunda.

Jalur yang membuat inisialisasi objek global menjadi ranjauSaat DLL dimuat loader lock diambil, lalu lewat CRT konstruktor objek global dijalankan, sehingga LoadLibrary, sinkronisasi thread, atau inisialisasi COM di dalamnya menjadi eksekusi larangan DllMainMuat DLL (mengambil loader lock)Entry point CRTKonstruktor objek globalPemrosesan setara LoadLibraryMemulai thread dan menunggu selesaiPemakaian COM atau User32Semua termasuk larangan DllMain

Gambar 4: Meski “DllMain kosong jadi aman”, variabel global dengan inisialisasi rumit menghidupkan kembali bahaya yang sama.

Ranjau 2: C++/CLI (assembly campuran). Pada susunan yang membungkus DLL native dengan C++/CLI (bentuk yang dibahas di artikel wrapper), ada risiko menjalankan MSIL (kode managed) di bawah loader lock. Eksekusi MSIL dapat memicu inisialisasi CLR atau pemuatan assembly lain. Kompiler mengeluarkan peringatan C4747 untuk kode yang membuat DllMain mengeksekusi MSIL secara langsung, tetapi eksekusi tidak langsung lewat fungsi di modul lain tidak dapat dideteksi. Kompilasi DllMain dan fungsi yang dipanggil darinya sebagai native dengan #pragma unmanaged, atau jangan miliki DllMain sama sekali.4

Apakah eksekusi MSIL di bawah loader lock dapat dideteksiKode yang membuat DllMain mengeksekusi MSIL secara langsung dapat dideteksi kompiler dengan peringatan C4747, tetapi eksekusi tidak langsung lewat fungsi di modul lain tidak dapat dideteksi, sehingga perlu dicegah dengan review pohon pemanggilan dan kompilasi native yang ketatPemanggilan dari DllMainMengeksekusi MSIL secara langsungMengeksekusi lewat modul lainDapat dideteksi dengan peringatan C4747Kompiler tidak dapat mendeteksiCegah dengan review dan #pragma unmanaged

Gambar 5: C4747 hanya melindungi eksekusi langsung. Jalur tidak langsung hanya tertangkap lewat review.

5. Desain yang benar — jadikan “tunda” sebagai kebijakan dasar

Rekomendasi praktik terbaik resmi jelas.1

  1. Selesaikan inisialisasi yang bisa pada waktu kompilasi (statis). Pertama-tama pertimbangkan apakah inisialisasi dinamis dapat diganti dengan inisialisasi statis.
  2. Sisanya tunda sampai pemakaian pertama. Selama pemakaian pertama terjadi dari API biasa yang dipanggil setelah pemuatan DLL selesai, inisialisasi berjalan di luar loader lock, dan hampir seluruh Windows API dapat dipakai dengan aman. Untuk eksklusivitas akses pertama, INIT_ONCE (one-time initialization) atau magic static C++ (static di dalam fungsi) dapat dipakai. Namun penundaan bukan obat mujarab — jika akses pertama itu dilakukan dari dalam DllMain atau static initializer, initializer pada akhirnya tetap berjalan di bawah loader lock, dan batasan yang sama kembali.
  3. Kecualikan hanya kegagalan yang harus dideteksi lebih awal. Ada kalanya persyaratannya adalah “jika berkas konfigurasi rusak, gagalkan pemuatan itu sendiri”. Dalam kasus itu pun, batasi pada minimum “coba lalu segera gagal”.
  4. Pertimbangkan DisableThreadLibraryCalls pada DLL_PROCESS_ATTACH. Jika DLL tidak memakai notifikasi thread, biaya notifikasi itu sendiri dapat dihilangkan (kecuali saat memakai CRT statis atau TLS statis).5
  5. Periksa dengan Application Verifier. Banyak pemanggilan berbahaya di dalam DllMain dapat dideteksi Application Verifier pada waktu jalan.1
Pedoman desain inisialisasi DLLInisialisasi pertama-tama ditimbang apakah dapat dijadikan inisialisasi statis pada waktu kompilasi; jika tidak, dasar utamanya adalah menunda sampai pemakaian pertama; hanya yang harus dideteksi lebih awal sebagai kegagalan muat yang ditinggalkan seminimal mungkin di DllMainyatidaktidakyaDapat diputuskan pada waktu kompilasi?Jadikan inisialisasi statisKegagalan harus dideteksi saat muat?Tunda sampai pemakaian pertama (dasar)Kerjakan hanya minimum di DllMainEksklusivitas dengan INIT_ONCE atau static di dalam fungsi

Gambar 6: Urutan keputusan adalah “dapatkah dibuat statis → dapatkah ditunda”; yang ditinggalkan di DllMain hanyalah minimum yang perlu dideteksi lebih awal.

Keputusan memakai DisableThreadLibraryCalls dapat ditentukan secara mekanis dengan cabang berikut.

Keputusan apakah DisableThreadLibraryCalls dipanggilDLL yang ditautkan dengan CRT statis tidak boleh memanggilnya; jika TLS statis aktif panggilan itu sendiri gagal sehingga jangan dipanggil; jika keduanya tidak dan DLL tidak memakai notifikasi thread, panggil pada DLL_PROCESS_ATTACH sambil memeriksa nilai kembali untuk mengurangi biaya notifikasiyatidakyatidaktidakyaDitautkan dengan CRT statis?Tidak boleh dipanggilMemakai TLS statis?Dipanggil pun gagal (FALSE)Notifikasi thread diperlukan?Panggil pada ATTACH (periksa nilai kembali)Jangan panggil; proses notifikasinya

Gambar 7: Tiga syarat — CRT statis, TLS statis, dan perlu-tidaknya notifikasi — menentukan secara unik apakah harus dipanggil.

Untuk penghentian thread saat unload, dokumentasi resmi menunjukkan protokol konkret. Bukan “menunggu” worker thread berakhir pada DLL_PROCESS_DETACH (saat unload lewat FreeLibrary), melainkan (1) memberi isyarat berakhir lewat event, (2) sisi thread merapikan pekerjaan sampai keadaan konsisten, mengembalikan isyarat, lalu masuk tunggu tak terbatas, (3) sisi DllMain mengonfirmasi keadaan konsisten lalu merapikan dengan TerminateThread.3 Terlihat kasar, tetapi didokumentasikan sebagai solusi realistis di dalam batasan “di dalam DllMain tidak boleh menunggu thread berakhir secara alami”.

Protokol penghentian thread saat unloadDllMain memberi isyarat berakhir ke worker thread lewat event, worker merapikan pekerjaan sampai keadaan konsisten lalu mengembalikan isyarat dan masuk tunggu tak terbatas, DllMain mengonfirmasi keadaan konsisten lalu menghentikan threadWorker threadDllMain (pemrosesan DETACH)Worker threadDllMain (pemrosesan DETACH)Tidak menunggu berakhir alami, jadi tidak deadlockmemberi isyarat berakhir lewat eventmerapikan pekerjaan sampai keadaan konsistenmengisyaratkan konsistensi selesai lalu tunggu tak terbatasmenghentikan dengan TerminateThread

Gambar 8: Alih-alih “menunggu berakhir alami”, “menunggu isyarat keadaan konsisten lalu memotong” menghindari benturan dengan loader lock.

Pada dasarnya, desain yang memiliki thread di DLL yang dapat di-unload sebaiknya dihindari; kepemilikan thread paling aman jika diletakkan di sisi EXE.

Sebaliknya, DLL_PROCESS_DETACH saat proses berakhir idealnya kembali tanpa melakukan apa pun. Pada titik ini semua thread lain sudah dihentikan secara paksa, dan keadaan DLL atau runtime yang diandalkan pun tidak dapat dipercaya. Pekerjaan rumit di sini hanya menjadi sumber deadlock atau crash. Data yang perlu dipersistensi harus ditulis di jalur shutdown aplikasi sendiri; jangan andalkan notifikasi ini.3

6. Cara menyelidiki jika sudah terjadi

Hang yang terkait loader lock punya sidik jari yang mudah dikenali.

Lihat stack pada dump saat hang. Ambil dump pada saat macet, lalu periksa stack setiap thread. Jika ditemukan pasangan thread yang menunggu kunci di dalam fungsi loader ntdll.dll (kelompok fungsi yang namanya dimulai Ldr) dan thread yang menunggu sesuatu yang lain di dalam DllMain atau static initializer (dynamic initializer), hampir dapat dipastikan. Thread yang berhenti di tengah pemanggilan LoadLibrary juga tokoh khas yang muncul.

Sidik jari hang yang terkait loader lockJika pada dump saat hang ditemukan pasangan thread yang menunggu kunci di dalam fungsi loader ntdll dan thread yang menunggu sesuatu yang lain di dalam DllMain atau static initializer, deadlock loader lock hampir dapat dipastikanyatidakDump saat hangThread yang menunggu kunci di fungsi LdrThread yang menunggu di dalam DllMain atau static initializerKeduanya ada?Hampir dipastikan deadlock loader lockSelidiki sebagai hang jenis lain

Gambar 9: Hang terkait loader lock punya sidik jari yang mudah dikenali: “menunggu Ldr + menunggu di dalam DllMain”.

Curigai sifat yang bergantung pada timing. Deadlock loader lock hanya terbentuk pada momen ketika pemuatan DLL bertumpuk dengan mulai atau berakhirnya thread. Kondisi reproduksi seperti “kadang saat start”, “hanya di mesin tertentu”, “hanya ketika dijalankan sebagai layanan” adalah tanda masalah jenis ini.

Jalankan pemeriksaan preventif. Mengalirkan tes dengan Application Verifier diaktifkan memungkinkan deteksi pemanggilan berbahaya di dalam DllMain pada waktu jalan.1 Untuk C++/CLI, jangan abaikan peringatan C4747; pada review fungsi yang dapat dijangkau dari DllMain, tambahkan sudut pandang “fungsi yang secara tidak langsung memanggil LoadLibrary” (inisialisasi COM, sebagian fitur CRT, delay-load import, dan sebagainya) ke daftar periksa review, agar tertangkap sebelum kecelakaan tertanam. Bahwa pemanggilan pertama fungsi import yang di-delay-load menjadi LoadLibrary di dalamnya adalah poin yang mudah terlewat.

7. Ringkasan

  • DllMain dipanggil sambil memegang loader lock (satu per proses, kunci yang menserialisasi semua notifikasi DLL). Semua batasan diturunkan dari situ.
  • Inti larangan adalah “jangan panggil LoadLibrary / FreeLibrary”, “jangan sinkronkan dengan thread lain”, “jangan panggil fungsi yang bergantung pada DLL selain Kernel32”. Konstruktor dan destruktor objek statis yang berjalan lewat CRT terkena batasan yang sama.
  • Kebijakan desain dasar adalah menunda. Inisialisasi yang bisa dibuat statis kerjakan secara statis; sisanya pada pemakaian pertama. Manfaatkan DisableThreadLibraryCalls dan Application Verifier.
  • Penghentian thread saat unload mengikuti protokol resmi (isyarat → konfirmasi konsistensi → hentikan). DLL_PROCESS_DETACH saat proses berakhir idealnya kosong.
  • Pada C++/CLI, eksekusi MSIL di bawah loader lock adalah ranjau tersendiri. Terapkan kompilasi native secara ketat pada pohon pemanggilan DllMain.

Batasan DllMain pada pandangan pertama tampak seperti deretan larangan yang tidak masuk akal. Namun jika satu poin “dipanggil sambil memegang loader lock, kunci puncak” dipegang, semua larangan adalah rumusan ulang prinsip yang sama. Diingat sebagai prinsip, maka ketika menemui kasus tepi yang tidak tertulis di dokumentasi pun, pertanyaan yang tepat tetap dapat diajukan: “Apakah ini pekerjaan yang boleh dikerjakan sambil memegang kunci?”

Artikel terkait

Area konsultasi terkait

KomuraSoft LLC menangani investigasi penyebab hang dan deadlock saat start atau saat memuat DLL (analisis dump), review desain seputar DllMain dan inisialisasi statis, serta perbaikan wrapper C++/CLI dan DLL plug-in menuju desain inisialisasi yang aman. Konsultasi dapat dimulai bahkan dari tahap yang sulit direproduksi seperti “hanya hang saat start di lingkungan tertentu”.

Tautan referensi

  1. Microsoft Learn, Dynamic-Link Library Best Practices. Tentang DllMain dipanggil sambil memegang loader lock sehingga fungsi yang dapat dipanggil sangat dibatasi; DllMain yang ideal adalah stub kosong dan inisialisasi sebaiknya ditunda sejauh mungkin; rekomendasi inisialisasi statis pada waktu kompilasi; hanya mengerjakan secara minimum kegagalan yang harus dideteksi lebih awal; dan deteksi kesalahan khas di dalam DllMain oleh Application Verifier. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  2. Microsoft Learn, DllMain entry point. Tentang seharusnya hanya melakukan inisialisasi dan pengakhiran sederhana di entry point; alasan LoadLibrary / FreeLibrary tidak boleh dipanggil (urutan muat sirkular dan pemakaian DLL sebelum inisialisasi atau setelah pengakhiran); Kernel32.dll dijamin sudah termuat sehingga dapat dipanggil sepanjang tidak memuat DLL lain; tidak adanya daftar lengkap fungsi yang aman; fungsi User, Shell, dan COM memicu access violation; notifikasi DLL diserialisasi sehingga komunikasi dengan thread atau proses lain memicu deadlock; dan batasan yang sama berlaku pada konstruktor serta destruktor objek statis jika ditautkan dengan CRT. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  3. Microsoft Learn, Dynamic-Link Library Best Practices - Best Practices for Synchronization. Tentang struktur yang deadlock jika menunggu thread berakhir di dalam DllMain (notifikasi DLL_THREAD_DETACH saat thread berakhir membutuhkan loader lock); protokol penghentian thread saat unload (beri isyarat lewat event, konfirmasi keadaan konsisten, lalu hentikan); pada DLL_PROCESS_DETACH saat proses berakhir thread lain sudah dihentikan secara paksa dan konsistensi ruang alamat tidak dijamin sehingga handler yang ideal kosong; dan membuat thread di DllMain membuat notifikasi tertahan dengan inisialisasi belum selesai lalu menimbulkan masalah. ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, Initialization of Mixed Assemblies. Tentang tidak boleh mengeksekusi MSIL di bawah loader lock; DllMain dan pohon pemanggilannya tidak boleh dikompilasi menjadi MSIL dan ditangani dengan #pragma unmanaged; peringatan C4747 dikeluarkan jika DllMain mencoba mengeksekusi MSIL secara langsung tetapi eksekusi tidak langsung lewat modul lain tidak dapat dideteksi; dan dynamic initializer objek statis dapat menimbulkan masalah yang sama. ↩ ↩2

  5. Microsoft Learn, DisableThreadLibraryCalls function (libloaderapi.h). Tentang menonaktifkan notifikasi DLL_THREAD_ATTACH / DLL_THREAD_DETACH untuk mengurangi overhead saat thread dibuat dan dihancurkan; tidak boleh dipanggil dari DLL yang ditautkan dengan CRT statis; dan bahwa pengoptimalan tidak dilakukan jika TLS statis (thread_local atau __declspec(thread)) aktif. ↩ ↩2

  6. Microsoft Learn, Dynamic-Link Library Best Practices - Deadlocks Caused by Lock Order Inversion. Tentang mendefinisikan hierarki kunci dan selalu mengambil dalam urutan yang sama; loader mengambil loader lock lalu memanggil DllMain sehingga loader lock harus diletakkan di puncak hierarki kunci; menjaga urutan perolehan antara API yang mengambil loader lock secara tidak langsung seperti GetModuleFileName dan kunci privat; serta contoh konkret deadlock akibat inversi urutan kunci. ↩ ↩2

Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.

Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.

Artikel ini berkaitan langsung dengan layanan berikut.

Pertanyaan yang sering diajukan

Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.

Benarkah DllMain sama sekali tidak boleh mengerjakan apa pun?
"Jangan lakukan apa pun" bukan hiperbola, melainkan kebijakan desain resmi. Microsoft sendiri menyatakan bahwa DllMain yang ideal adalah stub yang hampir kosong. Yang aman hanyalah fungsi Kernel32.dll — DLL ini dijamin sudah termuat pada saat DllMain berjalan — sepanjang pemanggilan itu tidak memuat DLL lain. Membuat critical section atau mutex, serta memakai TLS, masih diperbolehkan. Sebaliknya LoadLibrary/FreeLibrary, sinkronisasi dengan thread lain, dan pemanggilan fungsi User32, Shell, COM, dan sejenisnya dilarang karena dapat memicu deadlock atau access violation. Inisialisasi yang meragukan jangan dikerjakan di DllMain; tunda sampai objek itu dipakai pertama kali.
Apakah konstruktor variabel global C++ (objek statis) juga terkena batasan DllMain?
Ya. Jika DLL ditautkan dengan CRT (runtime C++), konstruktor dan destruktor objek global serta statis dijalankan lewat entry point yang disediakan CRT, secara de facto sebagai bagian dari DllMain. Jadi memanggil LoadLibrary dari konstruktor, memulai thread lain lalu menunggu selesainya, menginisialisasi COM, dan pekerjaan serupa membawa risiko yang sama persis dengan mengerjakannya di DllMain. Untuk objek global yang inisialisasinya rumit, simpan sebagai pointer lalu buat pada akses pertama, atau pakai static di dalam fungsi, agar waktu eksekusinya bergeser ke luar DllMain.
Perlukah memanggil DisableThreadLibraryCalls?
Bergantung pada kondisi. Jika DLL tidak membutuhkan notifikasi DLL_THREAD_ATTACH/DETACH, panggil DisableThreadLibraryCalls pada DLL_PROCESS_ATTACH untuk menghentikan notifikasi setiap kali thread dibuat atau dihancurkan, dan mengurangi overhead pada proses yang sering membuat thread. Ada dua pengecualian. Jangan panggil dari DLL yang ditautkan dengan CRT statis (CRT statis membutuhkan notifikasi thread). Jika TLS statis lewat thread_local atau __declspec(thread) aktif, panggilan itu sendiri gagal dan mengembalikan FALSE, jadi biasakan memeriksa nilai kembali. Untuk DLL biasa yang memakai CRT tautan dinamis, pakailah setelah memastikan tidak ada pemrosesan yang bergantung pada notifikasi thread.
Mengapa DLL C++/CLI (campuran managed) hang saat start?
Penyebab khasnya adalah mencoba menjalankan MSIL (kode managed) sambil loader lock masih dipegang. Pada assembly campuran C++/CLI, jika DllMain, fungsi yang dipanggil darinya, atau dynamic initializer variabel global dikompilasi menjadi MSIL, inisialisasi CLR atau pemuatan assembly lain dapat diperlukan di bawah loader lock, dan deadlock bisa terjadi. Kompiler mengeluarkan peringatan C4747 jika DllMain mencoba mengeksekusi MSIL secara langsung, tetapi tidak dapat mendeteksi eksekusi tidak langsung lewat modul lain. Penanganannya: kompilasi DllMain dan pohon pemanggilannya sebagai native dengan #pragma unmanaged, atau jangan miliki DllMain sama sekali.
Bolehkah merapikan sumber daya di DLL_PROCESS_DETACH?
Jawabannya berbeda antara "saat proses berakhir" dan "saat unload lewat FreeLibrary". Pada DLL_PROCESS_DETACH saat proses berakhir, thread lain sudah dihentikan secara paksa dan ruang alamat tidak dijamin masih konsisten, sehingga pembersihan seperti membebaskan memori justru berbahaya. Panduan resmi menyatakan bahwa "handler yang ideal kosong". Data yang perlu dipersistensi harus ditulis di jalur shutdown aplikasi sendiri; di sini pada dasarnya jangan melakukan apa pun lalu kembali. Sebaliknya, pada unload lewat FreeLibrary proses tetap hidup, jadi pembersihan lengkap — menghentikan thread, menutup handle, dan sebagainya — memang diperlukan. Namun menunggu thread berakhir di dalam DllMain akan deadlock, jadi ikuti protokol resmi: beri isyarat, tunggu sampai keadaan konsisten, dan selesaikan pekerjaan di luar DllMain.

Profil penulis

Halaman perkenalan penulis artikel.

Go Komura

Direktur KomuraSoft LLC

Berspesialisasi dalam pengembangan perangkat lunak Windows, konsultasi teknis, dan investigasi bug, terutama pada proyek dengan sistem yang sudah ada dan bug yang sulit direproduksi.

Kembali ke blog