总结下C++中模块(Dll)对外暴露接口的方式:

  (1)导出API函数的方式

  这种方式是Windows中调用DLL接口的基本方式,GDI32.dll, User32.dll都是用这种方式对外暴露系统API的。

  这种方式的优点是导出函数没有语言限制,什么语言都能调用;

  缺点是这种方式是面向过程的,外部如果要支持多实例等不是很方便,另外它要求的回调函数(callback)只能是普通C函数,C++中我们通常用类静态成员函数,很不方便。

  当然,我们通过封装其实也可以让这种方式支持多实例,通过一个抽象句柄HComponent,比如支持导出函数HComponent CreateInstance();VOID DeleteInstance(HComponent h);然后内部的其他导出函数的第一个参数都是实例句柄,类似INT SendMessage(HComponent h, ...),用这种方式可以模拟出面向对象的效果。

  另外如果用动态加载(LoadLibrary,GetProcAddress)的方式调用它的导出函数,即使导出函数内部实现修改了,外部程序也不用重新编译,仍然可用。

  导出函数方式一个比较的例子是GDI+的实现,整个GdiPlus.dll对外提供的都是普通导出函数,但是它却可以方便的给面向对象的语言使用,因为一方面它用Handle的方式在DLL内部封装了对象,另一方面它在DLL外围又用C++类的方式封装了头文件直接提供给用户,所以C++程序可以直接以面向对象的方式调用。

  (2)导出类方式

  导出类的方式是把整个C++类对外导出,MFC42.dll是这种方式。

  这种方式的优点是直接面向对象。

  缺点是只能给C++用,而且好编译器都要一致,另外DLL一变动,外部程序需要重新编译,而且外部程序可以通过头文件看到你类的内部实现,所以这种方式是不建议使用的方式。

  (3)COM方式

  COM方式实际上导出了几个固定函数(DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer), 然后以这几个函数为入口,调用组件内部‘实现的接口。

  COM方式综合了上面2种方法的所有优点,没有语言限制,面向对象,多实例,只能看到接口,动态升级等。

  当然COM因为其复杂性和对注册表的依赖,很多时候我们在封装模块时不愿意严格按照COM标准来实现,但是我们可以按照COM思想来提供接口。

  比如我们可以让我们模块只提供一个导出函数CreateFactory, 然后外部可以调用该接口来创建工厂,后通过工厂创建出各种类型的对象,这些对象实现了某些接口,外部只需要这些接口的头文件即可调用对象的方法。

  现在越来越多的组件以这种方式对外提供接口,比如D2D对外的导出接口是D2D1CreateFactory,然后可以通过该工厂来创建其他的对象,比如pD2DFactory->CreateHwndRenderTarget(...),后可以直接调用对象实现的接口:pRenderTarget->DrawRectangle(D2D1::RectF(100.f, 100.f, 500.f, 500.f), pBlackBrush);

  当然,上面几种DLL对外暴露接口的方式本质上没有区别,都是利用PE文件的导出节来导出数据和函数,但是根据它们使用方式的不同,对外部模块来说还是有很大的区别,我们的推荐次序依次是:COM方式->导出API函数方式->导出类方式。