Share via


Dukungan Linker untuk DLL yang dimuat keterlambatan

Linker MSVC mendukung pemuatan DLL yang tertunda. Fitur ini meringankan Anda dari kebutuhan untuk menggunakan fungsi LoadLibrary Windows SDK dan GetProcAddress untuk menerapkan pemuatan dll yang tertunda.

Tanpa beban tertunda, satu-satunya cara untuk memuat DLL pada waktu proses adalah dengan menggunakan LoadLibrary dan GetProcAddress; sistem operasi memuat DLL ketika eksekusi atau DLL menggunakannya akan dimuat.

Dengan beban tertunda, ketika Anda secara implisit menautkan DLL, linker menyediakan opsi untuk menunda beban DLL hingga program memanggil fungsi di DLL tersebut.

Aplikasi dapat menunda pemuatan DLL menggunakan /DELAYLOAD opsi penghubung (Tunda impor beban) dengan fungsi pembantu. (Implementasi fungsi pembantu default disediakan oleh Microsoft.) Fungsi pembantu memuat DLL sesuai permintaan pada runtime dengan memanggil LoadLibrary dan GetProcAddress untuk Anda.

Pertimbangkan untuk menunda pemuatan DLL jika:

  • Program Anda mungkin tidak memanggil fungsi dalam DLL.

  • Fungsi di DLL mungkin tidak dipanggil hingga terlambat dalam eksekusi program Anda.

Pemuatan DLL yang tertunda dapat ditentukan selama build proyek EXE atau DLL. Proyek DLL yang menunda pemuatan satu atau beberapa DLL itu sendiri tidak boleh memanggil titik masuk yang dimuat keterlambatan di DllMain.

Tentukan DLL untuk menunda pemuatan

Anda dapat menentukan DLL mana yang akan ditunda bebannya dengan menggunakan /delayload:dllname opsi linker. Jika Anda tidak berencana menggunakan versi fungsi pembantu Anda sendiri, Anda juga harus menautkan program Anda dengan delayimp.lib (untuk aplikasi desktop) atau dloadhelper.lib (untuk aplikasi UWP).

Berikut adalah contoh sederhana penundaan memuat DLL:

// cl t.cpp user32.lib delayimp.lib  /link /DELAYLOAD:user32.dll
#include <windows.h>
// uncomment these lines to remove .libs from command line
// #pragma comment(lib, "delayimp")
// #pragma comment(lib, "user32")

int main() {
   // user32.dll will load at this point
   MessageBox(NULL, "Hello", "Hello", MB_OK);
}

Buat versi DEBUG proyek. Menelusuri kode menggunakan debugger dan Anda akan melihat bahwa user32.dll dimuat hanya ketika Anda melakukan panggilan ke MessageBox.

Secara eksplisit membongkar DLL yang dimuat keterlambatan

Opsi /delay:unload linker memungkinkan kode Anda untuk secara eksplisit membongkar DLL yang dimuat keterlambatan. Secara default, impor yang dimuat keterlambatan tetap berada di tabel alamat impor (IAT). Namun, jika Anda menggunakan /delay:unload pada baris perintah linker, fungsi pembantu mendukung pembongkaran DLL secara eksplisit dengan __FUnloadDelayLoadedDLL2 panggilan, dan mengatur ulang IAT ke bentuk aslinya. Penunjuk yang sekarang tidak valid ditimpa. IAT adalah bidang dalam ImgDelayDescr struktur yang berisi alamat salinan IAT asli, jika ada.

Contoh pembongkaran DLL yang dimuat keterlambatan

Contoh ini menunjukkan cara secara eksplisit membongkar DLL, MyDll.dll, yang berisi fungsi fnMyDll:

// link with /link /DELAYLOAD:MyDLL.dll /DELAY:UNLOAD
#include <windows.h>
#include <delayimp.h>
#include "MyDll.h"
#include <stdio.h>

#pragma comment(lib, "delayimp")
#pragma comment(lib, "MyDll")
int main()
{
    BOOL TestReturn;
    // MyDLL.DLL will load at this point
    fnMyDll();

    //MyDLL.dll will unload at this point
    TestReturn = __FUnloadDelayLoadedDLL2("MyDll.dll");

    if (TestReturn)
        printf_s("\nDLL was unloaded");
    else
        printf_s("\nDLL was not unloaded");
}

