Menautkan executable ke DLL

File yang dapat dieksekusi menautkan ke (atau memuat) DLL dengan salah satu dari dua cara:

  • Penautan implisit, di mana sistem operasi memuat DLL secara bersamaan dengan yang dapat dieksekusi yang menggunakannya. Klien yang dapat dieksekusi memanggil fungsi DLL yang diekspor dengan cara yang sama seolah-olah fungsi ditautkan secara statis dan terkandung dalam yang dapat dieksekusi. Penautan implisit terkadang disebut sebagai pemuatan statis atau penautan dinamis waktu muat.

  • Penautan eksplisit, di mana sistem operasi memuat DLL sesuai permintaan pada runtime. Executable yang menggunakan DLL dengan penautan eksplisit harus secara eksplisit memuat dan membongkar DLL. Ini juga harus menyiapkan penunjuk fungsi untuk mengakses setiap fungsi yang digunakannya dari DLL. Tidak seperti panggilan ke fungsi dalam pustaka yang ditautkan secara statis atau DLL yang ditautkan secara implisit, klien yang dapat dieksekusi harus memanggil fungsi yang diekspor dalam DLL yang ditautkan secara eksplisit melalui penunjuk fungsi. Penautan eksplisit terkadang disebut sebagai beban dinamis atau penautan dinamis run-time.

Executable dapat menggunakan metode penautan untuk menautkan ke DLL yang sama. Selain itu, metode ini tidak saling eksklusif; satu executable dapat secara implisit menautkan ke DLL, dan yang lain mungkin melampirkannya secara eksplisit.

Menentukan metode penautan mana yang akan digunakan

Apakah menggunakan penautan implisit atau penautan eksplisit adalah keputusan arsitektur yang harus Anda buat untuk aplikasi Anda. Ada kelebihan dan kekurangan untuk setiap metode.

Penautan Implisit

Penautan implisit terjadi ketika kode aplikasi memanggil fungsi DLL yang diekspor. Ketika kode sumber untuk panggilan yang dapat dieksekusi dikompilasi atau dirakit, panggilan fungsi DLL menghasilkan referensi fungsi eksternal dalam kode objek. Untuk mengatasi referensi eksternal ini, aplikasi harus menautkan dengan pustaka impor (file.lib) yang disediakan oleh pembuat DLL.

Pustaka impor hanya berisi kode untuk memuat DLL dan untuk mengimplementasikan panggilan ke fungsi di DLL. Menemukan fungsi eksternal di pustaka impor memberi tahu linker bahwa kode untuk fungsi tersebut berada dalam DLL. Untuk mengatasi referensi eksternal ke DLL, linker hanya menambahkan informasi ke file yang dapat dieksekusi yang memberi tahu sistem tempat menemukan kode DLL saat proses dimulai.

Ketika sistem memulai program yang berisi referensi yang ditautkan secara dinamis, sistem menggunakan informasi dalam file yang dapat dieksekusi program untuk menemukan DLL yang diperlukan. Jika tidak dapat menemukan DLL, sistem mengakhiri proses, dan menampilkan kotak dialog yang melaporkan kesalahan. Jika tidak, sistem memetakan modul DLL ke ruang alamat proses.

Jika salah satu DLL memiliki fungsi titik entri untuk inisialisasi dan kode penghentian seperti DllMain, sistem operasi memanggil fungsi . Salah satu parameter yang diteruskan ke fungsi titik entri menentukan kode yang menunjukkan DLL melampirkan ke proses. Jika fungsi titik masuk tidak mengembalikan TRUE, sistem mengakhiri proses dan melaporkan kesalahan.

Terakhir, sistem memodifikasi kode proses yang dapat dieksekusi untuk menyediakan alamat awal untuk fungsi DLL.

Seperti kode program lainnya, loader memetakan kode DLL ke ruang alamat proses saat proses dimulai. Sistem operasi memuatnya ke dalam memori hanya jika diperlukan. Akibatnya, PRELOAD atribut kode dan LOADONCALL yang digunakan oleh file .def untuk mengontrol pemuatan di versi Windows sebelumnya tidak lagi memiliki arti.

Penautan Eksplisit

Sebagian besar aplikasi menggunakan penautan implisit karena ini adalah metode penautan term mudah untuk digunakan. Namun, ada kalanya penautan eksplisit diperlukan. Berikut adalah beberapa alasan umum untuk menggunakan penautan eksplisit:

  • Aplikasi tidak tahu nama DLL yang dimuatnya hingga durasi. Misalnya, aplikasi mungkin mendapatkan nama DLL dan fungsi yang diekspor dari file konfigurasi saat startup.

  • Proses yang menggunakan penautan implisit dihentikan oleh sistem operasi jika DLL tidak ditemukan saat startup proses. Proses yang menggunakan penautan eksplisit tidak dihentikan dalam situasi ini, dan dapat mencoba memulihkan dari kesalahan. Misalnya, proses dapat memberi tahu pengguna tentang kesalahan dan meminta pengguna menentukan jalur lain ke DLL.

  • Proses yang menggunakan penautan implisit juga dihentikan jika salah satu DLL yang ditautkan untuk memiliki DllMain fungsi yang gagal. Proses yang menggunakan penautan eksplisit tidak dihentikan dalam situasi ini.

  • Aplikasi yang secara implisit menautkan ke banyak DLL dapat lambat dimulai karena Windows memuat semua DLL saat aplikasi dimuat. Untuk meningkatkan performa startup, aplikasi mungkin hanya menggunakan penautan implisit untuk DLL yang diperlukan segera setelah pemuatan. Ini mungkin menggunakan penautan eksplisit untuk memuat DLL lain hanya ketika diperlukan.

  • Penautan eksplisit menghilangkan kebutuhan untuk menautkan aplikasi dengan menggunakan pustaka impor. Jika perubahan dll menyebabkan ordinal ekspor berubah, aplikasi tidak perlu menautkan ulang jika mereka memanggil GetProcAddress menggunakan nama fungsi dan bukan nilai ordinal. Aplikasi yang menggunakan penautan implisit masih harus menautkan ulang ke pustaka impor yang diubah.

