views:

377

answers:

4

I have an abstract class in my dll.

class IBase {
  protected:
       virtual ~IBase() = 0;
  public:
       virtual void f() = 0;
};

I want to get IBase in my exe-file which loads dll. First way is to create following function

IBase * CreateInterface();

and to add the virtual function Release() in IBase.

Second way is to create another function

boost::shared_ptr<IBase> CreateInterface();

and no Release() function is needed.

Questions.

1) Is it true that the destructor and memory deallocation is called in the dll (not in exe-file) in the second case?

2) Does the second case work well if exe-file and dll was compiled with different compilers (or different settings).

+2  A: 

I would advise against using shared_ptr in the interface. Even using C++ at all in the interface of a DLL (as opposed to "extern C" only routines) is problematic because name-mangling will prevent you from using the DLL with a different compiler. Using shared_ptr is particularly problematic because, as you have already identified, there is no guarantee that the client of the DLL will use the same implementation of shared_ptr as the caller. (This is because shared_ptr is a template class and the implementation is contained entirely in the header file.)

To answer your specific questions:

  1. I'm not quite sure what you are asking here... I'm assuming that your DLL will contain implementations of classes derived from IBase. The code for their destructors (as well as the rest of the code) will, in both of your cases, be contained in the DLL. However, if the client initiates the destruction of the object (by calling delete in the first case or by letting the last instance of the shared_ptr go out of scope in the second case), then the destructor will be called from client code.

  2. Name-mangling will usually prevent your DLL from being used with a different compiler anyway... but the implementation of shared_ptr may change even in a new release of the same compiler, and that can get you into trouble. I would shy away from using the second option.

Martin B
-1 because you *can* bind the "right" delete to a shared_ptr and boost::shared_ptr is already in the tr1. Thus it's very near to be standard.+1 mentioning name-mangling as another problematic point=0 ... so what?
phlipsy
That you can specify a custom deleter for use by shared_ptr is not the issue. The issue is that the "standard-to-be" (TR1) does not specify how a shared_ptr should be implemented, i.e. what its memory layout should be and where it should store its reference count. The DLL and the client may be compiled with two compilers that are both entirely standards-compliant but don't agree on what a shared_ptr is internally. *This* is the problem. So don't use shared_ptr in DLL interfaces... it may come back and bite you.
Martin B
@Marting: I accept, in either case the problem arising from different implementations of shared_ptr isn't solved by the custom deleter. But then the heading question should be: Is it useful to provide a C++ interface for a DLL?
phlipsy
Yes, that's the next question... and I would probably respond: No. The problem is that (non-managed) C++ doesn't have a real ABI -- DLLs were only designed with C in mind. COM interfaces were invented in response to this problem, and Alexey's first option is pretty close in spirit to a COM interface. COM interfaces have their own problems though, as you note in your answer, and I would probably go with the option of hiding all of the objects behind a pure C interface. Ah, the guys in the .NET world don't know how good they have it... ;)
Martin B
@Martin: I agree! But is it likely that Alexey's software is used by so many other programmers using a different compiler than the vcc? And do they really have a problem to update their libraries to the version Alexey's using in his project?
phlipsy
@Martin: I was faced with these questions too and finally answered for me: No. I'll rather provide a safe C++ interface and in the unlikely case a programmer want to use my library he just has to switch to my versions. My audience will surely be quite small...
phlipsy
@philipsy: If your audience is small, though, you lose the reasons for going with a DLL over a static library: 1. DLLs save you memory when the same DLL is used by multiple programs because it only needs to be loaded once. However, if your audience is small, how likely is it that multiple programs using your DLL will be running at the same time? 2. DLLs can be replaced and updated "in the field" -- but if only a single program is using the DLL, just update that instead. Far less of a versioning and testing hassle, far less to go wrong.
Martin B
+1  A: 
  1. Using shared_ptr will make sure the resource releasing function will be called in the DLL.
  2. Have a look at the answers to this question.

A way out of this problem is to create a pure C interface and a thin fully inlined C++ wrapper around it.

sbi
+2  A: 

An answer to your first question: The virtual destructor in your dll is called - the information about its location is embedded in your object (in the vtable). In the case of memory deallocation it depends how disciplined the users of your IBase are. If they know they have to call Release() and consider that exception can bypass the control flow in an surprising direction, the right one will be used.

But if CreateInterface() returns shared_ptr<IBase> it can bind the right deallocation function right to this smart pointer. Your library may look like this:

Destroy(IBase* p) {
    ... // whatever is needed to delete your object in the right way    
}

boost::shared_ptr<IBase> CreateInterface() {
    IBase *p = new MyConcreteBase(...);
    ...
    return shared_ptr<IBase>(p, Destroy); // bind Destroy() to the shared_ptr
}                                         // which is called instead of a plain
                                          // delete

Thus every user of your DLL is easily prevented against resource leaks. They never have to bother about calling Release() or pay attention to exceptions bypassing surprisingly their control flow.

To answer your second question: The downside of this approach is clearly stated by the other answers: You're audience has to use the same compiler, linker, settings, libraries as you. And if they could be quite a lot this can be major drawback for your library. You have to choose: Safety vs. larger audience

But there's a possible loophole: Use shared_ptr<IBase>in your application, i.e.

{
    shared_ptr<IBase> p(CreateInterface(), DestroyFromLibrary);
    ...
    func();
    ...
}

Thus no implementation specific object is passed across the DLL boundary. Nevertheless your pointer is safely hidden behind the shared_ptr, who's calling DestroyFromLibrary at the right time even if func()'s throwing an exception or not.

phlipsy
There seems to be a typo in the answer: "boost::shared_ptr<IBase> IBase* CreateInterface()". Remove "IBase*" from that.
shojtsy
You're absolutely right. Thank you!
phlipsy
A: 

On your first question: I'm taking an educated guess and not speaking from experience, but it seems to me that the second case the memory deallocation will be called "in the .exe". There are two things that happen when you call delete object;: first, destructors are called and second, the memory for the object is freed. The first part, destructor calling, will definitely work as you expect, calling the right destructors in your dll. However, since shared_ptr is class template, its destructor is generated in your .exe, and therefore it will call operator delete() in your exe and not the one in the .dll. If the two were linked against different runtime versions (or even statically linked against the same runtime version) this should lead to the dreaded undefined behavior (this is the part I'm not entirely sure about, but it seems logical to be that way). There's a simple way to verify if what I said is true - override the global operator delete in your exe, but not your dll, put a breakpoint in it and see what's called in the second case (I'd do that myself, but I have this much time for slacking off, unfortunately).

Note that the same gotcha exist for the first case (you seem to realize that, but just in case). If you do this in the exe:

IBase *p = CreateInterface();
delete p;

then you are in the same trap - calling operator new in the dll and calling operator delete in the exe. You'll either need a corresponding DeleteInterface(IBase *p) function in your dll or a Release() method in IBase (which doesn't have to be virtual, just not make it inline) for the sole purpose of calling the right memory deallocation function.

sbk