Catatan penting tentang membongkar DLL yang dimuat keterlambatan:

  • Anda dapat menemukan implementasi __FUnloadDelayLoadedDLL2 fungsi dalam file delayhlp.cpp, di direktori MSVC include . Untuk informasi selengkapnya, lihat Memahami fungsi pembantu beban penundaan.

  • Parameter name__FUnloadDelayLoadedDLL2 fungsi harus sama persis dengan (termasuk kasus) apa yang dikandung pustaka impor. (String tersebut juga berada dalam tabel impor dalam gambar.) Anda dapat melihat konten pustaka impor dengan menggunakan DUMPBIN /DEPENDENTS. Jika Anda lebih suka kecocokan string yang tidak peka huruf besar/kecil, Anda dapat memperbarui __FUnloadDelayLoadedDLL2 untuk menggunakan salah satu fungsi string CRT yang tidak peka huruf besar/kecil, atau panggilan API Windows.

Mengikat impor yang dimuat keterlambatan

Perilaku linker default adalah membuat tabel alamat impor (IAT) yang dapat diikat untuk DLL yang dimuat keterlambatan. Jika DLL terikat, fungsi pembantu mencoba menggunakan informasi terikat alih-alih memanggil GetProcAddress setiap impor yang dirujuk. Jika tanda waktu atau alamat pilihan tidak cocok dengan yang ada di DLL yang dimuat, fungsi pembantu mengasumsikan tabel alamat impor terikat sudah kedaluarsa. Ini berlanjut seolah-olah IAT tidak ada.

Jika Anda tidak pernah berniat untuk mengikat impor DLL yang dimuat keterlambatan, tentukan /delay:nobind pada baris perintah linker. Linker tidak akan menghasilkan tabel alamat impor terikat, yang menghemat ruang dalam file gambar.

Muat semua impor untuk DLL yang dimuat keterlambatan

Fungsi __HrLoadAllImportsForDll , yang didefinisikan dalam delayhlp.cpp, memberi tahu linker untuk memuat semua impor dari DLL yang ditentukan dengan /delayload opsi linker.

Saat Memuat semua impor sekaligus, Anda dapat mempusatkan penanganan kesalahan di satu tempat. Anda dapat menghindari penanganan pengecualian terstruktur di sekitar semua panggilan aktual ke impor. Ini juga menghindari situasi di mana aplikasi Anda gagal melalui proses: Misalnya, jika kode pembantu gagal memuat impor, setelah berhasil memuat yang lain.

__HrLoadAllImportsForDll Panggilan tidak mengubah perilaku kait dan penanganan kesalahan. Untuk informasi selengkapnya, lihat Penanganan kesalahan dan pemberitahuan.

__HrLoadAllImportsForDll membuat perbandingan peka huruf besar/kecil dengan nama yang disimpan di dalam DLL itu sendiri.

Berikut adalah contoh yang menggunakan __HrLoadAllImportsForDll dalam fungsi yang disebut TryDelayLoadAllImports untuk mencoba memuat DLL bernama. Ini menggunakan fungsi, CheckDelayException, untuk menentukan perilaku pengecualian.

int CheckDelayException(int exception_value)
{
    if (exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
        exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND))
    {
        // This example just executes the handler.
        return EXCEPTION_EXECUTE_HANDLER;
    }
    // Don't attempt to handle other errors
    return EXCEPTION_CONTINUE_SEARCH;
}

bool TryDelayLoadAllImports(LPCSTR szDll)
{
    __try
    {
        HRESULT hr = __HrLoadAllImportsForDll(szDll);
        if (FAILED(hr))
        {
            // printf_s("Failed to delay load functions from %s\n", szDll);
            return false;
        }
    }
    __except (CheckDelayException(GetExceptionCode()))
    {
        // printf_s("Delay load exception for %s\n", szDll);
        return false;
    }
    // printf_s("Delay load completed for %s\n", szDll);
    return true;
}

Anda dapat menggunakan hasil TryDelayLoadAllImports untuk mengontrol apakah Anda memanggil fungsi impor atau tidak.

