Bagikan melalui


DLL dan perilaku pustaka run-time Visual C++

Saat Anda membuat Pustaka Tautan Dinamis (DLL) dengan menggunakan Visual Studio, secara default, linker menyertakan pustaka run-time Visual C++ (VCRuntime). VCRuntime berisi kode yang diperlukan untuk menginisialisasi dan mengakhiri C/C++ yang dapat dieksekusi. 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 ini _DllMainCRTStartup melakukan tugas penting seperti penyiapan keamanan buffer tumpukan, inisialisasi dan penghentian pustaka run-time C (CRT), dan panggilan ke konstruktor dan destruktor untuk objek statis dan global. _DllMainCRTStartup juga memanggil fungsi kait untuk pustaka lain seperti WinRT, MFC, dan ATL untuk melakukan inisialisasi dan penghentian mereka sendiri. Tanpa inisialisasi ini, CRT dan pustaka lainnya, serta 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.

titik masuk DLL default _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 tambahan sesuai kebutuhan. Windows memanggil fungsi titik entri dalam empat situasi: lampirkan proses, lepas proses, lampirkan utas, dan lepaskan 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 lampirkan proses. Lampiran utas terjadi ketika proses DLL dimuat dalam membuat utas baru. Penghapusan utas terjadi ketika utas dihentikan, dan penghapusan proses adalah ketika DLL tidak lagi diperlukan dan dirilis 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 alasan untuk melampirkan proses sinyal.

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 run-time, menginisialisasi dan memanggil konstruktor untuk data statis dan non-lokal, menginisialisasi penyimpanan lokal utas, menaikkan penghitung statis internal untuk setiap lampiran, lalu memanggil pengguna atau pustaka yang disediakan DllMain. Pada proses lepas, fungsi melewati langkah-langkah ini secara terbalik. Ini memanggil DllMain, mengurangi penghitung internal, memanggil destruktor, memanggil fungsi penghentian CRT dan fungsi terdaftar atexit , dan memberi tahu pustaka penghentian lainnya. Ketika penghitung lampiran masuk ke nol, fungsi kembali FALSE menunjukkan ke 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 tambahan sendiri, dan hanya memanggil DllMain untuk meneruskan pesan. Jika DllMain kembali FALSE dari lampiran proses, memberi sinyal kegagalan, _DllMainCRTStartup memanggil DllMain lagi dan meneruskan DLL_PROCESS_DETACH sebagai argumen alasan , maka melewati sisa proses penghentian.

Saat membangun DLL di Visual Studio, titik _DllMainCRTStartup masuk default yang disediakan oleh VCRuntime ditautkan secara otomatis. Anda tidak perlu menentukan fungsi titik entri untuk DLL Anda dengan menggunakan opsi linker /ENTRY (Simbol titik entri).

Catatan

Meskipun dimungkinkan untuk menentukan fungsi titik entri lain untuk DLL dengan menggunakan opsi /ENTRY: linker, kami tidak merekomendasikannya, karena fungsi titik masuk Anda harus menduplikasi semua yang _DllMainCRTStartup dilakukan, dalam urutan yang sama. VCRuntime menyediakan fungsi yang memungkinkan Anda untuk menduplikasi perilakunya. Misalnya, Anda dapat segera memanggil __security_init_cookie pada lampiran proses 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 _DllMainCRTStartup entri default 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 DllMain fungsi untuk Anda. Misalnya, dalam DLL MFC reguler, terapkan CWinApp fungsi objek InitInstance dan ExitInstance anggota untuk melakukan inisialisasi dan penghentian yang diperlukan oleh DLL Anda. Untuk detail 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 tertentu yang tidak aman untuk dipanggil DllMain, lihat Praktik Terbaik Umum. Jika Anda memerlukan 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 aktual fungsi titik masuk DLL harus ditentukan pada baris perintah linker dengan opsi /ENTRY. Dengan Visual Studio, Anda tidak perlu menggunakan opsi /ENTRY jika nama fungsi entry-point Anda adalah DllMain. Bahkan, jika Anda menggunakan opsi /ENTRY dan memberi nama fungsi titik masuk Anda sesuatu selain DllMain, CRT tidak diinisialisasi dengan benar kecuali fungsi titik entri Anda melakukan panggilan inisialisasi yang sama yang _DllMainCRTStartup membuat.

Menginisialisasi DLL MFC reguler

Karena DLL MFC reguler memiliki CWinApp objek, MEREKA harus melakukan tugas inisialisasi dan penghentian mereka di lokasi yang sama dengan aplikasi MFC: dalam InitInstance fungsi anggota dan ExitInstance dari kelas -turunan DLL CWinApp. Karena MFC menyediakan DllMain fungsi yang dipanggil oleh _DllMainCRTStartup dan DLL_PROCESS_ATTACH DLL_PROCESS_DETACH, Anda tidak boleh menulis fungsi Anda sendiri DllMain . Fungsi yang disediakan 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 debug DL versiMFCOD.dll, versiMFCDD.dll, dan versiMFCND.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 (versiMFCDD.dll) AfxDbInitModule
Soket MFC (versiMFCND.dll) AfxNetInitModule

Menginisialisasi DLL ekstensi MFC

Karena DLL ekstensi MFC tidak memiliki CWinAppobjek -turunan (seperti halnya DLL MFC reguler), Anda harus menambahkan kode inisialisasi dan penghentian Anda ke DllMain fungsi yang dihasilkan Wizard DLL MFC.

Wizard menyediakan kode berikut untuk DLL ekstensi MFC. Dalam kode, PROJNAME adalah tempat penampung 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 Anda dari satu atau beberapa DLL MFC reguler, Anda harus mengekspor fungsi inisialisasi yang membuat CDynLinkLibrary objek. 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.

DllMain Dalam yang dihasilkan MFC DLL Wizard, panggilan untuk AfxInitExtensionModule mengambil kelasCRuntimeClass run-time (struktur) modul serta pabrik objek (COleObjectFactory objek) untuk digunakan saat CDynLinkLibrary objek dibuat. Anda harus memeriksa nilai AfxInitExtensionModulepengembalian ; jika nilai nol dikembalikan dari AfxInitExtensionModule, mengembalikan nol dari fungsi Anda DllMain .

Jika DLL ekstensi MFC Anda akan secara eksplisit ditautkan ke executable (yang berarti panggilan AfxLoadLibrary yang dapat dieksekusi untuk ditautkan 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 keluar atau ketika DLL dibongkar sebagai akibat dari AfxFreeLibrary panggilan). Jika DLL ekstensi MFC Anda akan 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 ketika 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 DLL_THREAD_ATTACH kasus dan DLL_THREAD_DETACH dalam DllMain fungsi. Kasus-kasus ini diteruskan ke DllMain ketika utas melampirkan dan melepaskan dari DLL. Memanggil TlsAlloc saat DLL melampirkan memungkinkan DLL mempertahankan indeks penyimpanan lokal utas (TLS) untuk setiap utas yang dilampirkan ke DLL.

Perhatikan bahwa file header Afxdllx.h 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 bahwa Anda tidak menentukan atau tidak mendefinisikan makro _AFX_NO_XXX apa pun di pch.h (stdafx.h di Visual Studio 2017 dan yang lebih lama). Makro ini hanya ada untuk tujuan 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 Utas 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 run-time MFC dan C.

Lihat juga

Membuat C/C++ DLL di Visual Studio
Titik masuk DllMain
Praktik Terbaik Pustaka Dynamic-link