Windows下靜態庫與動態庫的建立與使用

Windows下靜態庫與動態庫的建立與使用

學習內容:本博客介紹了Windows下使用Visual C++ 6.0製做與使用靜態庫與動態庫的方法。html

————————CONTENTS————————


一.什麼是靜態庫與動態庫?

庫(Library)是能夠複用的代碼,在一些大型項目中經常會用到庫。linux

本質上說,庫是一種可執行代碼的二進制形式,能夠被操做系統載入內存執行。程序員

在前期的課程中,咱們學習了Linux下靜態庫與動態庫的使用,Windows下與之對應:windows

  • 靜態庫:linux下.a文件、windows下.lib文件
  • 動態庫:linux下.so文件、windows下.dll文件

所謂靜態、動態是指連接。回顧一下將一個程序編譯成可執行文件的步驟:app

靜態庫與動態庫的區別就在於,【連接階段】如何處理庫,從而鏈接成可執行程序。函數


1.靜態庫工具

之因此成爲【靜態庫】,是由於在連接階段,會將彙編生成的目標文件.o與引用到的庫一塊兒連接打包到可執行文件中。所以對應的連接方式稱爲靜態連接。學習

試想一下,靜態庫與彙編生成的目標文件一塊兒連接爲可執行文件,那麼靜態庫一定跟.o文件格式類似。其實一個靜態庫能夠簡單當作是一組目標文件(.o/.obj文件)的集合,即不少目標文件通過壓縮打包後造成的一個文件。測試

靜態庫的特色可總結爲:ui

  1. 靜態庫對函數庫的連接是放在編譯時期完成的。
  2. 程序在運行時與函數庫再無瓜葛,移植方便。
  3. 浪費空間和資源,由於全部相關的目標文件與牽涉到的函數庫被連接合成一個可執行文件。

Linux下使用ar工具、Windows下使用lib.exe,將目標文件壓縮到一塊兒,而且對其進行編號和索引,以便於查找和檢索。通常建立靜態庫的步驟如圖所示:


2.動態庫

經過上面的介紹發現靜態庫,容易使用和理解,也達到了代碼複用的目的,那爲何還須要動態庫呢?

爲何須要動態庫,其實也是靜態庫的特色致使。

  • 空間浪費是靜態庫的一個問題。

  • 另外一個問題是靜態庫會對程序的更新、部署和發佈帶來麻煩。若是靜態庫lib.lib更新了,因此使用它的應用程序都須要從新編譯、發佈給用戶(對於玩家來講,多是一個很小的改動,卻致使整個程序從新下載,全量更新)。

動態庫在程序編譯時並不會被鏈接到目標代碼中,而是在程序運行是才被載入。不一樣的應用程序若是調用相同的庫,那麼在內存裏只須要有一份該共享庫的實例,規避了空間浪費問題。動態庫在程序運行是才被載入,也解決了靜態庫對程序的更新、部署和發佈頁會帶來麻煩。用戶只須要更新動態庫便可,增量更新

動態庫特色總結以下:

  1. 動態庫把對一些庫函數的連接載入推遲到程序運行的時期。
  2. 能夠實現進程之間的資源共享(所以動態庫也稱爲共享庫)
  3. 將一些程序升級變得簡單。
  4. 甚至能夠真正作到連接載入徹底由程序員在程序代碼中控制(顯式調用)。

綜上所述,靜態庫與動態庫的不一樣點在於:代碼被載入的時刻不一樣

  • 靜態庫在程序編譯時會被鏈接到目標代碼中,程序運行時將再也不須要該靜態庫,所以體積較大
  • 動態庫在程序編譯時並不會被鏈接到目標代碼中,而是在程序運行是才被載入,所以在程序運行時還須要動態庫存在,所以代碼體積較小
  • 動態庫的好處是,不一樣的應用程序若是調用相同的庫,那麼在內存裏只須要有一份該共享庫的實例。

返回目錄


二.靜態庫的建立與使用


1.靜態庫的建立

1.咱們先來完成準備工做。打開Visual C++ 6.0,點擊左上角/File->New...,選擇須要新建的對象:

選擇Workspaces選項卡,輸入Workspaces name以及Location,新建一個工做區:

本篇博客所用到的Project都建在這個工做區就能夠啦~

2.選中咱們新建的workspace,右擊選擇Add New Project to Workspace...

咱們先嚐試建立靜態庫,所以在Project選項卡選擇Win32 Static Library,並在右側輸入Project name便可完成項目建立。

