代码之家  ›  专栏  ›  技术社区  ›  foraidt

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

  •  8
  • foraidt  · 技术社区  · 17 年前

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

    • 我们构建了应用程序和DLL 已构建VC6和新应用程序 VC9。VC9应用程序必须使用 使用VC6编译的DLL,大部分 它们用C编写,一个用 C
    • C++库有问题,因为 名称装饰/修饰问题。
    • 用VC9编译一切 目前没有这样的选择 似乎有一些副作用。 解决这些问题需要很长时间 消费。
    • 我可以修改C++库,但它必须用VC6编译。
    • C++库本质上是另一个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's 使用类似COM的接口的建议对我没有帮助,因为接口成员名称仍将被修饰,因此无法从使用不同编译器创建的二进制文件中访问。 我说的对吗?

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

    5 回复  |  直到 9 年前
        1
  •  8
  •   Roger Lipscombe    17 年前

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

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

    当你使用以下方式分配东西时,你会遇到问题 new (或 malloc )从DLL中提取,然后将其返回给调用者。来电者的 delete (或 free )将尝试从其他堆中释放对象。这将大错特错。

    您可以使用COM样式 IFoo::Release 事物,或 MyDllFree() 事情。这两者,因为它们回调到DLL中,将使用正确的实现 删去 (或 free() ),因此他们将删除正确的对象。

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

        2
  •  3
  •   Roger Lipscombe    17 年前

    接口成员名称将 装饰——它们只是桌子上的补偿。您可以在头文件中定义一个接口(使用C结构,而不是COM“接口”),因此:

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

    然后,您可以从DLL导出函数,而无需进行任何修改:

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

    只要您使用的编译器能够生成兼容的vtable,这将很好地工作。自(至少,我认为)DOS的MSVC6.1以来,Microsoft C++已经生成了相同格式的vtable,其中vtable是指向函数的简单指针列表(在多重继承的情况下使用thunking)。GNU C++(如果我没记错的话)生成带有函数指针和相对偏移量的vtable。这些彼此不兼容。

        3
  •  3
  •   Community Mohan Dere    9 年前

    嗯,我想 Chris Becke's suggestion 很好。我不会用 Roger's first solution ,它只是名义上使用了一个接口,正如他提到的,可能会遇到抽象类和虚拟方法的编译器处理不兼容的问题。Roger指出了有吸引力的COM一致性案例 his follow-on .

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

    2. 这是关键的想法。所有使用接口实现(但不实现)的程序都使用一个通用的#include“*.h”文件,该文件将接口定义为结构体(C)、C/C++类(VC++)或结构体(非VC++,但C++)。*.h文件会根据您编译的是C语言程序还是C++语言程序自动进行适当调整。您不必仅仅为了使用*.h文件而了解该部分。*.h文件的作用是定义Interface结构或类型,比如IFoo,以及它的虚拟成员函数(而且只有函数,在这种方法中对数据成员没有直接可见性)。

    3. 头文件的构造是为了以一种适用于C和C++的方式遵守COM二进制标准,而不管使用的是哪种C++编译器。(Java JNI人员发现了这一点。)这意味着它可以在任何来源的单独编译的模块之间工作,只要一个完全由函数入口指针(vtable)组成的结构体被所有模块映射到内存中(因此它们必须都是x86 32位,或者都是x64,例如)。

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

      extern“C”HRESULT MkIFooImplementation(无效**ppv);

    它返回HRESULT(您也需要了解这些),并在您为接收IFoo接口指针提供的位置返回*pv。(我只是略读一下,这里需要更仔细的细节。不要相信我的语法)你用于此的实际函数原型也在*.h文件中声明。

    1. 关键在于,工厂条目(始终是一个未修饰的extern“C”)会执行所有必要的包装器类创建,然后将Ifoo接口指针传递到您指定的位置。这意味着,用于创建类的所有内存管理,以及用于最终确定类的所有存储器管理等,都将发生在构建包装器的DLL中。这是你必须处理这些细节的唯一地方。

    2. 当您从工厂函数获得OK结果时,您已经收到了一个接口指针,并且它已经为您保留了(已经代表您收到的接口指针执行了隐式IFoo:Addref操作)。

    3. 当你完成接口后,你可以通过调用接口的IFoo:release方法来释放它。这是最终的发布实现(如果您制作了更多的Add-ef副本),它将破坏工厂DLL中的类及其接口支持。无论包含工厂函数的DLL是否使用与调用代码相同的库,这都能让您正确依赖接口背后一致的动态存储空间分配和发布。

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

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

    我还没有概述您可能需要参考的示例资源以及您必须学习的内容,以便制作*.h文件并实际实现您想要共享的库的工厂函数包装器。如果你想深入挖掘,就大声喊。

        4
  •  1
  •   Joris Timmermans    17 年前

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

        5
  •  0
  •   Dustin Getz sunsations    17 年前

    不好玩,伙计。你会遇到很多挫折,你可能应该给这个:

    唯一的解决方案是写一个 使用处理程序的C风格DLL接口 或者我失踪了 什么?在这种情况下,我想,我 可能不会那么费力 直接使用封装的C库。

    仔细看。祝你好运。