【问题标题】:C Runtime objects, dll boundariesC 运行时对象,dll 边界
【发布时间】:2009-06-27 09:28:50
【问题描述】:

为 dll 设计 C API 的最佳方式是什么,它处理传递依赖于 C 运行时的“对象”(FILE*、malloc 返回的指针等)的问题。例如,如果两个 dll 与不同版本的运行时链接,我的理解是您无法将 FILE* 从一个 dll 安全地传递给另一个。

是使用依赖于 Windows 的 API(保证跨 dll 工作)的唯一解决方案吗? C API 已经存在并且很成熟,但主要是从 unix POV 设计的(当然,仍然必须在 unix 上工作)。

【问题讨论】:

    标签: windows unix dll abi


    【解决方案1】:

    您要求的是 C,而不是 C++ 解决方案。

    在 C 中做这种事情的常用方法是:

    • 将模块 API 设计为不需要 CRT 对象。获取以原始 C 类型传递的东西 - 即让消费者加载文件并简单地将指针传递给您。或者,让消费者传递一个完全限定的文件名,即在内部打开、读取和关闭。

    • 我想到了其他 c 模块、MS cabinet SD 和部分 OpenSSL 库 iirc 使用的方法,让消费应用程序将指向函数的指针传递给初始化函数。因此,在初始化期间的某个时刻,您将 FILE* 传递给的任何 API 都会获取一个指向具有与 fread、fopen 等签名匹配的函数指针的结构的指针。在处理外部 FILE*s 时,dll 始终使用传入的函数而不是 CRT 函数。

    通过像这样的一些简单技巧,您可以使您的 C DLL 接口完全独立于主机 CRT - 或者实际上需要使用 C 或 C++ 编写主机。

    【讨论】:

      【解决方案2】:

      现有答案都不正确:鉴于 Windows 上的以下情况:您有两个 DLL,每个 DLL 都与两个不同版本的 C/C++ 标准库静态链接。

      在这种情况下,您不应将指向由 C/C++ 标准库在一个 DLL 中创建的结构的指针传递给另一个 DLL。原因是这些结构可能在两个 C/C++ 标准库实现之间有所不同。

      你不应该做的另一件事是从一个 DLL 中释放一个由 new 或 malloc 分配的指针,该 DLL 是在另一个 DLL 中分配的。堆管理器的实现方式也可能不同。

      注意,您可以使用 DLL 之间的指针 - 它们只是指向内存。问题在于免费。

      现在,您可能会发现这有效,但如果有效,那么您就是幸运。这可能会在将来给您带来麻烦。

      您的问题的一个潜在解决方案是动态链接到CRT。例如,您可以动态链接到 MSVCRT.DLL。这样,您的 DLL 将始终使用相同的 CRT。

      注意,我建议在 DLL 之间传递 CRT 数据结构不是最佳实践。您可能想看看是否可以更好地考虑因素。

      请注意,我不是 Linux/Unix 专家 - 但您在这些操作系统上也会遇到同样的问题。

      【讨论】:

      • 我理解这些问题 - 我正在寻求这个问题的答案 :) 我希望有一个不假设更改现有 C API(在签名中使用 FILE* 等)的解决方案,但是似乎没有如果我不能保证所有东西都链接到同一个 C 运行时?尽管理论上在 Unix 上问题是相同的,但它很少成为问题,因为只有一个 C 运行时。我从来没有遇到过在库之间传递 FILE*、unix 上的文件描述符的问题——很多 C API 都是这样设计的,并且可以在这些操作系统上完美运行。
      • 如果在 API 的签名中包含 FILE* 不是一个好习惯,那么您将如何处理文件流?如果您调用的 dll 有一些内部调用 fprintf 的函数和其他需要 FILE* 的函数,您是否需要导出函数来打开和关闭文件?
      • 我提出了一个解决方案:) 不要静态链接到 CRT。链接到 MSVCRT.DLL。如果 Linux、Unix 或 MAC 上的所有 CRT 库都能保证完美运行,我会感到非常惊讶。我想你在那里也很幸运。
      • 啊,对不起,我没有静态链接 CRT,所以我无意识地忽略了那部分消息:) 由于主程序(在我的控制之外)链接到 msvcrt90.dll,链接到 msvcrt .dll 将意味着 2 个不同的 CRT,对吗?在 unix 上,几乎可以保证您的 C 对象总是引用相同的运行时。这与其说是幸运的问题,不如说是大多数 unice 上扁平命名空间的结果(.so 中的每个 malloc 都将解析为相同的 malloc),以及其他更多文化原因(源的可用性,一个 C 编译器等...)
      • 不知道关于 Unix,但是当使用 VC 时,即使你的 DLL 和使用它的 EXE 是使用完全相同的版本构建的,你也不能保证使用相同的 CRT。 VC 有不同的 C 运行时用于发布和调试构建,它们不能很好地混合。如果 DLL 是使用发布版 CRT 构建的,而 EXE 是使用调试版开发的,那么即使动态链接,它们也不会使用相同的 CRT。
      【解决方案3】:

      不同运行时的问题无法解决,因为 FILE* 结构属于 到 Windows 系统上的一个运行时。

      但是如果你写一个小的包装接口你就完成了,它并没有真正的伤害。

      stdcall IFile* IFileFactory(const char* filename, const char* mode);
      
      class IFile {
      
        virtual fwrite(...) = 0;
        virtual fread(...) = 0;
      
        virtual delete() = 0; 
      }
      

      这是为了在任何地方跨 dll 边界传递而保存的,并没有真正的伤害。

      P.S.:如果开始跨 dll 边界抛出异常,请小心。如果您在 Windows 操作系统上实现了一些设计标准,但在其他一些设计标准上会失败,这将很好地工作。

      【讨论】:

        【解决方案4】:

        如果 C API 存在并且已经成熟,那么通过使用纯 Win32 API 的东西在内部绕过 CRT 可以帮助您成功。另一半是确保 DLL 的用户使用相应的 Win32 API 函数。这将使您的 API 在使用和文档方面的可移植性降低。此外,即使您采用这种内存分配方式,CRT 函数和 Win32 函数都处理 void*,您仍然遇到文件问题 - Win32 API 使用句柄,并且对 FILE 结构一无所知。

        我不太确定 FILE* 的限制是什么,但我认为问题与跨模块的 CRT 分配相同。 MSVCRT 在内部使用 Win32 来处理文件操作,并且可以从同一进程中的每个模块使用底层文件句柄。关闭由另一个模块打开的文件可能不起作用,这涉及在可能不同的 CRT 上释放 FILE 结构。

        如果更改 API 仍然是一种选择,我会做的是为 DLL 中创建的任何可能的“对象”导出清理函数。这些清理函数将以与在该 DLL 中创建对象的方式相对应的方式处理给定对象的处置。这也将使 DLL 在使用方面绝对可移植。您唯一需要担心的是确保 DLL 的用户确实使用了您的清理函数,而不是常规的 CRT 函数。这可以使用几个技巧来完成,这值得另一个问题...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-07-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-21
          • 2011-01-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多