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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/331045/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 11:40:57  来源:igfitidea点击:

Using C++ DLLs with different compiler versions

c++windowsvisual-c++-6visual-c++-2008name-decoration

提问by foraidt

This question is related to "How to make consistent dll binaries across VS versions ?"

这个问题与“如何跨 VS 版本制作一致的 dll 二进制文件?”有关。

  • We have applications and DLLs built with VC6 and a new application built with VC9. The VC9-app has to use DLLs compiled with VC6, most of which are written in C and one in C++.
  • The C++ lib is problematic due to name decoration/mangling issues.
  • Compiling everything with VC9 is currently not an option as there appear to be some side effects. Resolving these would be quite time consuming.
  • I can modify the C++ library, however it must be compiled with VC6.
  • The C++ lib is essentially an OO-wrapper for another C library. The VC9-app uses some static functions as well as some non-static.
  • 我们有使用 VC6 构建的应用程序和 DLL 以及使用 VC9 构建的新应用程序。VC9-app 必须使用 VC6 编译的 DLL,其中大部分是用 C 编写的,一个是用 C++ 编写的。
  • 由于名称修饰/修改问题,C++ 库存在问题。
  • 使用 VC9 编译所有内容目前不是一种选择,因为似乎有一些副作用。解决这些问题将非常耗时。
  • 我可以修改 C++ 库,但是它必须用 VC6 编译。
  • C++ 库本质上是另一个 C 库的 OO 包装器。VC9-app 使用一些静态函数以及一些非静态函数。

While the static functions can be handled with something like

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

// Header file
class DLL_API Foo
{
    int init();
}

extern "C"
{
    int DLL_API Foo_init();
}

// Implementation file
int Foo_init()
{
    return Foo::init();
}

it's not that easy with the non-static methods.

使用非静态方法并不容易。

As I understand it, Chris Becke'ssuggestion of using a COM-like interface won't help me because the interface member names will still be decorated and thus inaccessible from a binary created with a different compiler. Am I right there?

据我了解,Chris Becke关于使用类 COM 接口建议对我没有帮助,因为接口成员名称仍将被修饰,因此无法从使用不同编译器创建的二进制文件中访问。我在吗?

Would the only solution be to write a C-style DLL interface using handlers to the objects or am I missing something? In that case, I guess, I would probably have less effort with directly using the wrapped C-library.

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

采纳答案by Roger Lipscombe

Interface member names will notbe decorated -- they're just offsets in a vtable. You can define an interface (using a C struct, rather than a COM "interface") in a header file, thusly:

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

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

Then, you can export a function from the DLL, with no mangling:

然后,您可以从 DLL 中导出一个函数,而无需修改:

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

This will work fine, provided that you're using a compiler that generates compatible vtables. Microsoft C++ has generated the same format vtable since (at least, I think) MSVC6.1 for DOS, where the vtable is a simple list of pointers to functions (with thunking in the multiple-inheritance case). GNU C++ (if I recall correctly) generates vtables with function pointers and relative offsets. These are not compatible with each other.

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

回答by Roger Lipscombe

The biggest problem to consider when using a DLL compiled with a different C++ compiler than the calling EXE is memory allocation and object lifetime.

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

I'm assuming that you can get past the name mangling (and calling convention), which isn't difficult if you use a compiler with compatible mangling (I think VC6 is broadly compatible with VS2008), or if you use extern "C".

我假设您可以通过名称修改(和调用约定),如果您使用具有兼容修改的编译器(我认为 VC6 与 VS2008 广泛兼容),或者如果您使用 extern "C",这并不困难.

Where you'll run into problems is when you allocate something using new(or malloc) from the DLL, and then you return this to the caller. The caller's delete(or free) will attempt to free the object from a different heap. This will go horribly wrong.

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

You can either do a COM-style IFoo::Releasething, or a MyDllFree()thing. Both of these, because they call back into the DLL, will use the correct implementation of delete(or free()), so they'll delete the correct object.

你可以做一个 COM 风格的IFoo::Release事情,或者做一MyDllFree()件事。这两个,因为它们回调到 DLL,将使用delete(或free())的正确实现,因此它们将删除正确的对象。

Or, you can make sure that you use LocalAlloc(for example), so that the EXE and the DLL are using the same heap.

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

回答by orcmid

Well, I think Chris Becke's suggestionis just fine. I would not use Roger's first solution, which uses an interface in name only and, as he mentions, can run into problems of incompatible compiler-handling of abstract classes and virtual methods. Roger points to the attractive COM-consistent case in his follow-on.

