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

时间:2008-12-01 14:38:33

标签: c++ windows vc6 visual-c++-2008 name-decoration

此问题与"How to make consistent dll binaries across VS versions ?"

有关
  • 我们已经构建了应用程序和DLL 用VC6和新的应用程序构建 用VC9。 VC9-app必须使用 用VC6编译的DLL,大多数 用C写的,一个用 C ++。
  • C ++ lib因为有问题 名称装饰/破损问题。
  • 使用VC9编译所有内容是 目前不是那里的选择 似乎是一些副作用。 解决这些问题将是时候了 耗时。
  • 我可以修改C ​​++库,但必须用VC6编译。
  • C ++ lib本质上是另一个C库的OO包装器。 VC9-app使用一些静态函数以及一些非静态函数。

虽然可以使用类似

的方式处理静态函数
// 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's建议使用类似COM的接口对我没有帮助,因为接口成员名称仍然会被装饰,因此无法从使用不同编译器创建的二进制文件中访问。 我在那儿吗?

唯一的解决方案是使用处理程序向对象编写C风格的DLL接口还是我遗漏了什么? 在这种情况下,我想,我可能会更少努力直接使用包装的C库。

5 个答案:

答案 0 :(得分:5)

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

我假设您可以通过名称修改(并调用约定),如果您使用兼容的修改编译器(我认为VC6与VS2008大致兼容),或者如果您使用extern,这并不困难“C”。

当您遇到问题时,使用new(或malloc)从DLL分配内容,然后将其返回给调用者。调用者的delete(或free)将尝试从不同的堆中释放对象。这将是非常错误的。

您可以执行COM样式IFoo::Release事件或MyDllFree()事件。这两个,因为他们回调到DLL,将使用delete(或free())的正确实现,因此他们将删除正确的对象。

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

答案 1 :(得分:3)

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

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

然后,您可以从DLL导出函数,不会出现任何错误:

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

如果您使用的是生成兼容vtable的编译器,这将正常工作。 Microsoft C ++生成了相同的格式vtable(至少,我认为)用于DOS的MSVC6.1,其中vtable是一个简单的函数指针列表(在多继承情况下使用thunking)。 GNU C ++(如果我没记错的话)生成带有函数指针和相对偏移的vtable。这些彼此不相容。

答案 2 :(得分:3)

嗯,我认为Chris Becke's suggestion就好了。我不会使用Roger's first solution,它仅在名称中使用接口,并且正如他所提到的,可能会遇到抽象类和虚方法的不兼容编译器处理问题。罗杰指出his follow-on中有吸引力的COM一致案例。

  1. 痛点:您需要学习制作COM接口请求并正确处理IUnknown,至少依赖于IUnknown:AddRef和IUnknown:Release。如果接口的实现可以支持多个接口,或者方法也可以返回接口,那么您可能还需要熟悉IUnknown:QueryInterface。

  2. 这是关键的想法。所有使用接口实现(但不实现它)的程序都使用通用的#include“* .h”文件,该接口将接口定义为struct(C)或C / C ++类(VC ++)或struct(非VC ++但C ++)。 * .h文件会根据您是编译C语言程序还是C ++语言程序自动适应。您不必仅仅使用* .h文件就知道该部分。 * .h文件所做的是定义接口结构或类型,比如说,IFoo,它的虚拟成员函数(并且只有函数,在这种方法中没有对数据成员的直接可见性)。

  3. 构造头文件是为了遵循适用于C的COM二进制标准,并且无论使用何种C ++编译器,都适用于C ++。 (Java JNI民众想出了这一点。)这意味着它可以在任何来源的单独编译的模块之间工作,只要完全由函数入口指针(一个vtable)组成的结构被所有这些都映射到内存中。 (所以它们必须是所有x86 32位,或者所有x64,例如)。

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

    这样的东西

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

  5. 返回一个HRESULT(你也需要了解它们),并且还会在你提供的接收IFoo接口指针的位置返回一个* pv。 (我正在浏览,这里你需要更详细的细节。不要相信我的语法)你用于此的实际函数构造型也在* .h文件中声明。

    1. 关键是工厂条目始终是未修饰的extern“C”,它会创建所有必需的包装器类,然后将指向的位置传递给指定的位置。这意味着用于创建类的所有内存管理以及用于完成它的所有内存管理等都将在构建包装器的DLL中发生。这是您必须处理这些细节的唯一地方。

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

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

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

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

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

答案 3 :(得分:1)

您还需要考虑其他事项,例如各个库正在使用哪些运行时。如果没有共享的对象那么好,但乍一看似乎不太可能 Chris Becker的建议非常准确 - 使用实际的 COM接口可以帮助您获得所需的二进制兼容性。您的里程可能会有所不同:)

答案 4 :(得分:0)

男人,没有乐趣。你很沮丧,你应该给这个:

  

唯一的解决方案是写一个   使用处理程序的C风格DLL接口   对象或我错过了   什么?在那种情况下,我想,我   可能会用更少的努力   直接使用包装的C库。

非常近看。祝你好运。