Berikut adalah dua bahaya penautan eksplisit yang perlu diperhatikan:

  • Jika DLL memiliki DllMain fungsi titik masuk, sistem operasi memanggil fungsi dalam konteks utas yang disebut LoadLibrary. Fungsi titik entri tidak dipanggil jika DLL sudah dilampirkan ke proses karena panggilan sebelumnya ke LoadLibrary yang tidak memiliki panggilan yang sesuai dengan FreeLibrary fungsi. Penautan eksplisit dapat menyebabkan masalah jika DLL menggunakan DllMain fungsi untuk menginisialisasi setiap utas proses, karena utas apa pun yang sudah ada ketika LoadLibrary (atau AfxLoadLibrary) disebut tidak diinisialisasi.

  • Jika DLL menyatakan data tingkat statis sebagai __declspec(thread), itu dapat menyebabkan kesalahan perlindungan jika ditautkan secara eksplisit. Setelah DLL dimuat oleh panggilan ke LoadLibrary, itu menyebabkan kesalahan perlindungan setiap kali kode mereferensikan data ini. (Data tingkat statis mencakup item statis global dan lokal.) Itu sebabnya, saat membuat DLL, Anda harus menghindari penggunaan penyimpanan lokal utas. Jika tidak dapat, maka beri tahu pengguna DLL Anda tentang potensi jepitan pemuatan DLL Anda secara dinamis. Untuk informasi selengkapnya, lihat Menggunakan penyimpanan lokal utas di pustaka tautan dinamis (Windows SDK).

Cara menggunakan penautan implisit

Untuk menggunakan DLL dengan menautkan implisit, klien yang dapat dieksekusi harus mendapatkan file-file ini dari penyedia DLL:

  • Satu atau beberapa file header (file.h) yang berisi deklarasi data, fungsi, dan kelas C++ yang diekspor di DLL. Kelas, fungsi, dan data yang diekspor oleh DLL semuanya harus ditandai __declspec(dllimport) dalam file header. Untuk informasi selengkapnya, lihat dllexport, dllimport.

  • Pustaka impor untuk ditautkan ke dalam executable Anda. Linker membuat pustaka impor saat DLL dibuat. Untuk informasi selengkapnya, lihat File LIB sebagai input linker.

  • File DLL aktual.

Untuk menggunakan data, fungsi, dan kelas dalam DLL dengan menautkan implisit, file sumber klien apa pun harus menyertakan file header yang mendeklarasikannya. Dari perspektif pengkodian, panggilan ke fungsi yang diekspor sama seperti panggilan fungsi lainnya.

Untuk membangun file klien yang dapat dieksekusi, Anda harus menautkan dengan pustaka impor DLL. Jika Anda menggunakan sistem makefile atau build eksternal, tentukan pustaka impor bersama dengan file objek atau pustaka lain yang Anda tautkan.

Sistem operasi harus dapat menemukan file DLL saat memuat executable panggilan. Itu berarti Anda harus menyebarkan atau memverifikasi keberadaan DLL saat menginstal aplikasi Anda.

Untuk menggunakan DLL dengan penautan eksplisit, aplikasi harus melakukan panggilan fungsi untuk memuat DLL secara eksplisit pada waktu proses. Untuk menautkan secara eksplisit ke DLL, aplikasi harus:

  • Panggil LoadLibraryEx atau fungsi serupa untuk memuat DLL dan mendapatkan handel modul.

  • Panggil GetProcAddress untuk mendapatkan penunjuk fungsi ke setiap fungsi yang diekspor yang dipanggil aplikasi. Karena aplikasi memanggil fungsi DLL melalui penunjuk, pengkompilasi tidak menghasilkan referensi eksternal, sehingga tidak perlu menautkan dengan pustaka impor. Namun, Anda harus memiliki typedef pernyataan atau using yang menentukan tanda tangan panggilan dari fungsi yang diekspor yang Anda panggil.

  • Panggil FreeLibrary setelah selesai dengan DLL.

Misalnya, fungsi sampel ini memanggil LoadLibrary untuk memuat DLL bernama "MyDLL", memanggil GetProcAddress untuk mendapatkan penunjuk ke fungsi bernama "DLLFunc1", memanggil fungsi dan menyimpan hasilnya, lalu memanggil FreeLibrary untuk membongkar DLL.

#include "windows.h"

typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);

HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
    HINSTANCE hDLL;               // Handle to DLL
    LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer
    HRESULT hrReturnVal;

    hDLL = LoadLibrary("MyDLL");
    if (NULL != hDLL)
    {
        lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
        if (NULL != lpfnDllFunc1)
        {
            // call the function
            hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
        }
        else
        {
            // report the error
            hrReturnVal = ERROR_DELAY_LOAD_FAILED;
        }
        FreeLibrary(hDLL);
    }
    else
    {
        hrReturnVal = ERROR_DELAY_LOAD_FAILED;
    }
    return hrReturnVal;
}

Tidak seperti contoh ini, dalam kebanyakan kasus Anda harus memanggil LoadLibrary dan FreeLibrary hanya sekali dalam aplikasi Anda untuk DLL tertentu. Ini terutama benar jika Anda akan memanggil beberapa fungsi di DLL, atau memanggil fungsi DLL berulang kali.

Apa yang ingin Anda ketahui lebih lanjut?

Baca juga

Membuat C/C++ DLL di Visual Studio