tags:

views:

150

answers:

3

I have an unmanaged Win32 C++ application that uses multiple C++ DLLs. The DLLs each need to use class Foo - definition and implementation.

Where do Foo.h and Foo.cpp live so that the DLLs link and don't end up duplicating code in memory?

Is this a reasonable thing to do?

[Edit] There is a lot of good info in all the answers and comments below - not just the one I've marked as the answer. Thanks for everyone's input.

+2  A: 

Use __declspec dllexport to export the class to the DLL's export table, then include the header file in your other projects and link against the main DLL's export library file. That way the implementation is common.

tyranid
+2  A: 

Where does Foo live? in another dll.

Is it reasonable? not really.

If you declare a class like this:

class __declspec(dllexport) Foo { ...

then msvc will export every member function of the class. However the resulting dll is very fragile as any small change to the class definition without a corresponding rebuild of every consuming dll means that the consuming code will allocate the incorrect number of bytes for any stack and heap allocations not performed by factory functions. Likewise, inline methods will compile into consuming dlls and reference the old layout of the class.

If all the dlls are always rebuilt together, then go ahead. If not - don't :P

Chris Becke
Good advice, but a casual or inexperienced reader might conclude either that DLLs are Evil, or that classes in DLLs are Evil. Niether is true. What is Evil is putting implementation details in the DLL's interface. Classes are fine for DLLs -- but you need to use proper interfaces (as in abstract base classes) which do not export implementation details.
John Dibling
Exporting via abstract base classes is much better, but completely obviates the need to export the class methods, and means that consuming code cannot derive from the exported "class".Exporting classes from DLLs IS evil: The problem with exporting the most tightly encapsulated class is: in c++ its the code that uses the class, NOT the dll code, that is responsible for allocating the correct number of bytes for the classes data.This will cause memory corruption errors if the class' raw size in memory increases and the consuming code is not rebuilt.And thats hard to remember to do.
Chris Becke
@Chris: see my post. whether or not it is evil depends on how you do it and who's going to use it.
John Dibling
@Chris: you're absolutely right that the client code can't derive from the exported class in the case of ABCs, though.
John Dibling
The export method IS used successfully by the MFC dlls - where the dll's name is changed if breaking changes are made to any of the exported classes.
Chris Becke
+2  A: 

Providing functionality in the form of classes via a DLL is itself fine. You need to be careful that you seperate the interrface from the implementation, however. How careful depends on how your DLL will be used. For toy projects or utilities that remain internal, you may not need to even think about it. For DLLs that will be used by multiple clients under who-knows-which compiler, you need to be very careful.

Consider:

class MyGizmo
{
public:
  std::string get_name() const;
private:
  std::string name_;
};

If MyGizmo is going to be used by 3rd parties, this class will cause you no end of headaches. Obviously, the private member variables are a problem, but the return type for get_name() is just as much of a problem. The reason is because std::string's implementation details are part of it's definition. The Standard dictates a minimum functionality set for std::string, but compiler writers are free to implement that however they choose. One might have a function named realloc() to handle the internal reallocation, while another may have a function named buy_node() or something. Same is true with data members. One implementation may use 3 size_t's and a char*, while another might use std::vector. The point is your compiler might think std::string is n bytes and has such-and-so members, while another compiler (or even another patch level of the same compiler) might think it looks totally different.

One solution to this is to use interfaces. In your DLL's public header, you declare an abstract class representing the useful facilities your DLL provides, and a means to create the class, such as:

DLL.H :

class MyGizmo
{
public:
  static MyGizmo* Create();
  virtual void get_name(char* buffer_alloc_by_caller, size_t size_of_buffer) const = 0;
  virtual ~MyGizmo();
private:
  MyGizmo();  // nobody can create this except myself

};

...and then in your DLL's internals, you define a class that actually implements MyGizmo:

mygizmo.cpp :

class MyConcreteGizmo : public MyGizmo
{
public:
  void get_name(char* buf, size_t sz) const { /*...*/ }
  ~MyGizmo() { /*...*/ }
private:
  std::string name_;
};

MyGizmo* MyGizmo::Create()
{
  return new MyConcreteGizmo;
}

This might seem like a pain and, well, it is. If your DLL is going to be only used internally by only one compiler, there may be no reason to go to the trouble. But if your DLL is going to be used my multiple compilers internally, or by external clients, doing this saves major headaches down the road.

John Dibling
+1 for interfaces and portability.
AshleysBrain