现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

VC环境下DLL接口申明的三种方式

2015-07-30 13:47 工业·编程 ⁄ 共 3912字 ⁄ 字号 暂无评论

方案一:个人认为算是比较“循规蹈矩”的一种

在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功能的实现方案。

总结

第一种方案是一种比较传统的方式,而第二种方式充分体现了面向对象的编程思想,实现了代码的重用,而第三种方式个人持“中立”观点,一般不会用到它,我之所以把它也写出来,是希望我们的开发思路不要一直走直线,偶尔转转弯,也许你真的能发现一条捷径!

给我留言

留言无头像?