4

I admit that this question sounds quite general. But after all, exporting classes from DLLs is a general and difficult topic, and frankly, I am currently confused on a quite general level.

The short question: How do object-oriented programming in C++ and DLLs fit together?

The long question: After reading this and this, I was slightly disappointed and confused because I wonder how object-oriented programming can work with DLLs if the DLL borders do not allow objects to be shared (assuming that two DLLs have used different compilers or compiler versions). The only options for exporting classes are these (as explained here or here):

  • export create and delete methods (C style, danger of dangling pointers, no objects as parameters, ugly)
  • export a pure virtual class and a factory function which creates an instance of the actual implementation class derived from the pure virtual class (needs inheritance, object deletion needs to be taken care of)

For example, I'd like to put common utility classes in one DLL, which I then use in several classes in other DLLs, which are themselves used in other DLLs. How can I do this? Is this an ill-defined way to organize my classes?

Bonus question: If I export a class having a pointer to implementation, is this equivalent to exporting a pure virtual class and a factory function? Or do the exported member functions have to be virtual?

EDIT: If it matters, I am on Windows 7 using Visual Studio 2010. Migrating vom an older Visual Studio made me sensitive to this problem.

Community
  • 1
  • 1
Fabian
  • 4,001
  • 4
  • 28
  • 59
  • Using DLLs is a very pragmatic solution to the problem of having to wait for a long time to get your program compiled and linked when you make a small change. Using them as a library that should be readily usable by any compiler version using any compile settings, well, no. Binary compatibility is not a C++ feature, that has to be added by you and you already know what that takes. – Hans Passant Apr 19 '16 at 11:30
  • @Hans Passant: Is there an alternative to DLLs to organize source code in a reusable way? Header implementation maybe? Anything else? – Fabian Apr 20 '16 at 08:19

2 Answers2

3

TL;DR : it depends.

The Simple Case

When the two DLLs (or a DLL and an executable) have been built with the same compiler and compiler version, and they use the same flavor of runtime (debug or release), and they link against the dynamic version of the runtime, you can do whatever you want. Delete across DLL boundaries for example.

The Less Simple Case

When one DLL link against a debug runtime and the other link against a release runtime, things get more complicated. This is because the debug runtime have different STL templates, for debugging purposes. For example, you can't manipulate an std::vector allocated from a debug DLL in the release DLL, and vice-versa. As long as you confine these STL templates into the right DLL, things should work. Obviously, you'll run into problems if you expose different ABI's yourself, for example by declaring a member in a #ifndef NDEBUG block.

You may have to force things a little bit with _CRT*defines for this to work, depending on what templates the debug DLL uses.

You should also create and destroy objects from the same DLL.

The Terrible (a.k.a. Real World) Case

When compiler versions don't match, on when at least one DLL is linked with a static runtime.

You'll run into a ton of ABI problems. The only safe thing to do is to rely (extensively) on the C ABI via extern "C". This is also probably what you want to do if you load the DLL at runtime.

A good thing to do at this point:

  • Write your DLL in C++
  • Do not expose (dllexport) anything using the C++ ABI.
  • Write a thin C wrapper around your C++ code.
  • Expose this C API with __declspec(dllexport)
  • Write a header-only C++ wrapper around the exposed C API.

Clients will then use your header-only wrapper, so every call to your DLL code will be made using the C ABI, which is extremely stable and unlikely to break.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Is the simple case realistic? What if I have a whole suite which I want to migrate from one compiler version to another? What do people do in this case? Also, could you explain in more detail how the thin C wrapper and the header-only C++ wrapper look like, maybe with a small example? That would be very kind. – Fabian Apr 20 '16 at 08:13
1

It's been a long time since I wrote one of the questions you linked, but let me try to help.

The short question: How do object-oriented programming in C++ and DLLs fit together?

That depends entirely on what your objects are exposing. If your objects are returning plain-C values, you should be okay. If your objects are returning POD classes containing plain-C values, you should also be okay. The headaches I was trying unsuccessfully to solve in that question/answer pair were almost entirely STL related.

To answer the natural followup question, the STL plays very badly with DLLs. C++ classes have innate cross-compiler compatibility issues due to different packing, member reordering, etc. The STL adds an extra layer of potential incompatibility because, when built into a debug DLL, extra members can be added.

For example, I'd like to put common utility classes in one DLL, which I then use in several classes in other DLLs, which are themselves used in other DLLs. How can I do this?

After failing to pass STL types successfully, I ultimately wrapped my C++ classes in a layer that translated them to and from their C counterparts. Where possible, I returned basic datatypes. Where I had to allocate memory (string, vector, etc.) I ended up with c-style create/delete functions. I believe I still exposed a pure virtual interface to protect as many implementation details as possible; I just didn't attempt to pass any STL objects directly across the DLL boundary.

If I export a class having a pointer to implementation, is this equivalent to exporting a pure virtual class and a factory function? Or do the exported member functions have to be virtual?

If I understand the question correctly, you want to do both.

struct MyDLL
{
  virtual void DoSomething() = 0;
  virtual int AddSomething(int argument1, int argument2) = 0;
}

extern "C" __declspec(dllexport) MyDLL* GetMyDLL();

Now your caller can call GetMyDLL and have a pointer to your class, and you can safely implement the virtual functions behind-the-scenes without worrying about your caller seeing what you're doing.

Community
  • 1
  • 1
cf-
  • 8,598
  • 9
  • 36
  • 58
  • Is it then possible to use pointers to my custom classes in a POD struct? A pointer is a POD type, right? My idea is to let the `MyDLL` struct have a pointer to an internal class, and to let `MyDLL` have ordinary functions which delegate to the internal class. – Fabian Apr 20 '16 at 08:17
  • My question aimed at how people live with this severe problem. I want to be able to move objects around my DLLs and not being able to safely do it hurts. Do people ignore possible problems or force their developers to all use the same compiler or do they simply not share anything except PODs and ints? Maybe I have to change my way of programming in the same way. – Fabian Apr 20 '16 at 08:26
  • As long as you're not actually passing a C++ class across the EXE/DLL boundary, I think you should be okay. I take it your "pointer to custom class" would function much like the `MyDLL`/`GetMyDLL` code in my answer? That should be safe enough. If you start trying to expose pointers to STL objects, though, don't expect that to end well. The EXE's compiler may have a different memory layout for those STL objects, causing the pointer to point to different things between your DLL and the EXE. – cf- Apr 20 '16 at 15:39
  • Actually, the comments and answer in [this question](http://stackoverflow.com/questions/22676050/is-there-a-standard-procedure-for-passing-class-objects-by-value) suggest even POD classes may not be completely safe to pass cross-compiler. I would suggest breaking everything down to fixed-size datatypes and/or a C ABI if at all possible. – cf- Apr 20 '16 at 15:41