使用不同编译器版本的C ++ DLL

这个问题是关于“如何使VS版本一致的dll二进制文件?

  • 我们有使用VC6构build的应用程序和DLL以及使用VC9构build的新应用程序。 VC9应用程序必须使用VC6编译的DLL,其中大部分是用C编写的,而C ++编写的则是一个。
  • 由于名称装饰/修剪问题,C ++ lib是有问题的。
  • 编译一切与VC9目前不是一个选项,因为似乎有一些副作用。 解决这些将是相当耗时的。
  • 我可以修改C ​​++库,但是它必须用VC6编译。
  • C ++ lib本质上是另一个C库的OO包装器。 VC9应用程序使用一些静态函数以及一些非静态函数。

而静态函数可以用类似的东西来处理

// Header file class DLL_API Foo { int init(); } extern "C" { int DLL_API Foo_init(); } // Implementation file int Foo_init() { return Foo::init(); } 

非静态方法并不那么容易。

据我所知, Chris Becke提出的使用COM接口的build议不会帮助我,因为接口成员的名字仍然会被修饰,从而不能从使用不同编译器创build的二进制文件中访问。 我在吗?

唯一的解决办法是写一个C风格的DLL接口使用处理程序的对象或我缺less的东西? 在这种情况下,我猜想,直接使用包装的C库可能会花费更less的精力。

接口成员名称将不会被装饰 – 它们只是在vtable中的偏移量。 您可以在头文件中定义一个接口(使用C结构,而不是COM接口),因此:

 struct IFoo { int Init() = 0; }; 

然后,你可以从DLL中导出一个函数,而不需要修改:

 class CFoo : public IFoo { /* ... */ }; extern "C" IFoo * __stdcall GetFoo() { return new CFoo(); } 

如果你正在使用一个生成兼容vtable的编译器,这将工作正常。 微软C ++已经生成了相同的格式的Vtable,因为(至少,我认为)DOS的MSVC6.1,其中vtable是一个简单的函数指针列表(在多重继承情况下thunking)。 GNU C ++(如果我记得正确)生成带有函数指针和相对偏移量的vtable。 这些不相容。

当使用用不同的C ++编译器编译的DLL而不是调用的EXE时要考虑的最大的问题是内存分配和对象生存期。

我假设你可以通过名称mangling(和调用约定),这是不难的,如果你使用兼容mangling(我认为VC6与VS2008广泛兼容)的编译器,或者如果您使用extern“C” 。

你会遇到问题的地方是当你从DLL中使用new (或malloc )分配一些东西,然后你把它返回给调用者。 调用者的delete (或free )将尝试从不同的堆释放对象。 这将是可怕的错误。

你可以做一个COM风格的IFoo::ReleaseMyDllFree() 。 这两个,因为他们回调到DLL,将使用正确的delete (或free() )的实施,所以他们会删除正确的对象。

或者,您可以确保使用LocalAlloc (例如),以便EXE和DLL使用相同的堆。

那么,我认为克里斯贝克的建议就好了。 我不会使用Roger的第一个解决方案 ,该解决方案只使用名称上的接口,而且如他所述,可能会遇到不兼容的抽象类和虚拟方法的编译器处理问题。 罗杰指出了他后续的吸引人的COM一致的情况。

  1. 痛点:您需要学习如何使用COM接口请求,并至少依靠IUnknown:AddRef和IUnknown:Release来处理IUnknown。 如果接口的实现可以支持多个接口,或者方法也可以返回接口,则可能还需要熟悉IUnknown:QueryInterface。

  2. 这是关键的想法。 所有使用接口实现(但不实现它)的程序都使用一个通用的#include“* .h”文件,该文件将接口定义为结构体(C)或C / C ++类(VC ++)或结构(非VC ++,但C ++)。 * .h文件根据您编译的是C语言程序还是C ++语言程序自动适应。 您不必简单地使用* .h文件就知道该部分。 * .h文件所做的是定义Interface结构或类型,可以说,IFoo具有其虚拟成员函数(并且只有函数,在此方法中不能直接查看数据成员)。

  3. 头文件被构造为以适用于C的方式遵守COM二进制标准,并且适用于C ++,而不管所使用的C ++编译器如何。 (Java的JNI民间人士认为这一点。)这意味着它可以在任何来源的独立编译的模块之间工作,只要一个完全由函数入口指针构成的结构(一个vtable)映射到所有的存储器都是一样的(所以它们必须都是x86 32位或全部x64)。

  4. 在通过某种包装类实现COM接口的DLL中,只需要一个工厂入口点。 像一个东西

    extern“C”HRESULT MkIFooImplementation(void ** ppv);

它返回一个HRESULT(你也需要了解这些),并且还会返回一个你接受IFoo接口指针的位置的* pv。 (我正在浏览,有更多的细节,你需要在这里。不要相信我的语法)你使用的实际函数原型也在* .h文件中声明。

  1. 重点是工厂入口,始终是一个未装饰的外部“C”做所有必要的包装类创建,然后提供一个Ifoo接口指针指向您指定的位置。 这意味着所有用于创建类的内存管理以及所有用于完成它的内存管理等都将发生在构建包装的DLL中。 这是你必须处理这些细节的唯一地方。

  2. 当你从工厂函数中得到一个OK结果时,你已经发出了一个接口指针,它已经为你保留了(有一个隐含的IFoo:Addref操作已经代表你交付的接口指针了)。

  3. 当你完成接口的时候,你可以通过调用接口的IFoo:Release方法来释放它。 这是最终的版本实现(如果你制作了更多的AddRef'd副本),这将在工厂DLL中拆除类和它的接口支持。 这是什么让你正确的依赖一致的动态存储分配和释放在接口后面,无论包含工厂函数的DLL是否使用与调用代码相同的库。

  4. 你应该也可以实现IUnknown:QueryInterface(作为方法IFoo:QueryInterface),即使它总是失败。 如果您想要使用COM二进制接口模型更复杂,因为您有更多的经验,您可以学习提供完整的QueryInterface实现。

这可能是太多的信息,但我想指出的是,在COM二进制接口的定义中解决了许多有关DLL的异构实现的问题,即使您不需要所有这些,它提供有效的解决方案的事实是有价值的 根据我的经验,一旦你掌握了这一点,你将永远不会忘记C ++和C ++互操作性的强大。

我没有勾画出您可能需要参考的示例的资源,以及为了制作* .h文件以及实际实现要共享的库的工厂函数包装所需学习的内容。 如果你想深入挖掘,那就更好。

还有其他一些你需要考虑的事情,例如各个图书馆正在使用哪些运行时间。 如果没有对象被共享,那很好,但是乍一看似乎不大可能。
Chris Becker的建议非常准确 – 使用实际的 COM接口可以帮助您获得所需的二进制兼容性。 你的旅费可能会改变 :)

没有乐趣,男人。 你有很多的挫折,你应该给这个:

唯一的解决办法是写一个C风格的DLL接口使用处理程序的对象或我缺少的东西? 在这种情况下,我猜想,直接使用包装的C库可能会花费更少的精力。

真的很近看。 祝你好运。