VC環境下DLL接口申明的三種方式

本文將介紹三種提供DLL接口的方式(對於如何編寫DLL內部的代碼不做介紹),由於筆者工作時間不長,難免有疏漏的之處,還請各位大俠不吝賜教,謝謝!

方案一:個人認爲算是比較“循規蹈矩”的一種

在DLL中寫好接口的實現代碼後,然後提供一個申明接口的頭文件供調用者使用,我想一般都會這樣寫:

第一步:定義需要的宏(後面會介紹該宏的作用)

#ifdef __DLLNAME_XX 

#define _XX_LOADDLL     extern "C" _declspec(dllexport)

#else       //__DLLNAME_XX

#define _XX_LOADDLL     extern "C" _declspec(dllimport)

#endif     //__DLLNAME_XX

 

第二步:定義DLL的名稱,個人覺得字符串常量比較好

// 主要給調用該DLL的模塊使用,應採用自己的命名規則,避免與其它的DLL重名

Const THAR strYourDllName[] = _T("XXX.dll"); // DLL的名稱

 

第三步:申明接口

_XX_LOADDLL  bool InterfaceA(LPCTSTR/*[in]*/);

typedef  bool (*LP InterfaceA)(LPCTSTR/*[in]*/);

//其它的接口聲明

 

定義宏__DLLNAME_XX的作用

如果我們在DLL的工程設置中添加了__DLLNAME_XX的定義,這樣DLL的工程由於定義了__DLLNAME_XX,則_XX_LOADDLL的值爲extern "C" _declspec(dllexport),即接口_XX_LOADDLL  bool InterfaceA(LPCTSTR/*[in]*/);將被展開爲extern "C" _declspec(dllexport)  bool InterfaceA(LPCTSTR/*[in]*/);

它正好表示爲一個“導出函數”,而調用該DLL的工程由於沒有定義__DLLNAME_XX,則_XX_LOADDLL的值爲extern "C" _declspec(dllimport),即在外部該接口將被展開爲

extern "C" _declspec(dllimport)  bool InterfaceA(LPCTSTR/*[in]*/);

而它表示該接口爲一個“導入接口”,這樣是不是很巧妙呢?

 

溫馨提示

在接口申明的頭文件中最好不要出現如CString等只有在MFC中才會出現的變量類型,儘量用通用的變量類型,例如Cstring用LPCTSTR代替等,這樣VB的程序也能調用你的接口,如果用CString的話,VB的程序就識別不了了。

 

方案二:採用類的封裝,也是個人覺得比較好的方式

此方案提供給調用者的不是一個接口申明頭文件,而是一個類(一般是兩個文件,一個.cpp和一個.h),該類中封裝了DLL中的所有接口的實現,通過這種方案,調用者就可以像使用一個普通的類一樣來使用你的DLL的接口了,是不是覺得很方便呢!呵呵,下面詳細介紹這種方案的實現步驟。

第一步:定義自己的DLL封裝類

名字暫且稱爲CDll吧,先介紹頭文件,頭文件一般需要這樣寫:

Class CDll

{

Public:

  // 構造函數

CDll();

// 析構函數

  Virtual ~CDll();

 

  Public:

        // 如下兩個DLL是必須的

  BOOL Init();             // DLL的初始化,負責DLL的加載,接口的導入

  BOOL UnInit();     // 釋放DLL

 

  public

// 接口

Bool InterfaceA();  // 該接口中調用DLL中對應的接口

// 其它接口

  Private:

       HMOUDLE m_handle;         // DLL 的句柄

LP InterfaceA  m_pfn InterfaceA; // 假設DLL有這樣一個接口

// 其它接口

}

幾個需要注意的地方:

Ø         CDll中必須要提供DLL中所有接口的調用,而且必須是一對一的方式,不要去改動DLL的調用邏輯,CDll只是提供一個簡單接口調用的過渡而已。

Ø         CDll中成員函數的名稱應儘量和DLL接口函數的名稱類似,這樣看起來比較統一,可讀性好。

 

寫完頭文件後,就讓我們實現.cpp文件吧:

Ø         構造函數

CDll()::CDll()

{

    m_handle = NULL;          // 句柄置爲空

    m_pfn InterfaceA = NULL;    // 接口指針初始化爲空

   // 其它初始化工作

}

Ø         析構函數

CDll()::~CDll()

{

If ( !m_handle )

{

    UnInit();     // 釋放DLL,防止調用者忘記釋放DLL

}

// 其它內存的清理工作

}

