動態連接庫(DLL)

動態連接庫和靜態連接庫:windows

動態連接庫通常不能直接執行,並且它們通常也不接收消息。函數

它們是包含許多函數的獨立文件,這些函數能夠被應用程序和其餘 DLL 調用以完成某些特定的工做。spa

一個動態連接庫只有在另一個模塊調用其所包含的函數時才被啓動。操作系統

 

「靜態連接」 通常是在程序開發過程當中發生的,用於把一些文件連接在一塊兒建立一個 Windows 可執行文件。線程

這些文件包括各類各樣的對象模塊(.OBJ),運行時庫文件(.LIB),一般還有已編譯的資源文件(.RES)。指針

與其相反,動態連接則發生在程序運行時。 code

靜態庫:函數和數據被編譯進一個二進制文件,擴展名爲(.lib)。對象

在使用靜態庫的狀況下,在編譯連接可執行文件時:blog

連接器從靜態庫中複製這些函數和數據,並把它們和應用程序的其餘模塊組合起來建立最終的可執行文件(.exe)。進程

當發佈產品時,只須要發佈這個可執行文件,並不須要發佈被使用的靜態庫。

 

「動態連接」 是指 Windows 的連接過程,在這個過程當中它把模塊中的函數調用與在庫模塊中的實際函數連接在一塊兒。

動態庫:在使用動態庫時,每每提供兩個文件:一個導入庫(.lib,非必須) 和一個(.dll)文件。

導入庫和靜態庫本質上的區別

靜態庫自己就包含了實際執行代碼和地址符號表等數據。

而對於導入庫而言,其實際的執行代碼位於動態庫中導入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。

動態連接庫的標準擴展名是(.dll)。只有擴展名爲(.dll)的動態連接庫才能被 Windows 操做系統自動加載。

若是該文件有另外的擴展名,則程序必須明確地用 LoadLibrary() 或 LoadLibraryEx() 加載相應模塊。

 

編寫動態連接庫

咱們編寫的程序均可以根據 UNICODE 標識符的定義編譯成可以處理 UNICODE 或者 非 UNICODE 字符串的程序。

在建立一個 DLL 時,對於任何有字符或者字符串參數的函數,它都應該包括 UNICODE 和非 UNICODE 兩個版本。

 VC++6.0 編譯器下:

File->New->Win32 Dynamic-Link Library->An empty DLL project || An Simple DLL project

An empty DLL project 和 An Simple DLL project 的區別是:後者有個簡單的示例代碼。

我之前者爲例:

新建兩個文件:MyDLL.h,MyDLL.cpp。

// MyDLL.h
#define Import extern "C" _declspec(dllexport)
Import int sum(int a, int b);
Import int sub(int a, int b);
// MyDLL.cpp#include"MyDLL.h"
Import int sum(int a, int b)
{
     return a+b;
}
Import int sub(int a, int b)
{
     return a-b;
}

最後編譯 MyDLL.cpp,若是成功則在 Debug 裏能夠看到 MyDLL.dll。

提示:

函數聲明前加上 "_declspec(dllexport)" 代表函數將輸出爲動態連接庫,是必不可少的。

在相同的調用約定下,採用不一樣的編譯器,對函數名的修飾是不同的。

例如:C語言和C++語言導出的dll文件中,函數的修飾名是不同的。

若是要C語言風格的(.dll)文件,就要再加上 "extern C" 進行修飾,或者把源文件名的後綴改成(.c)。

若是是要C++風格的(.dll)文件,則源文件名後綴必須爲(.cpp)。

 

調用方式:

隱式調用:

將 MyDLL.lib 和 MyDLL.h 拷貝到須要應用該 DLL 的工程的目錄下,將 MyDLL.dll 拷貝到產生的應用程序的目錄下,

並在須要應用該 DLL 中的函數的 CPP 文件開頭添加以下幾行:

#include"MyDLL.h"
#pragma comment(lib,"MyDLL")

例如:

// MyDLL.cpp#include<stdio.h>
#include"MyDLL.h"
#pragma comment(lib,"MyDLL")
int main(void)
{
    printf("3+6=%d\n",sum(3,6));
    printf("8-6=%d\n",sub(8,6));
    return 0;
}

顯式調用:

一、將 MyDLL.lib 和 MyDLL.h 拷貝到須要應用該 DLL 的工程的目錄下,將 MyDLL.dll 拷貝到產生的應用程序的目錄下,

      在添加 CPP 文件以前一步,須要在 Project->Setting->Link->Object/library modules 的框中增長 MyDll.lib 這個庫。

      最後,在建立的 CPP 文件的開頭添加這一行:

#include"MyDLL.h"

如今就可使用這個 DLL 文件了。

二、將 MyDLL.lib 和 MyDLL.h 拷貝到須要應用該 DLL 的工程的目錄下,將 MyDLL.dll 拷貝到產生的應用程序的目錄下,

      簡單的調用 DLL 文件的 CPP 文件以下:

