昨天,朋友再写一个控件给客户使用,问我一个问题: 怎么在动态链接库中导出类?(不使用MFC,因为感觉那个东西实在很烦人!)呵呵,说实在,我还没有这方面的经验。以前给客户用的控件一般都是做一堆函数在控件之中,然后进行“封装”起来,再扔给客户使用。当然很顺利,只要加一个.def文件,任何语言皆可以调用你的动态库,这样有什么不好吗?当然,我并不认为它有什么不好之处,而且,也能绕过“class”这个机制,让自己编译出来的代码量会更小,更精致。是的,这一切看起来都很好,但是,如果在你控件之中有很多函数供用户使用(想一想DirectX)——也就是说你的一个控件有很多个功能块,并且,这些功能块可以按着一定形式划分,每一个功能块都有一些函式供外界使用,而外部调用的时候只需要用 功能块->函数 调用,好的,来点例子:比如你有两个功能块分别是飞机(Airplane)和 汽车(Car),这两个功能块都要向外界提供一个启动引擎的方法(Startup),但是,这个方法在Airplane 和 Car中所作的动作肯定是不一样的,但是为了让你的调用更加自然,呵呵,面向对象!是的,这一点我们不用再讨论了,有时,我们的确需要那样做。
让我们回到主题,怎样将控件中类的方法供给别人,呵呵,我很惯性思维,首先想到将类的方法加到.def 之中。是的,没有问题:看看下面的代码:
Airplane.h
Class Airplane
{
Public:
int Startup(void *list);
int Stop ();
…
Private:
//……
};
然后,我们开始实现类在我们另一个Airplane.cpp
Airplane.cpp
。。
Int Airplane:: Startup(void *list){//};
Int Airplane:: Sop//(){};
。。。
是的,我们不得不给外部增加一个方法,也就是说要导出我们的类
于是,我们在Airplane.h 中增加一个方法声明
Airplane.h
extern "C" __declspec(dllexport) HANDLE CreateClass ();
extern "C" __declspec(dllexport) int ReleaseClass(HANDLE h_class);
实现它:
Airplane.cpp
HANDLE CreateClass ()
{
Airplane *tmp =NULL;
tmp = new Airplane;
return HANDLE(tmp)
}
int ReleaseClass(HANDLE h_class)
{
If(!h_class) return 0;
delete h_class;
return 1;
}
别忘了,在.def 文件中加入导出的方法名如下:
Exports
CreateClass @ 1
ReleaseClass @ 2
Startup @ 3
Stop @ 4
…..
是的,好像没有问题,一切okay! 且慢,如果,我还打算在上述控件中再加入另一个类Car ,呵呵,而且,在Car 之中还有一个Startup 和 Stop 的方法,糟糕!我该怎样定义我的.def文件?事情看起来陷入了僵局。Com, 我突然想起来了com,(其实我对com知之甚少,只是略有了解),directx之类的东西提供给外部通常是接口,也就是说我们通常获得的是接口,然后,再通过接口去调用,是的,没错!比如,我们总是这样:
DirectDrawCreateEx(NULL,(void**)&lpdd,IID_IDirectDraw7,NULL);
获取到lpdd 接口,然后,再调用这个接口的一些方法。看来,事情有了转机,让我们看看下面的代码:
Engine.h
__interface IAirplaneEng
{
Virtual int _stdcall Startup(void *list) =0;
virtual int _stdcall Stop () =0;
virtual int _stdcall Realse ()=0;
};
__interface ICarEng
{
Virtual int _stdcall Startup(void *list) =0;
virtual int _stdcall Stop () =0;
virtual int _stdcall Realse ()=0;
};
Class CAirplane
{
private:
virtual int _stdcall Startup(void *list);
virtual int _stdcall Stop ();
virtual int _stdcall Realse ();
…
Private:
//……
};
Class CCar
{
private:
virtual int _stdcall Startup(void *list);
virtual int _stdcall Stop ();
virtual int _stdcall Realse ();
…
Private:
//……
};
我们可以定义一个统一的方法,来创建和释放我们的接口,在Engine.h 中加入如下代码:
#define IID_ IAirplaneEng 1
#define IID_ ICarEng 2
extern "C" __declspec(dllexport) HRESULT CreateInstance(DWORD iid_num,void **iface);
让我们在Engine.cpp 中加入如下代码,主要是实现CreateInstance,和类的Realse()
HRESULT CreateInstance(DWORD iid_num,void **iface)
{
Switch(iid_num)
{
Case IID_ IAirplaneEng:
*iface = (IAirplaneEng *)new CAirplane;
Break;
Case IID_ ICarEng:
*iface = (ICarEng *)new CCar;
Break;
Default: break;
}
Return S_OK;
}
Void CAirplane::Release()
{
// Release resource….
Delete this;
}
Void CCar::Release()
{
// Release resource….
Delete this;
}
那么外部怎么样调用你的东西呢?其实很简单了,一段简短的代码如下:
#include “Engine.h”
Int main()
{
IAirplaneEng *p_IAirplane =NULL;
ICarEng * p_ICar =NULL;
If(!CreateInstance(IID_ IAirplaneEng,(void **) &p_IAirplane))
{//error information…..}
p_IAirplane->Starup();
p_IAirplane->Sop();
p_IAirplane->Release();
If(!CreateInstance(IID_ ICarEng,(void **)& p_ICar))
{//error information…..}
p_ICar ->Starup();
p_ICar ->Sop();
p_ICar ->Release();
Return 0;
}
好了,搞定!然后,你就可以将你的动态库和头文件提供给别人使用了,在头文件类不会暴露你类的任何细节,实现了有效的封装。当然上述也有不完美之处,最好在自己的类中提供类似于 com 的 AddRef 和 QueryInterface 的方法,也可以为每一个接口生成唯一的 guid 号,天哪,那不就是 com 了吗!呵呵。。