Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Saat Anda membuat Pustaka Tautan Dinamis (DLL) menggunakan Visual Studio, secara default, linker menyertakan pustaka runtime Visual C++ (VCRuntime). VCRuntime berisi kode yang diperlukan untuk menginisialisasi dan mengakhiri proses file eksekusi C/C++. Ketika ditautkan ke DLL, kode VCRuntime menyediakan fungsi titik masuk DLL internal yang disebut _DllMainCRTStartup yang menangani pesan OS Windows ke DLL untuk melampirkan atau melepaskan dari proses atau utas. Fungsi _DllMainCRTStartup melakukan tugas penting seperti penyiapan keamanan buffer stack, inisialisasi dan penghentian pustaka runtime C (CRT), dan pemanggilan konstruktor dan destruktor untuk objek statis dan global.
_DllMainCRTStartup juga memanggil fungsi kait untuk pustaka lain seperti WinRT, MFC, dan ATL untuk menjalankan inisialisasi dan penghentian masing-masing secara mandiri. Tanpa inisialisasi ini, CRT dan pustaka lainnya, dan variabel statis Anda, akan dibiarkan dalam keadaan tidak diinisialisasi. Rutinitas inisialisasi dan penghentian internal VCRuntime yang sama dipanggil apakah DLL Anda menggunakan CRT yang ditautkan secara statis atau DLL CRT yang ditautkan secara dinamis.
poin masuk default DLL _DllMainCRTStartup
Di Windows, semua DLL dapat berisi fungsi titik masuk opsional, biasanya disebut DllMain, yang dipanggil untuk inisialisasi dan penghentian. Ini memberi Anda kesempatan untuk mengalokasikan atau merilis sumber daya lain sesuai kebutuhan. Windows memanggil fungsi titik entri dalam empat situasi: penyambungan proses, pemisahan proses, penyambungan utas, dan pemisahan utas. Ketika DLL dimuat ke ruang alamat proses, baik ketika aplikasi yang menggunakannya dimuat, atau ketika aplikasi meminta DLL pada runtime, sistem operasi membuat salinan terpisah dari data DLL. Ini disebut melampirkan proses.
Penyambungan Utas terjadi ketika proses di mana DLL dimuat membuat utas baru.
Pemutusan utas terjadi ketika utas dihentikan, dan pemutusan proses adalah ketika DLL tidak lagi diperlukan dan dilepaskan oleh aplikasi. Sistem operasi melakukan panggilan terpisah ke titik masuk DLL untuk setiap peristiwa ini, meneruskan argumen alasan untuk setiap jenis peristiwa. Misalnya, OS mengirimkan DLL_PROCESS_ATTACH sebagai argumen penyebab untuk melampirkan sinyal proses.
Pustaka VCRuntime menyediakan fungsi titik masuk yang disebut _DllMainCRTStartup untuk menangani operasi inisialisasi dan penghentian default. Pada lampiran proses, _DllMainCRTStartup fungsi menyiapkan pemeriksaan keamanan buffer, menginisialisasi CRT dan pustaka lainnya, menginisialisasi informasi jenis runtime, menginisialisasi dan memanggil konstruktor untuk data statis dan nonlokal, menginisialisasi penyimpanan lokal utas, menaikkan penghitung statis internal untuk setiap lampiran, lalu memanggil pengguna atau pustaka yang disediakan DllMain. Saat proses terlepas, fungsi menjalani langkah-langkah ini dalam urutan terbalik. Ini memanggil DllMain, mengurangi penghitung internal, memanggil destruktor, memanggil fungsi penghentian CRT dan fungsi atexit yang terdaftar, serta memberi tahu pustaka lain tentang penghentian. Ketika penghitung lampiran menjadi nol, fungsi mengembalikan FALSE untuk menunjukkan kepada Windows bahwa DLL dapat dibongkar. Fungsi _DllMainCRTStartup ini juga dipanggil selama pemasangan utas dan melepaskan utas. Dalam kasus ini, kode VCRuntime tidak melakukan inisialisasi atau penghentian lain sendiri, dan hanya memanggil DllMain untuk meneruskan pesan. Jika DllMain mengembalikan FALSE dari penautan proses, memberi sinyal kegagalan, _DllMainCRTStartup memanggil DllMain lagi dan meneruskan DLL_PROCESS_DETACH sebagai argumen alasan, kemudian melanjutkan dengan sisa proses penghentian.
Saat membangun DLL di Visual Studio, titik masuk default yang disediakan oleh VCRuntime akan terhubung secara otomatis. Anda tidak perlu menentukan fungsi titik masuk untuk DLL Anda dengan menggunakan opsi linker /ENTRY (simbol titik masuk).
Catatan
Meskipun dimungkinkan untuk menentukan fungsi titik entri lain untuk DLL dengan menggunakan /ENTRY: opsi linker, kami tidak merekomendasikannya, karena fungsi titik masuk Anda harus menduplikasi semua yang dilakukan oleh _DllMainCRTStartup, dalam urutan yang sama. VCRuntime menyediakan fungsi yang memungkinkan Anda untuk menduplikasi perilakunya. Misalnya, Anda dapat segera memanggil __security_init_cookie saat proses terhubung untuk mendukung opsi pemeriksaan buffer /GS (pemeriksaan keamanan buffer). Anda dapat memanggil _CRT_INIT fungsi, meneruskan parameter yang sama dengan fungsi titik masuk, untuk melakukan fungsi inisialisasi atau penghentian DLL lainnya.
Menginisialisasi DLL
DLL Anda mungkin memiliki kode inisialisasi yang harus dijalankan saat DLL Anda dimuat. Agar Anda dapat melakukan fungsi inisialisasi dan penghentian DLL Anda sendiri, _DllMainCRTStartup panggil fungsi yang disebut DllMain yang dapat Anda sediakan. Anda DllMain harus memiliki tanda tangan yang diperlukan untuk titik masuk DLL. Fungsi titik masuk default _DllMainCRTStartup memanggil DllMain menggunakan parameter yang sama yang diteruskan oleh Windows. Secara default, jika Anda tidak menyediakan DllMain fungsi, Visual Studio menyediakannya untuk Anda dan menautkannya sehingga _DllMainCRTStartup selalu memiliki sesuatu untuk dipanggil. Ini berarti bahwa jika Anda tidak perlu menginisialisasi DLL Anda, tidak ada yang istimewa yang harus Anda lakukan saat membangun DLL Anda.
Ini adalah tanda tangan yang digunakan untuk DllMain:
#include <windows.h>
extern "C" BOOL WINAPI DllMain (
HINSTANCE const instance, // handle to DLL module
DWORD const reason, // reason for calling function
LPVOID const reserved); // reserved
Beberapa pustaka membungkus fungsi DllMain untuk Anda. Misalnya, dalam DLL MFC reguler, terapkan fungsi anggota objek CWinAppInitInstance dan ExitInstance untuk melakukan inisialisasi dan penghentian yang diperlukan oleh DLL Anda. Untuk informasi selengkapnya, lihat bagian Menginisialisasi DLL MFC reguler .
Peringatan
Ada batasan signifikan pada apa yang dapat Anda lakukan dengan aman di titik masuk DLL. Untuk informasi selengkapnya tentang API Windows yang tidak aman untuk dipanggil DllMain, lihat Praktik Terbaik Umum. Jika Anda membutuhkan apa pun kecuali inisialisasi paling sederhana, lakukan itu dalam fungsi inisialisasi untuk DLL. Anda dapat mengharuskan aplikasi untuk memanggil fungsi inisialisasi setelah DllMain berjalan dan sebelum mereka memanggil fungsi lain di DLL.
Menginisialisasi DLL biasa (non-MFC)
Untuk melakukan inisialisasi Anda sendiri di DLL biasa (non-MFC) yang menggunakan titik masuk yang disediakan _DllMainCRTStartup VCRuntime, kode sumber DLL Anda harus berisi fungsi yang disebut DllMain. Kode berikut menyajikan kerangka dasar yang menunjukkan seperti apa definisinya DllMain :
#include <windows.h>
extern "C" BOOL WINAPI DllMain (
HINSTANCE const instance, // handle to DLL module
DWORD const reason, // reason for calling function
LPVOID const reserved) // reserved
{
// Perform actions based on the reason for calling.
switch (reason)
{
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
Catatan
Dokumentasi Windows SDK yang lebih lama mengatakan bahwa nama sebenarnya dari fungsi titik masuk DLL harus ditentukan pada baris perintah linker dengan opsi /ENTRY. Dengan Visual Studio, Anda tidak perlu menggunakan /ENTRY opsi jika nama fungsi titik masuk Anda adalah DllMain. Bahkan, jika Anda menggunakan opsi
Inisialisasi CRT manual dengan _CRT_INIT
Saat membangun DLL yang menggunakan pustaka runtime C, untuk memastikan bahwa C Run-Time (CRT) diinisialisasi dengan benar, salah satu caranya adalah:
- Fungsi inisialisasi harus diberi nama
DllMain()dan titik masuk harus ditentukan dengan opsi-entry:_DllMainCRTStartup@12linker Ini adalah perilaku default saat membangun DLL (@12hanya x86 karena platform tersebut menggunakanstdcall), atau - Titik masuk DLL harus secara eksplisit memanggil
_CRT_INIT()saat penyambungan proses dan pemisahan proses. Ini hanya relevan jika Anda menggunakan/NOENTRYatau memiliki titik masuk kustom. Kami tidak merekomendasikan penggunaan/NOENTRYatau titik masuk kustom, jika memungkinkan, karena Anda harus menduplikasi semua kode inisialisasi dan penghentian yang_DllMainCRTStartupmenyediakan, dalam urutan yang sama.
Ini memungkinkan pustaka runtime C untuk mengalokasikan dan menginisialisasi data runtime C dengan benar ketika proses atau utas menghubungkan ke DLL, untuk membersihkan data runtime C dengan benar ketika proses terputus dari DLL, dan agar objek C++ global di DLL dapat dibangun dan dihancurkan dengan benar.
Sampel Win32 SDK semuanya menggunakan metode pertama. Lihat Referensi Win32 Programmer untuk DllEntryPoint() dan dokumentasi Visual C++ untuk DllMain().
DllMainCRTStartup() memanggil _CRT_INIT() dan _CRT_INIT() memanggil DllMain() aplikasi Anda, jika ada.
Jika Anda ingin menggunakan metode kedua dan memanggil kode inisialisasi CRT sendiri, alih-alih menggunakan DllMainCRTStartup() dan DllMain(), ada dua teknik:
Jika Anda memiliki titik masuk DLL Anda sendiri, lakukan hal berikut di titik masuk:
a. Gunakan prototipe ini (didefinisikan dalam
process.hketika_DECL_DLLMAINdidefinisikan) untuk_CRT_INIT():BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);Untuk informasi tentang
_CRT_INIT()nilai pengembalian, lihat dokumentasi untukDllEntryPoint; nilai yang sama dikembalikan.b. Pada
DLL_PROCESS_ATTACHdanDLL_THREAD_ATTACH, panggil_CRT_INIT()terlebih dahulu, sebelum fungsi runtime C dipanggil atau operasi floating-point dilakukan.c. Panggil kode inisialisasi/penghentian proses/utas Anda sendiri.
d. Pada
DLL_PROCESS_DETACHdanDLL_THREAD_DETACH, panggil_CRT_INIT()terakhir, setelah semua fungsi runtime C dipanggil dan semua operasi floating-point selesai.
Pastikan untuk meneruskan ke _CRT_INIT() semua parameter titik masuk; _CRT_INIT() mengharapkan parameter tersebut, sehingga hal-hal mungkin tidak berfungsi dengan andal jika dihilangkan (khususnya, fdwReason diperlukan untuk menentukan apakah inisialisasi atau penghentian proses diperlukan).
Di bawah ini adalah contoh skema fungsi titik masuk yang menunjukkan kapan dan bagaimana melakukan panggilan _CRT_INIT() ini ke titik masuk DLL.
BOOL WINAPI DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH || fdwReason == DLL_THREAD_ATTACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
if (fdwReason == DLL_PROCESS_DETACH || fdwReason == DLL_THREAD_DETACH)
if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
return(FALSE);
return(TRUE);
}
Catatan
Ini tidak diperlukan jika Anda menggunakan DllMain() dan -entry:_DllMainCRTStartup@12.
Menginisialisasi DLL MFC biasa
Karena DLL MFC reguler memiliki CWinApp objek, mereka harus melakukan tugas inisialisasi dan penghentian mereka di lokasi yang sama dengan aplikasi MFC: dalam InitInstance anggota fungsi dan ExitInstance dari kelas turunan DLL CWinApp. Karena MFC menyediakan fungsi DllMain MFC memanggil InitInstance saat DLL Anda dimuat dan memanggil ExitInstance sebelum DLL dibongkar.
DLL MFC reguler dapat melacak beberapa utas dengan memanggil TlsAlloc dan TlsGetValue dalam fungsinya InitInstance . Fungsi-fungsi ini memungkinkan DLL melacak data khusus utas.
Dalam DLL MFC reguler Anda yang secara dinamis menautkan ke MFC, jika Anda menggunakan dukungan MFC OLE, MFC Database (atau DAO), atau MFC Sockets, masing-masing, ekstensi MFC debugversi MFCOD.dllversi MFCDD.dll, danversi MFCND.dll ( di mana versi adalah nomor versi) ditautkan secara otomatis. Anda harus memanggil salah satu fungsi inisialisasi yang telah ditentukan sebelumnya berikut untuk setiap DLL ini yang Anda gunakan di DLL CWinApp::InitInstanceMFC reguler Anda.
| Jenis dukungan MFC | Fungsi inisialisasi untuk memanggil |
|---|---|
| MFC OLE (versi MFCOD.dll) | AfxOleInitModule |
| Database MFC (MFCD versiD.dll) | AfxDbInitModule |
| Soket MFC (MFCNversiD.dll) | AfxNetInitModule |
Menginisialisasi DLL ekstensi MFC
Karena DLL ekstensi MFC tidak memiliki objek yang diturunkan CWinApp (seperti halnya DLL MFC reguler), Anda harus menambahkan kode inisialisasi dan penghentian ke dalam fungsi DllMain yang dihasilkan Wizard DLL MFC.
Wizard menyediakan kode berikut untuk DLL ekstensi MFC. Dalam kode, PROJNAME adalah placeholder untuk nama proyek Anda.
#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("PROJNAME.DLL Initializing!\n");
// MFC extension DLL one-time initialization
AfxInitExtensionModule(PROJNAMEDLL,
hInstance);
// Insert this DLL into the resource chain
new CDynLinkLibrary(Dll3DLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("PROJNAME.DLL Terminating!\n");
}
return 1; // ok
}
Membuat objek baru CDynLinkLibrary selama inisialisasi memungkinkan DLL ekstensi MFC untuk mengekspor CRuntimeClass objek atau sumber daya ke aplikasi klien.
Jika Anda akan menggunakan DLL ekstensi MFC dari satu atau beberapa DLL MFC reguler, Anda harus mengekspor fungsi inisialisasi yang membuat objek CDynLinkLibrary. Fungsi tersebut harus dipanggil dari masing-masing DLL MFC reguler yang menggunakan DLL ekstensi MFC. Tempat yang tepat untuk memanggil fungsi inisialisasi ini berada dalam InitInstance fungsi anggota objek turunan MFC DLL CWinAppreguler sebelum menggunakan salah satu kelas atau fungsi yang diekspor DLL ekstensi MFC.
Dalam DllMain yang dihasilkan oleh Wizard DLL MFC, panggilan untuk AfxInitExtensionModule mengambil alih kelas runtime modul (CRuntimeClass struktur) dan pabrik objeknya (COleObjectFactory objek) untuk digunakan saat CDynLinkLibrary objek dibuat. Anda harus memeriksa nilai pengembalian AfxInitExtensionModule; jika nilai nol dikembalikan dari AfxInitExtensionModule, kembalikan nol pada fungsi Anda DllMain.
Jika DLL ekstensi MFC Anda secara eksplisit ditautkan ke executable (yang berarti executable memanggil AfxLoadLibrary untuk menautkan ke DLL), Anda harus menambahkan panggilan ke AfxTermExtensionModule pada DLL_PROCESS_DETACH. Fungsi ini memungkinkan MFC untuk membersihkan DLL ekstensi MFC ketika setiap proses terlepas dari DLL ekstensi MFC (yang terjadi ketika proses tersebut keluar atau ketika DLL dibongkar akibat panggilan fungsi AfxFreeLibrary). Jika DLL ekstensi MFC Anda ditautkan secara implisit ke aplikasi, panggilan ke AfxTermExtensionModule tidak diperlukan.
Aplikasi yang secara eksplisit menautkan ke DLL ekstensi MFC harus memanggil AfxTermExtensionModule saat membebaskan DLL. Mereka juga harus menggunakan AfxLoadLibrary dan AfxFreeLibrary (bukan fungsi LoadLibrary Win32 dan FreeLibrary) jika aplikasi menggunakan beberapa utas. Menggunakan AfxLoadLibrary dan AfxFreeLibrary memastikan bahwa kode startup dan shutdown yang dijalankan saat DLL ekstensi MFC dimuat dan dibongkar tidak merusak status MFC global.
Karena MFCx0.dll sepenuhnya diinisialisasi pada saat DllMain dipanggil, Anda dapat mengalokasikan memori dan memanggil fungsi MFC dalam DllMain (tidak seperti MFC versi 16-bit).
DLL ekstensi dapat mengurus multithreading dengan menangani kasus DLL_THREAD_ATTACH dan DLL_THREAD_DETACH dalam fungsi DllMain. Kasus-kasus ini diteruskan ke DllMain ketika utas terhubung dan terputus dengan DLL. Memanggil TlsAlloc ketika sebuah DLL sedang melampirkan memungkinkan DLL untuk mempertahankan indeks penyimpanan lokal utas (TLS) untuk setiap utas yang terhubung ke DLL.
File Afxdllx.h header berisi definisi khusus untuk struktur yang digunakan dalam DLL ekstensi MFC, seperti definisi untuk AFX_EXTENSION_MODULE dan CDynLinkLibrary. Anda harus menyertakan file header ini di DLL ekstensi MFC Anda.
Catatan
Penting bagi Anda untuk tidak melakukan pendefinisian atau penghapusan definisi makro apa pun di pch.h (stdafx.h di Visual Studio 2017 dan versi sebelumnya). Makro ini hanya ada untuk memeriksa apakah platform target tertentu mendukung fitur tersebut atau tidak. Anda dapat menulis program Anda untuk memeriksa makro ini (misalnya, #ifndef _AFX_NO_OLE_SUPPORT), tetapi program Anda tidak boleh menentukan atau mendefinisikan makro ini.
Fungsi inisialisasi sampel yang menangani multithreading disertakan dalam Menggunakan Penyimpanan Lokal Thread dalam Pustaka Dynamic-Link di Windows SDK. Perhatikan bahwa sampel berisi fungsi titik entri yang disebut LibMain, tetapi Anda harus memberi nama fungsi DllMain ini sehingga berfungsi dengan pustaka runtime MFC dan C.
Lihat juga
Membuat C/C++ DLL di Visual Studio
Titik masuk DllMain
Praktik Terbaik Pustaka Tautan Dinamis