可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is there some way to make c++ dlls built with diffrent compilers compatible with each other? The classes can have factory methods for creation and destruction, so each compiler can use its own new/delete (since diffrent runtimes have there own heaps).
I tried the following code but it crashed on the first member method:
interface.h
#pragma once
class IRefCounted
{
public:
virtual ~IRefCounted(){}
virtual void AddRef()=0;
virtual void Release()=0;
};
class IClass : public IRefCounted
{
public:
virtual ~IClass(){}
virtual void PrintSomething()=0;
};
test.cpp compiled with VC9, test.exe
#include "interface.h"
#include <iostream>
#include <windows.h>
int main()
{
HMODULE dll;
IClass* (*method)(void);
IClass *dllclass;
std::cout << "Loading a.dll\n";
dll = LoadLibraryW(L"a.dll");
method = (IClass* (*)(void))GetProcAddress(dll, "CreateClass");
dllclass = method();//works
dllclass->PrintSomething();//crash: Access violation writing location 0x00000004
dllclass->Release();
FreeLibrary(dll);
std::cout << "Done, press enter to exit." << std::endl;
std::cin.get();
return 0;
}
a.cpp compiled with g++
g++.exe -shared c.cpp -o c.dll
#include "interface.h"
#include <iostream>
class A : public IClass
{
unsigned refCnt;
public:
A():refCnt(1){}
virtual ~A()
{
if(refCnt)throw "Object deleted while refCnt non-zero!";
std::cout << "Bye from A.\n";
}
virtual void AddRef()
{
++refCnt;
}
virtual void Release()
{
if(!--refCnt)
delete this;
}
virtual void PrintSomething()
{
std::cout << "Hello World from A!" << std::endl;
}
};
extern "C" __declspec(dllexport) IClass* CreateClass()
{
return new A();
}
EDIT:
I added the following line to the GCC CreateClass method, the text was correctly printed to the console, so its defenatly the function call thats killing it.
std::cout << "C.DLL Create Class" << std::endl;
I was wondering, how does COM manage to maintain binary compatibility even across languages, since its basicly all classes with inheritence (although only single) and therefore virtual functions. I'm not massivly bothered if I cant have overloaded operators/functions as long as I can maintain the basic OOP stuff (ie classes and single inheritence).
回答1:
You should be able to mix modules built with different compilers if you lower your expectations and stick to simple functions.
The way classes and virtual functions behave is defined by the C++ standard, but the way that's implemented is up to the compiler. In this case, I know that VC++ builds objects which have virtual functions with a "vtable" pointer in the first 4 bytes of the object (I'm assuming 32-bit), and that points to a table of pointers to the method entry points.
So the line: dllclass->PrintSomething();
is actually equivalent to something like:
struct IClassVTable {
void (*pfIClassDTOR) (Class IClass * this)
void (*pfIRefCountedAddRef) (Class IRefCounted * this);
void (*pfIRefCountedRelease) (Class IRefCounted * this);
void (*pfIClassPrintSomething) (Class IClass * this);
...
};
struct IClass {
IClassVTable * pVTab;
};
(((struct IClass *) dllclass)->pVTab->pfIClassPrintSomething) (dllclass);
If the g++ compiler is implementing the virtual function tables in any way differently from MSFT VC++ -- as it is free to do and still be conformant to the C++ standard -- this will just crash as you've demonstrated. The VC++ code expects the function pointers to be in particular places in memory (relative to the object pointer).
It gets more complicated by inheritance, and really, really, complicated with multiple inheritance and virtual inheritance.
Microsoft has been very public about the way VC++ implements classes, so you can write code that depends on it. For example, a lot of COM object headers distributed by MSFT have both C and C++ bindings in the header. The C bindings expose their vtable structure like my code above does.
On the other hand, GNU -- IIRC -- has left open the option of using different implementations in different releases, and just guaranteeing the programs built with it's compiler (only!) will conform to the standard behaviour,
The short answer is to stick to simple C-style functions, POD structures (Plain Old Data; i.e., no virtual functions), and pointers to opaque objects.
回答2:
You're almost certainly asking for trouble if you do this - while other commenters are correct that the C++ ABI may be the same in some instances, the two libraries are using different CRTs, different versions of the STL, different exception throwing semantics, different optimizations... you're heading down a path towards madness.
回答3:
I think you'll find this MSDN article useful
Anyway, from a quick glance at your code, I can tell you that you should not declare a virtual destructor in an interface. Instead, you need to do delete this
within A::Release() when the ref count drops to zero.
回答4:
One way you might be able to organize the code is to use classes in both the app and the dll, but keep the interface between the two as extern "C" functions. This is the way I have done it with C++ dlls used by C# assemblies. The exported DLL functions are used to manipulate instances accessible via static class* Instance() methods like this:
__declspec(dllexport) void PrintSomething()
{
(A::Instance())->PrintSometing();
}
For multiple instances of objects, have a dll function create the instance and return the identifier, which can then be passed to the Instance() method to use the specific object needed. If you need inheritance between the app and the dll, create a class on the app side that wraps the exported dll functions and derive your other classes from that. Organizing your code like this will keep the DLL interface simple and portable between both compilers and languages.
回答5:
You do critically depend on the v-table layout being compatible between VC and GCC. That is somewhat likely to be okay. Ensuring that the calling convention matches is something you should check (COM: __stdcall, you: __thiscall).
Significantly, you are getting a AV on writing. Nothing is being written when you make the method call itself, so it is likely that operator<< is doing the bombing. Does std::cout get probably initialized by the GCC runtime when a DLL is loaded with LoadLibrary()? The debugger should tell.
回答6:
You can as long as you only use extern "C"
functions.
This is because the "C" ABI is well defined, while the C++ ABI is deliberately not defined. Thus each compiler is allowed to define its own.
In some compilers the C++ ABI between different versions of the compiler or even with different flags will generate an incompatable ABI.
回答7:
interesting.. what happens if you compile the dll in VC++ as well, and what if you put some debug statements in CreateClass()?
I'd say its possible that your 2 different runtime 'versions' of cout are conflicting instead of your method call - but I trust that the returned function pointer/dllclass isn't 0x00000004?
回答8:
Your problem is maintaining ABI. Though same compiler but different versions you still want to maintain the ABI. COM is one way of solving it. If you really want to understand how COM solves this then check out this article CPP to COM in msdn which describes the essence of COM.
Apart from COM, there are other (one of the oldest) ways to solve ABI like using Plain old data and opaque pointers. Look at Qt/KDE library developers way of solving ABI.
回答9:
The problem with your code that causes the crash is virtual destructors in the interface definition:
virtual ~IRefCounted(){}
...
virtual ~IClass(){}
Remove them and everything is going to be ok. The problem is caused by the way the virtual function tables are organized.
MSVC compiler ignores the destructor, but GCC adds it as a first function in the table.
Have a look at the COM interfaces. They don't have any constructors/destructors. Never define any destructors in the interface and it's going to be ok.