【问题标题】:DLL written in C vs the same written in C++用 C 编写的 DLL 与用 C++ 编写的相同
【发布时间】:2013-05-19 18:40:56
【问题描述】:

我今天正在和一位同事讨论。他声称用 C 语言编写 DLL 将允许以任何语言编写的任何其他应用程序使用该 DLL。但是,如果该 DLL 是用 C++ 编写的,那么可以使用该 DLL 的应用程序的数量是有限的(可能是因为语言限制)。

  1. 他说的对吗?
  2. 如果您要编写一个 DLL,该 DLL 应该由用各种语言编写的各种应用程序使用(但在同一平台上;让我们暂时忘记可移植性),您会用 C/C++ 编写它吗?为什么?

我希望这个问题不是Gorilla vs. Shark 那种问题。如果是,请关闭它。

【问题讨论】:

  • 我的 OneJoker 库是用 C 编写的,可以在 Linux 和 Windows 上从 C、C++、Java 和 Python 调用。这相当简单,因为 Java 的 JNI 和 Python 的 ctypes 模块都设计用于 C,而不是 C++。 C++ 引入了许多依赖项,破坏了外部函数名称,等等。只是我的经验。
  • 如果需要导出 C 接口,可以extern "C"。关于第二个问题,您想查看 COM 对象(任何东西都可以使用它们,这就是重点)。

标签: c++ c dll code-reuse


【解决方案1】:

1) 如果 DLL 提供的 INTERFACE 确实是 C++ 接口,那么是的,它使其他语言很难(如果不是不可能)与 DLL 进行交互。

C++ 的接口比 C 更复杂,因为类的结构更复杂(this 指针被传递,虚函数指针/VTABLE 布局)和异常处理(被调用的代码必须处理事实异常以某种方式发生,为此,代码需要能够“展开”引发异常的代码的调用堆栈并销毁途中创建的任何对象,直到找到catch -如果 DLL 中的调用堆栈中不存在该问题,您将遇到问题 - 这种展开不是 C++ 标准的一部分,因为该标准不希望限制处理器需要/应该实现 C++ 的架构和特性超过必要)。在 DLL 中捕获异常将解决这里的问题。

换句话说,如果调用代码的语言不是 C++ [并且可能来自同一供应商],则需要对调用它的任何语言对 C++ 进行一些处理。这可能会变得相当复杂。

任何 C++ 对象都需要在调用代码中从/到相关语言进行翻译。对于与 C 相同的基本类型,这通常不是问题,但类、结构等必须与本地语言兼容的内容进行匹配。

另一方面:C 函数很容易与之交互:将参数放入堆栈,调用函数,并在返回时清理参数。没有什么奇怪的事情发生,没有隐藏的函数参数,也不需要展开堆栈。唯一轻微的复杂性是函数返回 struct(大于一定大小) - 但这是 C 中的一个非常特殊的情况。C 的“对象”要简单得多,并且大多数语言都有与基本 C 对应的类型语言类型(但是 C 风格的 struct 仍然会导致一些有趣的问题,如果“巧妙地”使用 union 可能是一个真正的挑战)。

2) 选择语言是一项复杂的工作,它在很大程度上取决于 DLL 应该如何使用或它应该提供什么样的接口,以及它本身应该连接到哪些接口 - 如果你的 DLL 是接口到另一个 C++ DLL(或其他一些 C++ 代码),那么您可能想要使用 C++。但是有一些方法可以生成具有 C 接口的 C++ DLL,方法是使用 extern "C" 作为接口函数(并确保没有任何东西 throws 越过“C”的墙,因为这肯定会导致问题)。

结论: 显然,通过将接口限制为“仅使用 C++”,那么任何使用 C、Python 或 Lisp 库(所有这些都可能相当容易地调用 C 函数)的人都会变得更加复杂,用户将必须将 C++ 代码包装在 C 语言包装器中。是的,这是可以做到的,并且当一些非常好的 C++ 库可用时经常使用,有人想要连接到具有可用 C 样式接口的语言。它与“在 DLL 中提供 C 到 C++ 接口”几乎相同的解决方案,只是它不是由 DLL 的生产者提供的。

【讨论】:

  • 哇,我不知道异常会造成这样的问题!如果异常被 DLL 函数本身捕获怎么办?在这种情况下,调用者不需要做任何事情对吗?
  • IMO 这个答案有点误导。堆栈展开是被调用函数的责任,而不是调用者的责任,因为调用者从不知道函数内部发生了什么。跨语言传递对象无论如何都不起作用,因为无论如何您都必须在语言之间翻译对象。 C 处理不太复杂的对象,因此开销较小,但仍然必须这样做。在 C++ 中具有“隐藏”参数是调用约定的一部分。你必须知道这一点,就像你必须知道 C 中的名字前面有一个 '_'。
  • @Devolus:好的,我会改写一下。
【解决方案2】:

大多数语言都提供了一种(简单的)方法来从 DLL 调用 C 函数。 C++ 的情况并非如此,因为 C++ ABI(C++ 函数的二进制接口)是特定于供应商的。

此外,几乎不可能与使用高级 C++ 构造(如模板或 STL)的 C++ DLL 进行交互。

但是,您的 DLL 的内容可以用 C++ 编写,您只需确保您的接口符合 C 标准。为此,请不要在接口中使用 C++ 结构,并在声明中加上:

#ifdef __cpluscplus
extern "C" {
#endif

/* You declarations here */

#ifdef __cpluscplus
}
#endif

...这样,您可以使用 C 接口包装您的 C++ 库。

编辑:正如 Mats Petersson 所写,不要忘记确保在包装器中处理所有可能的 C++ 异常。

【讨论】:

  • 这个 C 包装器一般会被认为是杂乱无章的吗?因为除非底层 C++ 代码使用 STL 和模板等非常复杂,否则我觉得将代码重构为 C 会更好。我说的对吗?
  • 不,我不这么认为。您的底层代码可能使用高级 C++ 概念,实现算法,使用高级 C++ 库,但它的接口可以非常简单,例如简单的启动/停止函数以及将一些缓冲区作为参数并返回一些缓冲区的函数,并封装数据在 JSON、XML 或 protobuf 数据块中。
  • 它认为这正是封装的作用。使用简单的界面隐藏高级处理...但是这取决于您的库。如果你的 DLL 需要提供 utility 功能并且有非常丰富的 API 可以导出到外部世界,你可能需要重新考虑以跨语言的方式使用这个 DLL 或者尝试像 SWIG 之类的库( swig.org)。
【解决方案3】:

每种语言都有自己的特性,例如调用约定、堆栈设置和其他内容。每当您尝试处理跨越语言边界的函数调用时,您都必须处理这个问题。编译器通常支持多种调用约定,因此您必须确保定义和编译正确,然后您可以使用任何语言与其他模块进行通信。 从一种语言或另一种语言来看,这样的说法是更容易或更难做到这一点是不正确的,因为你总是不得不在某个时候处理这​​个问题。

我选择最适合我的任务的语言,而不是相反。当我编写 GUI 应用程序时,我通常使用 Java,因为它让我可以专注于解决方案而不是跟踪内存。 :) 当我想要性能时,我使用 C 或 C++ 甚至汇编程序,所以语言的选择取决于我打算用它做什么或我的环境看起来如何。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 2014-04-16
    • 1970-01-01
    • 2012-04-28
    • 2012-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多