tags:

views:

897

answers:

7

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).

A: 

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?

gbjbaanb
No, the VC debugger showed a reasomable value for the pointer, also as far as I can tell (Ive never tried to debug a non VC binary with VC before) it never actauly got into the PrintSomething method, at least the Stack Frame indicates it never entered the dll at this point.
Fire Lancer
When you're debugging code that wasn't built with VC -- or even when you are but don't have the debug symbols -- you can't completely trust what the debugger is telling you about the call stack.
Die in Sente
+1  A: 

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.

Hans Passant
How can I check if cout it being correctly created in the GCC dll using VC? Also how exactly is COM working through __stdcall, since I thaught even basic classes had to work through __thiscall under VC?
Fire Lancer
+5  A: 

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.

Die in Sente
Id rather not have to use just plain functions everywhere, tbh Id just tell everyone they must compile the dll's with VC9 before I took that step...
Fire Lancer
You might be able to do what you want with CORBA, but I don't know much about it.
Die in Sente
+1  A: 

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 delibratley 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 incompatable ABI.

Martin York
im going for http://en.wikipedia.org/wiki/Application_binary_interface
Fire Lancer
@Shy: Application Binary Interface.
Die in Sente
+2  A: 

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.

Nemanja Trifunovic
+2  A: 

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.

Paul Betts
+1  A: 

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.

Tim Butterfield
Ok, is there some way to do this at least party automaticaly, rather than writing like 4 things for every method (loading the C method so the interface can find it, translating the call to the interface to a C method, going from the C method to an oop method in the dll, and finally the dll method?
Fire Lancer
You could partly automate this. You would need to keep a file list of the classes and methods. A script could process that file, generating the files to be #included where needed. Keep in mind though, that the C dll functions would not be able to directly accept C++ instance* parameters.
Tim Butterfield