Penanganan dan pemberitahuan kesalahan

Jika program Anda menggunakan DLL yang dimuat keterlambatan, program harus menangani kesalahan dengan kuat. Kegagalan yang terjadi saat program sedang berjalan akan mengakibatkan pengecualian yang tidak tertangani. Untuk informasi selengkapnya tentang penanganan dan pemberitahuan kesalahan penundaan DLL, lihat Penanganan kesalahan dan pemberitahuan.

Impor yang dimuat penundaan cadangan

Impor yang dimuat keterlambatan dapat dibuang dengan menggunakan DUMPBIN /IMPORTS. Impor ini muncul dengan informasi yang sedikit berbeda dari impor standar. Mereka dipisahkan ke bagian mereka sendiri dari /imports daftar, dan secara eksplisit diberi label sebagai impor yang dimuat keterlambatan. Jika ada informasi bongkar yang ada dalam gambar, yang dicatat. Jika ada informasi ikatan yang ada, stempel waktu dan tanggal DLL target dicatat bersama dengan alamat terikat impor.

Batasan pada DLL delay-load

Ada beberapa batasan pada penundaan pemuatan impor DLL.

  • Impor data tidak dapat didukung. Solusinya adalah secara eksplisit menangani impor data sendiri dengan menggunakan LoadLibrary (atau dengan menggunakan GetModuleHandle setelah Anda tahu pembantu beban penundaan telah memuat DLL) dan GetProcAddress.

  • Pemuatan Kernel32.dll penundaan tidak didukung. DLL ini harus dimuat agar rutinitas pembantu delay-load berfungsi.

  • Pengikatan titik masuk yang diteruskan tidak didukung.

  • Proses mungkin memiliki perilaku yang berbeda jika DLL dimuat keterlambatan, alih-alih dimuat pada start-up. Ini dapat dilihat jika ada inisialisasi per proses yang terjadi di titik masuk DLL yang dimuat keterlambatan. Kasus lain termasuk TLS statis (penyimpanan lokal utas), dinyatakan menggunakan __declspec(thread), yang tidak ditangani ketika DLL dimuat melalui LoadLibrary. TLS dinamis, menggunakan TlsAlloc, , TlsGetValueTlsFree, dan TlsSetValue, masih tersedia untuk digunakan dalam DLL statis atau tertunda.

  • Menginisialisasi ulang penunjuk fungsi global statis ke fungsi yang diimpor setelah panggilan pertama dari setiap fungsi. Itu diperlukan karena penggunaan pertama penunjuk fungsi menunjuk ke thunk, bukan fungsi yang dimuat.

  • Saat ini tidak ada cara untuk menunda pemuatan hanya prosedur tertentu dari DLL saat menggunakan mekanisme impor normal.

  • Konvensi panggilan kustom (seperti menggunakan kode kondisi pada arsitektur x86) tidak didukung. Selain itu, register floating-point tidak disimpan di platform apa pun. Berhati-hatilah jika rutinitas pembantu kustom atau rutinitas kait Anda menggunakan jenis floating-point: Rutinitas harus menyimpan dan memulihkan status floating-point lengkap pada komputer yang menggunakan konvensi panggilan register dengan parameter floating-point. Berhati-hatilah dengan penundaan pemuatan DLL CRT, terutama jika Anda memanggil fungsi CRT yang mengambil parameter floating-point pada tumpukan prosesor data numerik (NDP) dalam fungsi bantuan.

Memahami fungsi pembantu beban penundaan

Fungsi pembantu untuk pemuatan tertunda yang didukung linker adalah apa yang sebenarnya memuat DLL saat runtime. Anda dapat mengubah fungsi pembantu untuk menyesuaikan perilakunya. Alih-alih menggunakan fungsi pembantu yang disediakan di delayimp.lib, tulis fungsi Anda sendiri dan tautkan ke program Anda. Satu fungsi pembantu melayani semua DLL yang dimuat keterlambatan. Untuk informasi selengkapnya, lihat Memahami fungsi pembantu beban penundaan dan Mengembangkan fungsi pembantu Anda sendiri.

Baca juga

Membuat C/C++ DLL di Visual Studio
Referensi linker MSVC