接下來一路next,一個Win32 Static Library格式的Project就建成了:

3.注意到一個工程下面有兩個文件夾,分別是Source FilesHeader Files。顧名思義,Source Files用來存放源文件,如.cpp等;Header Files則用來存放頭文件。

咱們先來建一個頭文件,存放函數說明。

選中Header Files文件夾,點擊File->New...->Files->C/C++ Header File,並輸入文件名,完成建立。

內容爲:

int statlib_demo(int a, int b);

4.一樣的方法,在Source Files中新建staticlib.cpp,此次須要選中C++ Source File

內容爲:

#include "staticlib.h"
int statlib_demo(int a, int b)
{
    if (a > b)
        return a-b;
    else
        return a+b;
}

就此完成了代碼基本的編寫。

5.選擇這個Project,依次點擊CompileBuild按鈕:

打開Project所在文件夾中的Debug文件夾,能夠看到成功生成lib.lib,這就是咱們須要的靜態庫啦~

返回目錄


2.靜態庫的使用

接下來咱們編寫代碼,對生成的靜態庫進行測試。

1.與前面相似,此次在workspace中新建一個Win32 Console Application的Project:

選擇A "Hello, world!" application.

能夠看到在工做區生成了一個名爲libTest的Project,其中包含一些自動生成的文件和代碼:

2.將剛纔生成的lib.lib拷貝到新建的這個項目文件夾下,以進行後續的調用。

3.修改這個Project中的代碼,完成對靜態庫調用的測試:

libTest.cpp

#include "stdafx.h"
#include "../lib/staticlib.h"  //要注意在這裏添加頭文件的位置
int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    int iRet;
    iRet = statlib_demo(3,5);
    printf("the result of value is %x\n",iRet);
    return 0;
}

StdAfx.h:只須要在#include <stdio.h>下一行加上#pragma comment (lib,"lib")便可。其中,第一個「lib」表明連接一個lib庫,第二個「lib」是這個庫的名字,需根據實際狀況修改。

StdAfx.cpp不須要作改動。

4.完成了以上代碼的編寫和修改,Build這個項目,並Execute Program就能夠了。運行結果以下:

到此爲止,咱們完成了靜態庫的編寫和測試。最關鍵的點就是#pragma comment(lib,"XXX")這句預處理指令了。

靜態連接時,編譯器將函數和過程都編譯到exe文件,且函數的相對位置在連接時已經肯定。多個程序調用同一個函數時,內存中保存多份函數。

返回目錄


三.動態庫的建立與使用


1.動態庫的建立

DLL(Dynamic Linkable Library),動態連接庫,能夠向程序提供一些函數、變量或類。這些能夠直接拿來使用。

DLL中通常定義有兩種類型的函數:導出函數和內部函數。其中:

  • 導出函數能夠被外部程序調用;
  • 內部函數只能在DLL內部使用。

爲了使生成的dll文件能夠被外部程序調用,咱們主要關注導出函數的生成與使用。

從dll中聲明導出函數有兩種方式:

  • (1)用模塊定義(.def) 文件聲明,.def文件爲連接器提供了有關被連接程序的導出、屬性及其餘方面的信息;
  • (2) 用__declspec(dllexport)來聲明函數

下面咱們來詳細瞭解這兩種方式的用法。

1、使用.def文件

1.一樣地,在workspace新建一個Project,選擇爲Win32 Dynamic-Link Library

選擇A simple DLL project.

能夠看到在工做區生成了一個名爲dll_def的Project,其中包含一些自動生成的文件和代碼:

2.修改這個Project中的代碼,完成動態庫的編寫:

dll_def.cpp

#include "stdafx.h"
#include "dll_def.h"
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

int cmp3_demo(int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}

Header Files中新建一個dll_def.h,內容爲:

int cmp3_demo(int a, int b);

其餘已存在的文件不須要作修改。

3.最關鍵的步驟到了!!!——編寫.def文件。

  • DEF文件是一個包含EXE文件或DLL文件聲明的文本文件;
  • 每一個.DEF文件至少必須包含LIBRARY語句和EXPORTS語句

.def文件的規則爲:

  1. LIBRARY語句說明.def文件相應的DLL;
  2. EXPORTS語句後列出要導出函數的名稱。能夠在.def文件中的導出函數名後加@n,表示要導出函數的序號爲n(在進行函數調用時,這個序號將發揮其做用);
  3. .def 文件中的註釋由每一個註釋行開始處的分號 (;) 指定,且註釋不能與語句共享一行。

