65.9K
CodeProject 正在变化。 阅读更多。
Home

设计 C/C++ 项目中的 DLL 接口

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2012 年 8 月 16 日

CPOL

5分钟阅读

viewsIcon

62507

downloadIcon

2573

一些建议,使您的 DLL 接口更具吸引力且更易于维护,即使是在跨平台项目中

引言

本技巧假定您熟悉 DLL 的使用以及静态/动态链接等相关术语。如果您不知道如何使用 DLL,请在学习了 此处(跨平台维基百科页面)、此处(Windows)或 此处(Unix:Linux、BSD、OSX 等)的 DLL 相关基础知识后再阅读本技巧。

我的建议是,除非万不得已,否则请不要在项目中使用 DLL,因为它们会带来麻烦。如果您仍然不得不将部分 C/C++ 代码放到 DLL 中(或者您需要创建插件接口),并且由您来设计 DLL 接口(该接口仅供您的 C/C++ 代码使用),那么请继续阅读。随附了一个极简的压缩项目文件,其中包含 Visual C++ 2010 解决方案和 Unix 构建脚本,用于演示在 Windows 和 Unix(Linux/BSD/OSX)平台上的插件(DLL / 共享库)加载。

我心目中的 DLL 接口应该是什么样的?

首先,在 DLL 的公共头文件中声明一个接口(一个只有纯虚方法的类)。只使用纯虚方法非常重要!然后,将 DLL 中唯一导出的函数设置为返回指向该接口的指针。之后,在 DLL 的私有源文件中,您可以从该接口派生一个类并实现其方法。现在,DLL 的用户只需调用 DLL 的接口获取方法,然后通过调用检索到的接口指针的方法,以优雅的 C++ 方式继续使用它。

别忘了阅读随附的示例源代码。它非常简单且简短。

为什么这样做很好?

  • 它看起来就是更美观。
  • 由于您的 DLL 和使用它的模块之间的链接接口非常精简——只有一个导出的 DLL 函数——因此很容易实现跨平台,因为丑陋的 ifdef 和平台依赖代码极少。精简的接口也使得实现平台无关的延迟加载变得容易。
  • 如果您决定重构,那么将此 DLL 代码合并到您的应用程序中将非常容易——轻而易举。
  • 假设您正在为程序编写插件接口,那么通过在程序内部实现 DLL 接口,您可以轻松地将内部插件放入可执行文件中。这样,您就可以轻松地在内置功能和 DLL 实现的功能之间切换,对于可执行文件而言,这两种功能都只是一个简单的接口指针。

版本化插件 DLL(我的风格)

有很多方法可以检测插件的版本。例如,通过将版本编码到 DLL 的 versioninfo 资源中,将版本编码到文件名中,或者通过调用 DLL 导出的版本获取函数来获取版本……我使用一种完全不同的方法,该方法与前面列出的方法相比有几个优点。

假设我有一个媒体播放器程序和一个用于它的特定接口的编解码器插件。一段时间后,我发布了一个新版本的媒体播放器,它具有修改后的接口,但我决定也为旧版本的媒体播放器发布该插件。我通过编写单个 DLL 来做到这一点,该 DLL 同时支持旧版本和新版本的插件接口。我的插件 DLL 的公共接口头文件将包含旧版本和新版本接口的声明,并且包含两个导出的函数:一个返回旧接口指针,另一个返回新接口指针。导出的方法当然有不同的名称:GetMediaPlayerInterface()GetMediaPlayerInterface2()。DLL 实现两个接口,并且可以在内部使用两个实现之间的大量共享代码,我只需要维护一个项目。

我有一段代码可以在不实际加载 DLL 的情况下检测 DLL 是否导出了某些函数。这段代码很有用,原因有很多:如果你必须加载一个 DLL 才能确定它是否是你的插件,那么在调用 LoadLibrary() 时,库的 DllMain() 可能会在你的进程中执行,即使它不是你的插件。另一个优点是执行速度更快,当你的应用程序有很多插件时,这可能非常重要。这里有另一个技巧,其中包含无需使用 LoadLibrary() 加载 DLL 即可检测 DLL 是否导出所需函数(或函数)的代码:在不加载 DLL 的情况下检查 DLL 中导出的符号/函数

为用 C 编写的 DLL 设计接口的建议

我个人从不在用户应用程序中使用 C,并且通常反对在非非常底层/系统级开发并且目标平台上有合理的 C++ 编译器可用时使用 C。如果您有 C 编写的 DLL 的源代码,那么您可以执行以下操作:

  1. 如果您想保留 C 接口,请执行以下操作:
    • 在 DLL 的公共头文件中声明一个 struct。这个 struct 的成员将是指针函数。这个 struct 将相当于 C++ 接口类。
    • 在您的 DLL 的某个地方,定义这个 struct 的一个(static)实例,并用指向您的函数实现的指针填充它。
    • 从 DLL 导出单个函数,该函数返回指向先前定义的(staticstruct 实例的 const 指针。
  2. 或者,您可以创建一个 C++“包装器”接口,从中调用原始的 C 函数。
© . All rights reserved.