// MyDLL.cpp#include<stdio.h>
#include<windows.h>
int main(void)
{
    HMODULE hModule;
    typedef int (*pSum)(int a, int b);
    typedef int (*pSub)(int a, int b);
    pSum Sum = NULL;
    pSub Sub = NULL;
    hModule = LoadLibrary("MyDLL.dll");
    Sum = (pSum)GetProcAddress(hModule,"sum");
    Sub = (pSum)GetProcAddress(hModule,"sub");
    printf("3+6=%d\n",Sum(3,6));
    printf("8-6=%d\n",Sub(8,6));
    return 0;
}

介紹一下兩個函數:

LoadLibrary() 介紹:

功能:將指定模塊加載到調用進程的地址空間中。指定的模塊可能會致使加載其餘模塊。

函數原型:HMODULE WINAPI LoadLibrary(

                  LPCTSTR lpFileName // 動態連接庫的名字。

                  );

返回值:若是函數成功, 則返回值是模塊的句柄。若是函數失敗, 返回值爲 NULL。

 

GetProcAddress() 介紹:

功能:從指定的動態連接庫 (DLL) 中檢索導出函數或變量的地址。

函數原型:FARPROC WINAPI GetProcAddress(

                  HMODULE hModule,  // 模塊的句柄。

                  LPCSTR  lpProcName  // 函數或變量的名字, 或函數的序號值。

                  );

返回值:若是函數成功, 則返回值是導出函數或變量的地址。若是函數失敗, 返回值爲 NULL。

 

再來看一下這段代碼:     typedef int (*pSum)(int a, int b);

咱們一般見到的都是:     typedef unsigned Long uLong;

其實 typedef int (*pSum)(int a, int b); 的意思也挺好理解的:

就是定義一個別名爲 pSum 函數指針,指向返回值爲 int 型而且含有兩個 int 型參數的函數指針。


VS2017下:

其實 VS2017 下的步驟和上面也同樣,這裏介紹一下模塊定義文件建立 DLL 文件:

文件->新建->項目->DLL

// MyDLL.cpp#include "stdafx.h"
int sum(int a, int b)
{
    return a + b;
}

int sub(int a, int b)
{
    return a - b;
}

解決方案->資源文件->添加->新建項->代碼->模塊定義文件

// Source.def
LIBRARY

EXPORTS
sum
sub

項目->MyDLL屬性->連接器->輸入->模塊定義文件:Source.def

最後:生成->MyDLL

 

隱式調用和顯式調用的對比:

一、隱式連接方式實現簡單,一開始就把dll加載進來,在須要調用的時候直接調用便可。

     可是若是程序要訪問十多個 DLL 文件,若是都採用隱式連接方式加載他們的話,在該程序啓動時:

     這些dll都須要被加載到內存中,並映射到調用進程的地址空間,這樣將加大程序的啓動時間。

     並且通常來講,在程序運行過程當中只是在某個條件知足的狀況下才須要訪問某個dll中的函數。

     這樣若是全部dll都被加載到內存中,資源浪費是比較嚴重的。

二、顯示加載的方法則能夠解決上述問題,DLL 只有在須要用到的時候纔會被加載到內存中。

     另外,其實採用隱式連接方式訪問 DLL 時,在程序啓動時也是經過調用 LoadLibrary() 加載該進程須要的動態連接庫的。

 

帶有 API 函數的 動態連接庫:

建立方式相同,只是得有個 DllMain() 入口函數。

// Dll.h 
/*
#define Import extern "C" _declspec(dllexport)
Import void Text(void);
*/
#include"Dll.h"
#include<windows.h>
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpRserved)
{
   switch(ul_reason_for_call)
    {
     case DLL_PROCESS_ATTACH:
          Text();
          break;
     case DLL_PROCESS_DETACH:
          break;
     case DLL_THREAD_ATTACH:
          break;
     case DLL_THREAD_DETACH:
          break;
    }
   return 0;
}
Import void Text(void)
{
   MessageBox (NULL, TEXT ("Hello, World!"), TEXT ("HelloMsg"), MB_OKCANCEL);
}

DllMain() 簡介:

功能:動態連接庫 (DLL) 中的可選入口點。

函數原型:BOOL APIENTRY DllMain(
                  HMODULE hModule,  // DLL 模塊的句柄。
                  DWORD  ul_reason_for_call,  // 指示爲何調用 DLL 入口點函數的緣由代碼。
                  LPVOID   lpvReserved // 保留值,一般爲 NULL。
                  );

參數:ul_reason_for_call

含義
DLL_PROCESS_ATTACH 當 dll 文件第一次被進程加載時,調用該值下的 DLL 函數。
DLL_PROCESS_DETACH 當 dll 文件從進程中被解除時,調用該值下的 DLL 函數。TerminateProcess() 除外。
DLL_THREAD_ATTACH 當進程建立一個線程時,新建的線程將調用該值下的 DLL 函數。
DLL_THREAD_DETACH 當線程調用 ExitThread() 結束時,該進程將調用該值下的 DLL 函數。TerminateThread() 除外。

返回值:當系統使用 DLL_PROCESS_ATTACH 值調用 DllMain 函數時,

               若是調用成功則返回 TRUE,不然返回 FALSE。

相關文章
相關標籤/搜索