Search code examples
c++dlldllexport

Call method from Class in DLL without exposing Class


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?


Solution

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