I have a class in a DLL that has a method that I want to call externally but without exposing the class itself. Lets say I have the following class:
// MyClass.h
class MyClass
{
public:
// ...
void SetNumber(int x);
private:
int _number;
};
// MyClass.cpp
// ...
MyClass::SetNumber(int x)
{
_number = x;
}
I want to create an instance of MyClass and use it throughout the lifetime of the DLL.
// main.cpp
#define EXTERN extern "C" __declspec( dllexport )
int APIENTRY WinMain(/* ... */)
{
MyClass * myclass = new MyClass(); // I need to use this instance
// everytime in the exported SetNumber function.
return 0;
}
void EXTERN SetNumber(int x)
{
// Get myclass pointer
myclass.SetNumber(x);
}
Right now I have two ideas and I'm not sure if either is a good approach.
1) Use a Singleton where I create a private static instance of MyClass
and use it in every function I export by calls like MyClass().Instance().SetNumber(x)
. Is the static instance safe from external use?
2) Create a thread when linked and have the thread respond to events that each of my exported functions would create and push into a public queue. This sounds like a major hack.
Any suggestions?
You have several options at your disposal. I'll present here 2 options where the client of your DLL has full control of the lifetime of the MyClass
instance that it uses, although the client doesn't know it's actually a MyClass
instance.
Assuming your DLL-exposed feature is publicly known as Flubber. The first option is that you deliver your DLL with a public header file Flubber.h containing this:
#ifdef FLUBBER_EXPORTS
#define FLUBBER_API __declspec(dllexport)
#else
#define FLUBBER_API __declspec(dllimport)
#endif
typedef struct FlubberHandle_* FlubberHandle;
FLUBBER_API FlubberHandle CreateFlubber();
FLUBBER_API void DestroyFlubber(FlubberHandle flubber);
FLUBBER_API void SetFlubberNumber(FlubberHandle flubber, int number);
The implementation looks like this:
#include "Flubber.h"
class MyClass
{
public:
void SetNumber(int x){ _number = x;}
private:
int _number;
};
struct FlubberHandle_
{
MyClass impl;
};
FLUBBER_API FlubberHandle CreateFlubber()
{
return new FlubberHandle_;
}
FLUBBER_API void DestroyFlubber(FlubberHandle flubber)
{
delete flubber;
}
FLUBBER_API void SetFlubberNumber(FlubberHandle flubber, int number)
{
flubber->impl.SetNumber(number);
}
When you build your DLL, define the FLUBBER_EXPORTS
macro. When the client uses the DLL, it must not do the same, but just reference the DLL import library (Flubber.lib) and include the Flubber.h header:
#include <Flubber.h>
int main()
{
FlubberHandle flubberHandle = CreateFlubber();
... // Do lots of other stuff before setting the number
SetFlubberNumber(flubberHandle, 4711);
... // Do even more stuff and eventually clean up the flubber
DestroyFlubber(flubberHandle);
return 0;
}
That's the simplest way to let the DLL client contol the lifetime of an instance that exposes certain functionality.
However, it doesn't take much effort to provide your DLL clients with a richer interface by "wrapping back" the FlubberHandle
management in a proper RAII class. If the "hidden" MyClass
class exposes many other public things, you can still opt to only expose what you choose in the exported free functions (CreateFlubber
, DestroyFlubber
and SetFlubberNumber
) and the RAII class:
#ifdef FLUBBER_EXPORTS
#define FLUBBER_API __declspec(dllexport)
#else
#define FLUBBER_API __declspec(dllimport)
#endif
typedef struct FlubberHandle_* FlubberHandle;
FLUBBER_API FlubberHandle CreateFlubber();
FLUBBER_API void DestroyFlubber(FlubberHandle flubber);
FLUBBER_API void SetFlubberNumber(FlubberHandle flubber, int number);
class Flubber final
{
FlubberHandle m_flubber;
public:
Flubber() : m_flubber(CreateFlubber()) {}
Flubber(const Flubber&) = delete;
Flubber& operator=(const Flubber&) = delete;
~Flubber() { DestroyFlubber(m_flubber); }
void SetNumber(int number) { SetFlubberNumber(m_flubber, number); }
};
Using the RAII class, the client's experience of your DLL and its API will be much improved:
#include "stdafx.h"
#include <Flubber.h>
int main()
{
Flubber flubber;
... // Do lots of other stuff before setting the number
flubber.SetNumber(4711);
return 0;
}
This last approach was coined "Hourglass Interface" by Stefanus DuToit (his presentation "Hourglass Interfaces for C++ APIs" at CppCon 2014 is available online at https://www.youtube.com/watch?v=PVYdHDm0q6Y), and it's simply the idea that you have a library with a "fat"/full/rich C++ implementation in the bottom. You expose its functionality to library clients via a thin C function layer. On top of that, you also distribute a rich C++ interface to your exposed functionality by wrapping the C functions in a proper (usually RAII) wrapper class.