Ø         BOOL Init ()  初始化函數的寫法

{

If ( m_handle )

{

    Return TRUE;

}

m_handle = ::LoadLibrary(_T(“CDll.dll”));    // 加載DLL

If ( !m_handle )  

{

              Return FALSE;  // DLL加載失敗,直接返回

}

m_pfn InterfaceA =( LP InterfaceA)::GetProAddress(m_handle, _T(“接口名”);

// 其它接口按同樣的方式進行初始化

Return TRUE;

}

Ø         BOOL UnInit ()  反初始化函數的寫法

{

If ( m_handle )  

{

    ::FreeLibrary(m_handle);

m_pfn InterfaceA = NULL;

// 其它接口指針也置爲空

}

Return TRUE;

}

Ø         Bool CDll::InterfaceA()   // 終於到接口的實現了,呵呵

{

If ( Init() ) // 主要是爲使用者提供方便,不用事先調用Init也能直接使用接口

{

    If (m_pfnInterfaceA )

    {

         Return  m_pfnInterfaceA();

}

}

Return FALSE;

}

Ø         其它接口的實現,參照CDll::InterfaceA()的實現方法就可以了

到這裏,我們的接口類就全部寫完了,調用者要使用DLL的接口就非常的方便了,例如要使用DLL的_XX_LOADDLL bool InterfaceA(LPCTSTR/*[in]*/);接口,現在只用這樣就可以了:

CDll dll;

dll. InterfaceA();

是不是覺得很方便呢??呵呵…而且這種方式實現的類是一個可以重用的類,充分體現了面向對象中代碼重用的思想。

 

方案三:個人覺得技巧性比較高,可以把接口隱藏的很好

和方案一一樣,需要提供一個頭文件,不管DLL有多少個接口,對外開放的接口卻只有一個,但是調用者可以訪問DLL的所有接口,是不是覺得很玄乎?呵呵,不賣關子了,直接進入主題。頭文件一般需要像這樣寫:

第一步:請參看“方案一”的第一步

第二步:請參看“方案一”的第二步

第三步:申明(內部)接口

typedef  bool (*LPInterfaceA)(LPCTSTR/*[in]*/); // 不用extern “C”…了

// 其它接口的申明

注意這些接口必須在DLL代碼中實現。

第四步:定義接口集結構體(也可以定義爲類)

typedef struct INTERFACE

{

   LPInterfaceA m_pfnInterfaceA; // 接口地址

   // 其它接口的定義

   Void  Init()

   {

      m_pfnInterfaceA = NULL;

      // 其它接口也要初始化爲NULL

}

 }INTERFACE,*LPINTERFACE;

第五步:申明對外的唯一接口

_XX_LOADDLL  bool LPGetAllInterface(INTERFACE* pInterface);

typedef  bool (*LPGetAllInterface)( INTERFACE* pInterface /*[in/out]*/);

 

接口LPGetAllInterface必須完成pInterface各成員的初始化工作,比較推薦的方式是:在DLL內部將LPInterfaceA (LPCTSTR/*[in]*/)定義爲全局變量,而LPGetAllInterface接口就可以這樣實現

LPGetAllInterface( INTERFACE* pInterface /*[in/out]*/)

{

m_pfnInterfaceA = LPInterfaceA; // 函數名就是代表函數起始地址

// 其它接口的初始化

}

至此,整個頭文件就寫完了,調用者就可以這樣調用DLL了,

INTERFACE interface;

interface.Init();

LPGetAllInterface lpGetAllIterface;

lpGetAllInterface(&interface);

if ( interface. m_pfnInterfaceA)

{

    interface.m_pfnInterfaceA(lpszText); // 調用LPInterfaceA接口

}

此方案是受到COM編程思想的啓發,這樣一來,對外界來說,DLL永遠只有一個不變的接口,在某些場合,這是非常重要的,而且這種方法可以在不改變接口的情況下增加新的接口,新的功能,雖然這句話聽起來有點拗口,但卻是一種很好擴展DLL功能的實現方案。

 

總結

第一種方案是一種比較傳統的方式,而第二種方式充分體現了面向對象的編程思想,實現了代碼的重用,而第三種方式個人持“中立”觀點,一般不會用到它,我之所以把它也寫出來,是希望我們的開發思路不要一直走直線,偶爾轉轉彎,也許你真的能發現一條捷徑!

發佈了13 篇原創文章 · 獲贊 8 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章