好吧,我认为Chris Becke 的建议很好。我不会使用Roger 的第一个解决方案,它只是名义上使用了一个接口,正如他所提到的,可能会遇到抽象类和虚拟方法的编译器处理不兼容的问题。罗杰在他的后续文章中指出了有吸引力的 COM 一致案例。

  1. The pain point: You need to learn to make COM interface requests and deal properly with IUnknown, relying on at least IUnknown:AddRef and IUnknown:Release. If the implementations of interfaces can support more than one interface or if methods can also return interfaces, you may also need to become comfortable with IUnknown:QueryInterface.

  2. Here's the key idea. All of the programs that use the implementation of the interface (but don't implement it) use a common #include "*.h" file that defines the interface as a struct (C) or a C/C++ class (VC++) or struct (non VC++ but C++). The *.h file automatically adapts appropriately depending on whether you are compiling a C Language program or a C++ language program. You don't have to know about that part simply to use the *.h file. What the *.h file does is define the Interface struct or type, lets say, IFoo, with its virtual member functions (and only functions, no direct visibility to data members in this approach).

  3. The header file is constructed to honor the COM binary standard in a way that works for C and that works for C++ regardless of the C++ compiler that is used. (The Java JNI folk figured this one out.) This means that it works between separately-compiled modules of any origin so long as a struct consisting entirely of function-entry pointers (a vtable) is mapped to memory the same by all of them (so they have to be all x86 32-bit, or all x64, for example).

  4. In the DLL that implements the the COM interface via a wrapper class of some sort, you only need a factory entry point. Something like an

    extern "C" HRESULT MkIFooImplementation(void **ppv);

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

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

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

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

    extern "C" HRESULT MkIFooImplementation(void **ppv);

which returns an HRESULT (you'll need to learn about those too) and will also return a *pv in a location you provide for receiving the IFoo interface pointer. (I am skimming and there are more careful details that you'll need here. Don't trust my syntax) The actual function stereotype that you use for this is also declared in the *.h file.

它返回一个 HRESULT(您也需要了解这些)并且还将在您提供的用于接收 IFoo 接口指针的位置返回一个 *pv。(我正在略读,这里有更多您需要的详细信息。不要相信我的语法)您为此使用的实际函数构造型也在 *.h 文件中声明。

  1. The point is that the factory entry, which is always an undecorated extern "C" does all of the necessary wrapper class creation and then delivers an Ifoo interface pointer to the location that you specify. This means that all memory management for creation of the class, and all memory management for finalizing it, etc., will happen in the DLL where you build the wrapper. This is the only place where you have to deal with those details.

  2. When you get an OK result from the factory function, you have been issued an interface pointer and it has already been reserved for you (there is an implicit IFoo:Addref operation already performed on behalf of the interface pointer you were delivered).

  3. When you are done with the interface, you release it with a call on the IFoo:Release method of the interface. It is the final release implementation (in case you made more AddRef'd copies) that will tear down the class and its interface support in the factory DLL. This is what gets you correct reliance on a consistent dynamic stoorage allocation and release behind the interface, whether or not the DLL containing the factory function uses the same libraries as the calling code.

  4. You should probably implement IUnknown:QueryInterface (as method IFoo:QueryInterface) too, even if it always fails. If you want to be more sophisticated with using the COM binary interface model as you have more experience, you can learn to provide full QueryInterface implementations.

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

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

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

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

This is probably too much information, but I wanted to point out that a lot of the problems you are facing about heterogeneous implementations of DLLs are resolved in the definition of the COM binary interface and even if you don't need all of it, the fact that it provides worked solutions is valuable. In my experience, once you get the hang of this, you will never forget how powerful this can be in C++ and C++ interop situations.

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

I haven't sketched the resources you might need to consult for examples and what you have to learn in order to make *.h files and to actually implement factory-function wrappers of the libraries you want to share. If you want to dig deeper, holler.

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

回答by Joris Timmermans

There are other things you need to consider too, such as which run-times are being used by the various libraries. If no objects are being shared that's fine, but that seems quite unlikely at first glance.
Chris Becker's suggestions are pretty accurate - using an actualCOM interface may help you get the binary compatibility you need. Your mileage may vary :)

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

回答by Dustin Getz

not fun, man. you are in for a lot of frustration, you should probably give this:

不好玩,伙计。你很沮丧,你应该给出这个:

Would the only solution be to write a C-style DLL interface using handlers to the objects or am I missing something? In that case, I guess, I would probably have less effort with directly using the wrapped C-library.

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

a really close look. good luck.

仔细一看。祝你好运。