C++ Communicating private data from opaque pointer

701 Views Asked by At

I've recently learned about opaque pointers in C++. I've started using them to hide private members that are platform specific. Such as references to definitions in <windows.h> etc.

Now, I have several systems that build off each other and need to intercommunicate. For example, Direct3D needing a window handle (HWND). I do not want to expose platform definitions to my core system, however my subsystems need to communicate that data.

I'm exposing the opaque data and allowing access through a void pointer. This allows access to all private data.

Example usage (main.cpp):

// System:: namespace is my platform specific code
System::Window window;
System::DirectX::Direct3D9 gfxcontext(window);

Window definition (System/Window.h):

class Window
{
    WindowData* data; // Opaque pointer
public:
    void* getHandle() const; // returns an HWND handle
    Window();
    ~Window();
}

How to retrieve useful data (Direct3D9.cpp):

#include "Window.h"

Direct3D9::Direct3D9(const Window& _window)
{
    HWND windowHandle = *(HWND*)_window.getHandle();
    // [...]
    pp.hDeviceWindow = windowHandle;
}

However, this code works!:

*(HWND*)_window.getHandle() = 0; // changes HWND in WindowData to NULL!

Is there a way to communicate the platform specific information between subsystems without exposing it to my independent code -and- keeping private data private?


Edit current WindowData implementation

struct Window::WindowData
{
    static LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
    HWND windowHandle;
    WNDCLASSEX windowClass;
    HINSTANCE processInstance;
};

The HWND is used by DirectX in the presentation parameters (D3DPRESENT_PARAMETERS::hDeviceWindow)

3

There are 3 best solutions below

3
On BEST ANSWER

I would let getHandle (or better getWindowData return a WindowData * instead of a void *. Then let WindowData be just a forward declaration in the "System/Window.h" file.

Inside "Direct3D9", use the fully definition of WindowData, so:

HWND hwnd = _window.getWindowData()->windowHandle;

If at some later stage, you port to Linux, you can have a completely different structure inside WindowData [based on some #ifdef __WIN32/#else type of structure in the implementation side].

2
On

You can copy the data and return a unique_ptr. Or you can just return an HWND as void* instead of HWND* since it is just a pointer anyway, although that really exploits the implementation. Keep in mind though, others can still change you window somehow over the HWND, and I guess there's not much you can do about it.

3
On

Define functionally what you need to do, then implement it in terms of interface. Don't expose (make public) the pointer. Thereafter, implement the interface in terms of the HWND and the platform specific API.

e.g:

struct WindowHandleImpl
{
  virtual void show() = 0;
  virtual void maximize() = 0;
  //etc...
};

struct Win32WinHandleImpl : WindowHandleImpl
{
  std::unique_ptr<HWND> handle_; //Use deleter...
  virtual void show(); //In terms of HWND, using Win32 API
  virtual void maximize();
};

struct XWinHandleImpl : WindowHandleImpl
{
  //In terms of platform specific handle.
};

struct Window
{
  void show(); //In terms of WindowHandleImpl
  void maximize();//In terms of WindowHandleImpl
  private:
    std::unique_ptr<WindowHandleImpl> pimpl_;
};

Window::Window( const Factory& factory )
: pimpl_( factory.createWindow() )
{
}
//or 
Window::Window()
: pimpl_( SystemFactory::instance().createWindow() )
{
}