針對咱們這個Project,須要在Source Files中新建一個dll_def.def文件,內容爲:

LIBRARY DLL_DEF
EXPORTS

cmp3_demo

4.選擇這個Project,依次點擊CompileBuild按鈕,打開Project所在文件夾中的Debug文件夾,能夠看到成功生成dll_def.dll,由此完成了動態庫的生成。

2、使用關鍵字_declspec(dllexport)

下面咱們嘗試使用第二種方法生成dll文件。

1.在workspace新建一個Project,選擇爲Win32 Dynamic-Link Library,具體過程再也不贅述。

2.修改這個Project中的代碼,完成動態庫的編寫:

dll_decl.cpp

#include "stdafx.h"
#include "dll_decl.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

LIB_API int cmp_demo(int a, int b)
{
    if (a < b)
        return a;
    else
        return b;
}

其他已存在的文件中的代碼不須要作修改。

3.這個方法最關鍵的一步!!!——編寫dll_decl.h頭文件。

導出函數的聲明時須要有_declspec(dllexport)關鍵字。具體形式爲:

_declspec(dllexport) <返回類型> <導出函數名> (<函數參數>);

在本Project中,編寫的代碼以下:

#define LIB_API __declspec(dllexport)

LIB_API int cmp_demo(int a, int b);

4.與上文一樣的步驟,選擇這個Project,依次點擊CompileBuild按鈕,打開Project所在文件夾中的Debug文件夾,能夠看到成功生成dll_decl.dll,由此使用第二種方法,完成了動態庫的生成:

返回目錄


2.動態庫的使用

  • 動態連接庫加載分爲靜態加載(隱式連接)和動態加載(顯式連接):
    • 靜態加載方式是在編譯程序代碼時即完成對DLL的加載,應用程序結束時卸載DLL。
    • 動態加載方式是使用用API函數來加載和卸載DLL的。

1、隱式連接

1.在workspace中新建一個Win32 Console Application的Project,名爲dllstaticTest。首先,須要把以前生成的dll文件和對應的lib文件拷到這個文件夾下,以完成後續調用。

2.隱式連接動態庫主要有三種方法:

①將lib文件直接加入新建的這個測試project中。

②在測試程序所在project的StdAfx.h文件中增長#pragma comment (lib,"XXX")實現。

③在本Project下的「project」菜單的「setting」下對話框中選擇這個工程,並在對應的「Link」選項窗口中的「Object/library modules」下增長鬚要導入的lib文件:

3.修改這個Project中的代碼,完成動態庫的測試:

dllstaticTest.cpp

#include "stdafx.h"
#include "../dll_decl/dll_decl.h"

int main(int argc, char* argv[])
{
    int iRet;
    printf("Hello World!\n");
    iRet = cmp_demo(3,5);
    printf("return value is: %x\n",iRet);
    return 0;
}

2、顯式連接

上面介紹的動態庫使用方法和靜態庫相似,屬於隱式連接,編譯的時候指定相應的庫和查找路徑。其實,動態庫還經過調用API,如:LoadLibrary、GetProcAddress、FreeLibrary完成動態調用(即顯式連接)。

應用程序必須進行函數調用以在運行時顯式加載 DLL。爲顯式連接到 DLL,應用程序需經過調用函數完成如下步驟:

  1. 首先調用LoadLibrary()函數加載DLL並獲得模塊句柄;
  2. 使用句柄調用GetProcAddress()函數獲取導出函數指針,使用指針調用導出函數;
  3. 調用FreeLibrary()函數釋放加載DLL。

代碼以下所示:

#include "stdafx.h"
#include <windows.h>
#include "../dll_def/dll_def.h"

int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    //LoadLibrary函數裝載dll
    HMODULE hmod = LoadLibrary("dll_def.dll");
    if(!hmod)
    {
        printf("load library failed\n");
        return 0;
    } 
    typedef int (*LoadProc)(int x, int y);
    //GetProcAddress得到"cmp3_demo"函數地址
    LoadProc Load_proc = (LoadProc)GetProcAddress(hmod,"cmp3_demo");
    if(!Load_proc)
    {
        printf("load function cmp3_demo failed\n");
        return 0;
    }
    //經過函數指針間接調用"cmp3_demo"
    int iRet = Load_proc(3,5);
    printf("the cmp3_demo the value is:%x\n",iRet);
    //釋放dll
    FreeLibrary(hmod);
    return 0;
}

返回目錄


附:參考資料

相關文章
相關標籤/搜索