1. 程式人生 > >C++動態連結庫的製作

C++動態連結庫的製作

輸入函式__declspec(dllimport) 與輸出函式__declspec(dllexport) 有什麼區別呢?我知道他們不同,但差別在哪呢?我用的全是__declspec(dllexport) , __declspec(dllimport)一般在什麼時用呢?說說一般在什麼時分別用到它們?

匯出函式__declspec(dllexport)在dll中用

匯入函式__declspec(dllimport)在要呼叫dll的程式中用 

這是指靜態連線
動態連結就不需要__declspec(dllimport)

很多書都有介紹

_declspec(dllexport) 與__declspec(dllimport) 的使用說明


__declspec(XXXXXX)是windows擴充套件C++的編譯巨集頭

_declspec(dllexport) 

宣告一個匯出函式,是說這個函式要從本DLL匯出。我要給別人用。一般用於dll中 。
省掉在DEF檔案中手工定義匯出哪些函式的一個方法。當然,如果你的DLL裡全是C++的類的話,你無法在DEF裡指定匯出的函式,只能用__declspec(dllexport)匯出類。

__declspec(dllimport)

宣告一個匯入函式,是說這個函式是從別的DLL匯入。我要用。一般用於使用某個dll的exe中 。
不使用 __declspec(dllimport) 也能正確編譯程式碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的程式碼。編譯器之所以能夠生成更好的程式碼,是因為它可以確定函式是否存在於 DLL 中,這使得編譯器可以生成跳過間接定址級別的程式碼,而這些程式碼通常會出現在跨 DLL 邊界的函式呼叫中。但是,必須使用 __declspec(dllimport) 才能匯入 DLL 中使用的變數。

    相信寫WIN32程式的人,做過DLL,都會很清楚__declspec(dllexport)的作用,它就是為了省掉在DEF檔案中手工定義匯出哪些函式的一個方法。當然,如果你的DLL裡全是C++的類的話,你無法在DEF裡指定匯出的函式,只能用__declspec(dllexport)匯出類。但是,MSDN文件裡面,對於__declspec(dllimport)的說明讓人感覺有點奇怪,先來看看MSDN裡面是怎麼說的:

    不使用 __declspec(dllimport) 也能正確編譯程式碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的程式碼。編譯器之所以能夠生成更好的程式碼,是因為它可以確定函式是否存在於 DLL 中,這使得編譯器可以生成跳過間接定址級別的程式碼,而這些程式碼通常會出現在跨 DLL 邊界的函式呼叫中。但是,必須使用 __declspec(dllimport) 才能匯入 DLL 中使用的變數。

extern    "C"   

指示編譯器用C語言方法給函式命名。

在製作DLL匯出函式時由於C++存在函式過載,因此__declspec(dllexport)    function(int,int)    在DLL會被decorate,例如被decorate成為    function_int_int,而且不同的編譯器decorate的方法不同,造成了在用GetProcAddress取得function地址時的不便,使用extern    "C"時,上述的decorate不會發生,因為C沒有函式過載,但如此一來被extern"C"修飾的函式,就不具備過載能力,可以說extern    和   extern    "C"不是一回事。

C++編譯器在生成DLL時,會對匯出的函式進行名字改編,並且不同的編譯器使用的改變規則不一樣,因此改編後的名字會不一樣。這樣,如果利用不同的編譯器分別生成DLL和訪問該DLL的客戶端程式碼程式的話,後者在訪問該DLL的匯出函式時會出現問題。為了實現通用性,需要加上限定符:extern “C”。

但是利用限定符extern “C”可以解決C++和C之間相互呼叫時函式命名的問題,但是這種方法有一個缺陷,就是不能用於匯出一個類的成員函式,只能用於匯出全域性函式。
  LoadLibrary匯入的函式名,對於非改編的函式,可以寫函式名;對於改編的函式,就必須吧@和號碼都寫上,一樣可以載入成功,可以試試看。

解決警告  inconsistent dll linkage

    inconsistent dll linkage警告是寫dll時常遇到的一個問題,解決此警告的方法如下:

    一般PREDLL_API工程依賴於是否定義了MYDLL_EXPORTS來決定巨集展開為__declspec(dllexport)還是__declspec(dllimport)。展開為__declspec(dllexport)是DLL編譯時的需要,通知編譯器該函式是需要匯出供外部呼叫的。展開為__declspec(dllimport)是給呼叫者用的,通知編譯器,該函式是個外部匯入函式。

對於工程設定裡面的預定義巨集,是最早被編譯器看到的。所以當編譯器編譯DLL工程中的MYDLL.cpp時,因為看到前面有工程設定有定義MYDLL_EXPORTS,所以就把PREDLL_API展開為__declspec(dllexport)了。

這樣做的目的是為了讓DLL和呼叫者共用同一個h檔案,在DLL專案中,定義MYDLL_EXPORTS,PREDLL_API就是匯出;在呼叫該DLL的專案中,不定義MYDLL_EXPORTS,PREDLL_API就是匯入。

使用dll的兩種方式

方法一: load-time dynamic linking (隱式呼叫)
  在要呼叫dll的應用程式連結時,將dll的輸入庫檔案(import library,.lib檔案)包含進去。具體的做法是在原始檔開頭加一句#include ,然後就可以在原始檔中呼叫dlldemo.dll中的輸出檔案了。

方法二: run-time dynamic linking (顯示呼叫)
  不必在連結時包含輸入庫檔案,而是在源程式中使用LoadLibrary或LoadLibraryEx動態的載入dll。
  主要步驟為(以demodll.dll為例): 

1) typedef函式原型和定義函式指標。
 typedef void (CALLBACK* DllFooType)(void) ;
 DllFooType pfnDllFoo = NULL ;
2) 使用LoadLibrary載入dll,並儲存dll例項控制代碼
 HINSTANCE dllHandle = NULL ;
 ... 
 dllHandle = LoadLibrary(TEXT("dlldemo.dll"));
3) 使用GetProcAddress得到dll中函式的指標
 pfnDllFoo = (DllFooType)GetProcAddress(dllHandle,TEXT("DllFoo")) ;
 注意從GetProcAddress返回的指標必須轉型為特定型別的函式指標。
4)檢驗函式指標,如果不為空則可呼叫該函式 
 if(pfnDllFoo!=NULL)
 DllFoo() ;
5)使用FreeLibrary解除安裝dll
 FreeLibrary(dllHandle) ;

    使用run-time dynamic linking 比較麻煩,但有它的好處(下面討論)。MSDN中有一篇文章DLLs the Dynamic Way討論使用c的巨集建立一個基類pDll完成以上覆雜的操作,使用時只需定義一個類繼承自類pDll並對類和函式使用巨集。 
 以上兩種方法都要求應用程式能找到dll檔案,Windows按以下順序尋找dll檔案:

   如果系統不能找到dll檔案,將結束呼叫dll的程序並彈出一個“啟動程式時出錯”對話方塊,告訴你“找不到所需的dll檔案-XXX.dll”



一、DLL的建立 

建立專案: Win32->Win32專案,名稱:MyDLL


選擇DLL (D) ->完成.

1、新建標頭檔案testdll.h
testdll.h程式碼如下:

#ifndef TestDll_H_
#define TestDll_H_
#ifdef MYLIBDLL
#define MYLIBDLL extern "C" _declspec(dllimport) 
#else
#define MYLIBDLL extern "C" _declspec(dllexport) 
#endif
MYLIBDLL int Add(int plus1, int plus2);
//You can also write like this:
//extern "C" {
//_declspec(dllexport) int Add(int plus1, int plus2);
//};
#endif



2、新建原始檔testdll.cpp
testdll.cpp程式碼如下:

#include "stdafx.h"
#include "testdll.h"
#include <iostream>
using namespace std;
int Add(int plus1, int plus2)
{
int add_result = plus1 + plus2;
return add_result;
}




3、新建模組定義檔案mydll.def
mydll.def程式碼如下:

LIBRARY "MyDLL"
EXPORTS
Add @1



4、vs2010自動建立dllmain.cpp檔案,它定義了DLL 應用程式的入口點。

dllmain.cpp程式碼如下:
// dllmain.cpp : 定義 DLL 應用程式的入口點。
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
  break;
}
return TRUE;
}

最後,編譯生成MyDLL.dll檔案和MyDLL.lib檔案。



1>------ 已啟動生成: 專案: MyDLL, 配置: Debug Win32 ------

1>  dllmain.cpp

========== 生成: 成功 1 個,失敗 0 個,最新 0 個,跳過 0 個 ==========

 

1>------ 已啟動生成: 專案: MyDLL, 配置: Debug Win32 ------

1>  stdafx.cpp

1>  testdll.cpp

1>  MyDLL.cpp

1>  正在生成程式碼...

1>     正在建立庫 D:\Visual C++\工程\Libaray\MyDLL\Debug\MyDLL.lib 和物件 D:\Visual C++\工程\Libaray\MyDLL\Debug


標準C/C++的DLL編寫

DLL也就是動態連結庫,使用DLL程式設計的好處大家應當都知道了吧,可是怎麼樣來作呢,今天我就來說說。

首先,你要確定你要匯出那些個函式,然後你就在你要匯出的函式名前加上下面一句話:

    // 輸出函式的字首
    #define  DLL_EXPORT   extern "C" __declspecdllexport )

    DLL_EXPORT VOID  ExportFun()
    {
        ...
    }

  是不是很簡單啊。如果你要匯出整個類或者全域性變數,你需要這樣做:

// 輸出類的字首
#define  DLL_CLASS_EXPORT   __declspecdllexport )

// 輸出全域性變數的字首
#define  DLL_GLOBAL_EXPORT   extern __declspecdllexport )

 完成了這些以後,我們就要在主程式中呼叫這些個函數了,用下面的方法:

    HINSTANCE hInst = NULL;
    hInst = LoadLibrary("*.dll");        // 你的DLL檔名

    if (!hInst)
    {
        MessageBox(hWnd,"無法載入 *.Dll ","Error",MB_OK);
    }

    還記得上面我宣告的那個ExportFun()函式嗎?我不能直接得到那個函式,但是可以把那個函式的地址取出來。其實函式地址使用起來和函式是一樣的。只不過,為了使用方便,需要定義一個函式指標的型別。如果要指向上面的那個ExportFun(),則它的函式指標的型別定義如下:

    typedef void (CALLBACK* LPEXPORTFUN)(void)

    之後需要做的是宣告一個指標,然後得到DLL中ExportFun()的地址。GetProcAddress函式的第一個引數是之前得到的DLL的例項控制代碼,後面一個是DLL中那個函式的函式名。
       
    LPEXPORTFUN pFun = NULL;
    LPEXPORTFUN pFun = (LPEXPORTFUN)GetProcAddress(hInst, "ExportFun");

好了,到這裡已經就要大功告成了,還差最後一步,呼叫那個函式:

pFun();

大功告成!!