動態鏈接庫(DLL)總結


  以前的學習筆記,記錄庫的一點學習心得。主要是Windows下的靜態庫和動態鏈接庫,動態鏈接庫只寫了關於非MFC的DLL,比較初級,適合和我一樣的新手看看。有不對的地方請指出,有疏漏的地方請補充,如果您比較閑的話,呵呵,感激不盡。

一:靜態鏈接庫

  靜態庫(static library)將函數和數據編譯進一個二進制文件,通常可以命名為*.lib,編譯器在鏈接過程中,將這些二進制數據復制出來,並與調用庫的其他模塊數據組合在一起,形成最終的可執行文件,等以后使用這個可執行文件時,就不需要這個靜態庫的支持了。

Windows下靜態庫的創建和使用

vs2010新建一個win32靜態庫工程,添加兩個文件。

extern int gVar;
extern int add(int lhs, int rhs);//extern可以省略,因為函數默認作用域是extern
class T
{
public:
    T();
    T(int x, int y);
    int add();
private:
    int a;
    int b;
};

 

#include "../dllTest/staticLib.h"
int gVar = 10;
int add(int lhs, int rhs)
{
    return lhs + rhs;
}

T::T():a(0),b(0) {}
T::T(int x, int y):a(x),b(y) {}
int T::add()
{
    return a+b;
}

我們在庫中聲明了一個變量,一個函數,和一個類。然后build會生成一個staticlib.lib文件。為了測試這個庫文件,我們新建一個win32控制台測試工程Test,在main函數中調用庫,有兩種方式:第一,你可以使用指令#pragma comment(lib,”staticlib.lib”),第二,可以在工程屬性里面添加庫及其路徑,這里我們可以把前面靜態庫工程中的staticLib.hstaticlib.lib拷貝到Test工程目錄下,然后選擇:Property/Linker/General/Additional Library Directories,把staticlib.lib所在目錄添加進來;再選擇Linker/Input/Additional Dependencies,把庫staticlib.lib加進去就可以了。這里我們用第一種方式加入庫,代碼如下:

#include <iostream>
#include "staticLib.h"
#pragma comment(lib,"Debug/staticlib.lib")

int main(int argc, char *argv[])
{
    T t(1,2);
    std::cout<<gVar<<std::endl;
    std::cout<<add(1,2)<<std::endl;
    std::cout<<t.add()<<std::endl;
}
輸出結果:
10
3
3

二:動態鏈接庫

  故名思義,動態鏈接庫只有在需要的時候才會調用,所以相比靜態鏈接庫來說,有效減少程序運行時占用的內存空間。我們先創建一個dll,工程名為:dlllib。新建兩個文件:dllLibDeclare.hdllLibImp.cpp。代碼如下:

#ifndef _dlllibdeclare_h_
#define _dlllibdeclare_h_

#ifdef DLL_API
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

DLL_API int gVar;
DLL_API int add(int lhs, int rhs);
class DLL_API T
{
public:
    T();
    T(int x, int y);
    int add();
private:
    int a;
    int b;
};
#endif

 實現文件:

#include "dlllibdeclare.h"
#define DLL_API
int gVar = 10;

int add(int lhs, int rhs)
{
    return lhs + rhs;
}

T::T():a(0),b(0) {}
T::T(int x, int y):a(x),b(y) {}
int T::add()
{
    return a+b;
}

編譯之后,會生成一個dlllib.lib文件和一個dlllib.dll文件(此文件雖然也是以.lib結尾,但與上面提到的靜態鏈接庫還是有很大差別的,套用一句廣告詞,不是所有的lib都叫靜態庫)。這個文件只是包含了相應的dll的接口說明,並不包含實際的代碼,從文件大小就可以看出。頭文件里面定義了DLL_API這個宏,是為了方便以后調用這個dll的時候,直接利用此頭文件,而不用再次聲明我們導出的這些數據單元(因為沒有預定義DLL_API這個宏的時候是導入)。說白了,就是方便多個工程重復利用。我們用vs2010自帶的工具dumpbin查看dll里面導出的內容是什么?選擇tool/visual studio command prompt,在命令行窗口輸入如下命令:

   我們可以看到,導出了5個單元,對應這類T的構造函數,拷貝構造函數,類的add成員函數,還有add函數和gVar整型變量。其中由於有2個同名的add函數,不太好辨認到底哪個是類的成員,哪一個是獨立的類外的函數?不要緊,我們只要導出函數聲明和最終的dll輸出名之間的映射關系文件就ok了。在工程dlllibproperties/Linker/Debugging里面設置輸出map信息就可以查看了:

   在Debug模式下重新編譯dlllib,會生成map.txt文件,打開這個文件,在文件最后可以看到如下內容:

 

這里清楚的指明了函數的對應關系。好了,下面我們調用這個dll做一下測試,新建一個工程:dllTest。代碼如下:

#pragma comment(lib,"Debug/dlllib.lib")
__declspec(dllimport) int __stdcall add(int lhs, int rhs);
int __declspec(dllimport) gVar;
class __declspec(dllimport) T
{
public:
    T();
    T(int x, int y);
    int add();
private:
    int a;
    int b;
};
int main(int argc, char *argv[]) 
{
    T t(1,2);
    std::cout<<gVar<<std::endl;
    std::cout<<add(1,2)<<std::endl;
    std::cout<<t.add()<<std::endl;
}

   這里我沒有用#include “dllLibDeclare.h”,而是自己聲明了dll中的函數和數據,主要是為了說明__stdcall的問題,這個小地方折磨了我很久,所以拿出來強調一下。注意沒有,我在聲明外部add函數的時候,指明了函數的調用方式是__stdcall,如果沒有這個,編譯會報鏈接錯誤的。因為,在win32中,dll的調用方式默認是WINAPI(也就是__stdcall)的,而對於一般的c/c++工程,函數調用方式是__cdecl的,這兩者的差別在於:__stdcall方式與__cdecl對函數名最終生成符號的方式不同,若采用C編譯方式(c++中需將函數聲明為extern “C”)__stdcall調用約定在輸出函數名前面加下划線,后面加”@”符號和參數的字節數,形如_functionname@number;而__cdecl調用約定僅在輸出函數名前面加下划線,行如_functionname。而對於C++編譯方式,這兩者的差別與C的又會有區別,詳見:http://baike.baidu.com/view/1276580.htm

  所以,我們把握一個原則,前后對應。為了防止自己聲明時的調用類型與dll原型不一致,我們調用時,最好以包含同一個頭文件的方式進行聲明,而且為了便於被其它語言程序調用,約定調用方式為__stdcall。新的”dllLibDeclare.h”中,我們這樣寫:

DLL_API int __stdcall add(int lhs, int rhs);

而調用dll時,包含這個頭文件即可:

#include "dlllib/dlllibdeclare.h"
#pragma comment(lib,"Debug/dlllib.lib")

   模塊定義文件:上面的dll中我們利用__declspec(dllimport)指明導出的數據單元,其實也可以利用模塊里定義導出數據。我們新建一個文件”set.def”,可以在工程中新建,也可以手動在別處新建。手動新建的可以通過在properties/Linker/Input中設定,如下圖:

 采用模塊定義這種方式時,Def文件應該是不支持類的導出(有待考證),我們只導出一個變量和函數:

 int gVar = 10; int __stdcall add(int lhs, int rhs) { return lhs + rhs; }

Def的內容為:

 

;set dll
LIBRARY dlllib
EXPORTS
add @1
gVar DATA

 

 

  其中以分號開頭的行代表注釋,且不能與其它內容同行。LIBRARY指明要輸出的庫的名稱,EXPROTS下面是具體要輸出的數據單元,add @1表示輸出的add函數的序號是1,這個序號在顯示調用dll的時候用的上,gVar DATA表示輸出一個全局變量。下面我們就給出隱式調用這個dll的測試代碼:

#include <iostream>
#pragma comment(lib,"Debug/dlllib.lib")
extern _declspec(dllimport) int gVar;
extern _declspec(dllimport) int __stdcall add(int lhs, int rhs);
int main(int argc, char *argv[]) 
{
    std::cout<<gVar<<std::endl;
    std::cout<<add(1,2)<<std::endl;
    gVar = 2;
    std::cout<<gVar<<std::endl;
}

 也可以顯示調用:

#include <iostream>
#include <windows.h>
Int main(int argc, char *argv[])
{
    HMODULE hin;
    char gvar[] = "gVar";
    char add[] = "add";
    typedef int (__stdcall *padd)(int, int);//don't forget to set __stdcall
    hin = LoadLibrary(L"Debug/dlllib.dll");
    if(NULL == hin) {
        std::cout<<"can't load the dll file!"<<std::endl;
        return 0;
    }
    padd addfunc = (padd)GetProcAddress(hin,MAKEINTRESOURCEA(1) /*add*/ /*"add"*/);//three different way to find the function
    int *pvar = (int*)GetProcAddress(hin,gvar);
    std::cout<<addfunc(1,2)<<std::endl;
    std::cout<<*pvar<<std::endl;
    FreeLibrary(hin);
}

Ok,Windows下面的mfc dll就是這樣的。這樣的dll也可以被其他語言調用,這里給一個在perl里面調用的例子:

 perl調用dll的例子:

use strict;
use Win32::API;
my $dllPath = "./dlllib.dll";
my $func = new Win32::API($dllPath,"int __stdcall add(int lhs, int rhs)");
if(not defined $func)
{
    die"can't import api\n";
}
my $sum = 0;
$sum = $func->Call(3,5);
print "$sum\n";

 好吧,先寫到這里,以后寫篇Linux下的庫學習心得。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM