1

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)

Nathan Goings
  • 1,145
  • 1
  • 15
  • 33
  • Exactly how are you storing the `HWND` in this code? There are slightly different approaches that you could take. I'm leaning towards a `pImpl` solution. – Mats Petersson Sep 07 '13 at 21:19
  • Mats, I've added the WindowData code -- which includes the HWND. I though opaque pointers were pImple... – Nathan Goings Sep 08 '13 at 02:46

3 Answers3

1

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() )
{
}
Werner Erasmus
  • 3,988
  • 17
  • 31
  • Please elaborate, either I don't understand or that's common sense. I'm implementing the various actions I want such as toggling fullscreen, min/maximizing window, etc. However, other subsystems might need the `HWND`, `LPDIRECT3DDEVICE9`, audio I/O handles etc. I don't want to expose those to my "core" but definitely allow subsystems to access it. – Nathan Goings Sep 08 '13 at 02:52
  • I've not implemented my own window systems, but I've previously implemented/encapsulated threading libraries, TCP/UDP/Serial communication libraries for various Operation Systems / Platforms. During these endeavors I've never needed to expose HWND/Thread handles etc. Encapsulate the concept that varies - if a handle needs to be shared, it needs to be shared in terms of a specific "functional" responsibility. – Werner Erasmus Sep 12 '13 at 01:49
  • The responsibility/requirement exists independent of whether a handle exists (or not - for some platforms). If you need to share a handle, share it through an interface that defines its responsibility. Eventually all the interfaces culminates in one implementation, and each sharer shares the handle in terms of the responsibility (or interface) that it defines (via shared_ptr). – Werner Erasmus Sep 12 '13 at 01:49
1

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

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • I tried this originally. However, because I started with the idea that `WindowData` is the private information for `Window` it didn't feel right to expose *all* my private data. I've solved that by making `Window` a `friend` to `WindowData` and making `Window::data` publicly available. That allows `Window` to access the private fields in `WindowData` and subsystems can access the public fields. As I've never used friendship before, I'm not sure if it's the best solution. – Nathan Goings Sep 08 '13 at 18:07
  • Without fully understanding what "Window" is supposed to do (or how it's meant to do it) it's quite hard to say exactly what is the "best" solution. In general, friendship should be limited. It is better to expose functions that provides the right things. E.g. a `_window.GetWindowData()->GetHandle()`, perhaps? – Mats Petersson Sep 08 '13 at 19:31
  • Window is a class that creates and displays GUI windows... Everything in my `System` namespace implements a documented, external interface while the internals implement the platform specific code. The idea is that `WindowData` is an "extension" of `Window`. Being that each "pImpl" has an "owner" being a `friend` to that owner doesn't seem like a problem. `_window.GetWindowData()->GetHandle()` seems like it would add too many abstraction layers. However anything I implement in `WindowData` needs to further type-safety and permissions to limit dependency. Accessors seem to fill that role. – Nathan Goings Sep 09 '13 at 02:36
-1

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.

cooky451
  • 3,460
  • 1
  • 21
  • 39
  • The question is not about **hiding** the actual data, it is about abstracting away the (platform-specific) implementation details. – IInspectable Sep 07 '13 at 22:05
  • Cooky451, you are correct about the HWND, not much I can do to protect it. I'm using `(void*)` in the above examples. However, the casting becomes complicated. I've been experimenting with wrapping the output in another opaque-pointer but I haven't quite gotten it. I'm not sure if it's good design to have many small structs/classes to wrap things in. – Nathan Goings Sep 08 '13 at 03:10