C++編寫DLL的方法

RascallySnake發表於2013-07-10

      在寫C++程式時,時常需要將一個class寫成DLL,供客戶端程式呼叫。這樣的DLL可以匯出整個class,也可以匯出這個class的某個方法。

一、匯出整個class

方法很簡單,只需要在類的標頭檔案中class和類名之間加上_declspec(dllexport),同時在另外一份提供給客戶端呼叫程式使用的類的標頭檔案中class和類名之間加上_declspec(dllimport)。為了能讓客戶端程式和DLL程式公用該類的一份標頭檔案,通常在類的標頭檔案中使用巨集和預編譯指令來處理。如下DLLTest.h:

#ifdef  DLL_TEST_API
#else
#define DLL_TEST_API _declspec(dllimport)
#endif

Class DLL_TEST_API  CDLLTest
{

         Public:
                  CDLLTest();

                  ~CDLLTest();

                  int Add(int a, int b);

};

 DLLTest.cpp如下:

#define DLL_TEST_API     _declspec(dllexport)

#include “DLLTest.h”

………………………………………

 這樣,在DLL編譯時DLL_TEST_API被定義為_declspec(dllexport),而且客戶端程式編譯時它被定義為_declspec(dllimport)。

二、匯出這個類的某個或者某幾個方法。

這時,需要將_declspec(dllexport)放到成員函式名前,如DLLTest.h:

#ifdef DLL_TEST_API
#else
#define DLL_TEST_API _declspec(dllimport)
#endif

Class CDLLTest
{

         Public:
                      CDLLTest();
                      ~CDLLTest();
                      int  DLL_TEST_API  Add(int a, int b);

};

 

但是,如果僅僅是這樣的話,當客戶端程式#include這個標頭檔案後,定義DLLTest這個類的一個物件後(靜態方式連結DLL),客戶端程式無法連結通過,會提示建構函式和解構函式無法解析,此時,需要將建構函式和解構函式前也加上DLL_TEST_API巨集即可。

當然這裡還有個問題就是類的函式在匯出後,名字會發生變化,我們可以在函式名前再加上extern “C” ,如 extern “C” DLL_TEST_API int Add(int a ,int b);但這隻解決了C與C++呼叫時名字變更問題,可靠的方法還是增加一個模組定義檔案def,在該檔案中定義匯出函式的名稱,我們將在後面看到樣例。

DLL編寫完成後,就只剩下客戶端程式如何去呼叫該DLL了,靜態方式呼叫DLL和動態方式呼叫DLL。

一、靜態方式呼叫DLL

這個方法就簡單了,將DLLTest.h標頭檔案和DLLTest.lib,DLLTest.dll檔案拷貝到客戶端程式的當前目錄下,在客戶端程式中#include<DLLTest.h>,然後通過#pragma comment(lib,”DLLTest.lib”)的方式引入lib庫,或者在客戶端程式的工程屬性裡面增加對該lib檔案的引入。

然後就可以在客戶端程式中如同使用本地的一個class一樣使用該DLL了,如:

CDLLTest dllTest;

dllTest.Add(1,2);

 

二、動態方式呼叫DLL

動態呼叫這個DLL,就需要對這個class進行修改了。

首先,在DLLTest.cpp檔案中增加一個全域性函式,該函式可以返回這個class的一個例項,這樣,客戶端程式呼叫這個全域性函式後,得到該class的例項,就可以呼叫該class的例項方法了。

extern “C”  _declspec(dllexport)  CDLLTest*  GetInstance()
{
         return new CDLLTest;
}

 注:extern “C” 只是解決了c與c++編譯器之間的相容問題,如果需要和其他編譯器之間相容,可靠的辦法還是增加一個.def檔案,檔案內容如下:

LIBRARY  “DLLTest”

EXPORTS

GetInstance = GetInstance

 這樣就指定了DLL的函式匯出後的名稱仍然不變。

這樣,客戶端程式就可以通過該函式來獲取class的一個例項了。如下:

先需要定義一個函式指標型別:

typedef  CDllTestBase*  (*pfGetInst)();

//注:CDllTestBase類後面會介紹。

HMOUDLE hMod = LoadLibrary( _T(“DLLTest.DLL”) );

if(hMod)
{
        pfGetInst  pfGetInstance  =  (pfGetInst)GetProcAddress(“GetInstance”);
        if( p )
        {
            //通過基類指標指向派生類物件
              CDllTestBase * pInst = pfGetInstance ();
if( NULL
!= pInst )
{ pInst
->Add( 1,2);
}    if( NULL
!= pInst ) { //釋放物件 delete pInst;
} }
}

      當然,這裡還是需要include這個DLL的標頭檔案DLLTestBase.h,如果將之前所寫的標頭檔案DLLTest.h直接拷貝到客戶端程式的當前目錄下,並include進來的話,在編譯連線時,是無法通過的,我們需要對這個標頭檔案進行修改,首先增加一個.h 檔案DLLTestBase.h,在這個檔案中我們將需要在客戶端程式中呼叫的函式都命名成純虛擬函式,然後讓CDLLTest類繼承自CDLLTestBase類,DLLTestBase.h如下:

Class  CDLLTestBase
{
      Public:
                Virtual  ~CDLLTestBase(){};//虛解構函式,且為行內函數
                   Virtual int Add(int a, int b) = 0;

}

 DLLTest.h修改後如下:

#include “DLLTestBase.h”
Class CDLLTest  :  public  CDLLTestBase
{
     Public:
                CDLLTest();
                ~CDLLTest();
                int  Add(int a, int b);
};

注:這裡的DLLTestBase需要提供一個虛解構函式,這樣在客戶端程式中就可以通過基類指標來釋放派生類物件了。

      這樣,只需要將DLLTestBase.h拷貝到客戶端程式的當前目錄下,然後在客戶端程式中#include”DLLTestBase.h”,就可以如上面介紹一樣在客戶端程式中呼叫DLL裡面的方法了。

        

相關文章