Cross platform architecture of a class

Started by
18 comments, last by snk_kid 19 years, 7 months ago
Well, I've only recently started doing cross-platform stuff (currently win32 and linux) so I still have some questions over how to better design things. I'll start off with a simple example and list the possibilities that occurred to me. Please let me know if I missed something and feel free to suggest better ideas. So I am developing a window class that will create a plain vanilla main application window that will later be used to render things. Currently I'm dealing with a win32 and X implementations but let's suppose that I can later port to Mac or some other random XYZ platform. Take 1.

// Window.h
class Window
{
public:
    Create();

#ifdef _WIN32
    SetIcon(HANDLE);    // Just an example
#endif

#ifdef _LINUX
    SetIcon(ICONMAP);   // Just an example
#endif
};

// Window.cpp
#ifdef _WIN32
  // Implement one way
#endif

#ifdef _LINUX
  // Implement another way
#endif



This is the most basic way of doing things. Needless to say this way sucks. If I want to add an implementation I have to modify both the header file and the source file, they will get rather large, confusing, hard to read and maintain. Take 2. A simple improvement is separating Window.cpp into Win32Window.cpp and XWindow.cpp. Depending on the platform the project will be compiled with a different file. This is a little better but we're still stuck with a header file that has to modified if we decide to add implementations. Note, I am purposely not abstracting the icon (I'm just trying to illustrate the fact that some implementations may need specialized methods that don't really apply to other implementations). So let's move on. Take 3.

// BasicWindow.h
class BasicWindow
{
    // ...
};

// XWindow.h
class XWindow : public BasicWindow
{ /* ... */};

// Win32Window.h
class Win32Window : public BasicWindow
{ /* ... */};

// Window.h
#ifdef _WIN32
    typedef Win32Window Window;
#endif

#ifdef _LINUX
    typedef XWindow Window;
#endif



So far this is the best solution but it still suffers from one problem. If I add virtual functions to BasicWindow (say, Create() = 0;) the user of these classes will pay a runtime penalty eventhough all decisions about types can be made at compile time. In the case of this class it's acceptable, but I'd still like to know of a better solution that avoids this problem. Any suggestions are welcome.
Advertisement
pimpl?
coupled with putting platform dependant definitions and constans in diffrent equally named files in diffrent directories

i.e.:

/your_lib
platform independant headers and files using
dependant headers like <your_lib/window.h>
/your_lib/windows
depandant headers like window.h goes here
/your_lib/uniw
and here
/your_lib/xyz
and here

then you configure your build enviorment to look for thoose headers in the correct spot depending on platform.

Hope that's clear enough.
HardDrop - hard link shell extension."Tread softly because you tread on my dreams" - Yeats
I already do this. The problem is that Win32Window, XWindow and XYZWindow have some methods in common (lowest common denominator, if you will) and some methods that aren't applicable to other platforms. If I create a base class with common methods, the user has to pay runtime penalty for virtual functions. If I don't, the compiler doesn't ensure that all "common" methods are implemented on all platforms.
See the article linked to in this thread Portability (#ifdef Considered Harmful)

Also, have you thought about using templates?
An interesting article. Basically, I already do this (use different cpp files on different platforms). My problem is dealing with properties of platforms that are shared across *all* platforms.

What I wanted to do is ensure all classes in a certain group have a consistant interface in addition to their own specific methods. I don't want to use virtual functions in a base class because this makes no sense: the decision over which class is used is made at compile time, the user shouldn't need to pay the runtime penalty for virtual functions.

How would I use templates to solve this problem?
Quote:Original post by CoffeeMug
If I add virtual functions to BasicWindow (say, Create() = 0;) the user of these classes will pay a runtime penalty eventhough all decisions about types can be made at compile time.


The other alternative to dynamic polymorphism is static polymorphism using templates e.g.

class Win32Window;template < typename WinImp = Win32Window >class Window {   WinImp _imp;public:     Window(const WinImp& imp = WinImp()): _imp(imp) {}   /* .... */   template < typename Icon >   Window& icon(const Icon& i) {        _imp.icon(i);        return *this;   }   /* .... */};class XWindow {    ICONMAP _icon;public:   /* .... */   XWindow& icon(const ICONMAP& ic) {      _icon = ic;      return *this;   }   /* .... */};class Win32Window {   HANDLE _icon;public:   /* .... */   Win32Window& icon(HANDLE h) {      _icon = h;      return *this;   }   /* .... */};int main() {   Window< Win32Window > window_win32;   HANDLE ic_handle;   /* blah blah blah */   window_win32.icon(ic_handle);   return 0;}


There is probably a more elegant solution thou, and i don't think the cost of virtual methods should really bother you so much cause its probably not that much in terms of flexibility you get.

The bridge patterns is really good but you'll have to abstract alot of stuff such as you icon example, you probably need to abstract that into an actual icon type for instance.
Quote:Original post by snk_kid
*** Source Snippet Removed ***

I already considered this, but this causes two problems.
1. Implementation doesn't have access to base interface. For instance, base interface may have functions like GetSize()/SetSize() which are the same accross all platforms. When I call a specific platform's Create(), it should have access to base interface's methods.

2. Win32Window may want to expose methods to a user that are meaningless in XWindow. This is not possible with your example.
Quote:Original post by snk_kid
i don't think the cost of virtual methods should really bother you so much

The cost of virtual functions does not bother me in this particular case. I am trying to find a no runtime cost solution because virtual functions may prove to be too expensive in the future when I deal with time critical code that requires different implementations on different platforms.

EDIT: instead of aggregating I could do this:
template <class ImplType>class BasicWindow : public ImplType{ /* ... */};

and then use #ifdef to typedef a Window type with an appropriate base for an appropriate platform, but the first problem will still stand.
For things which are shared across all platforms I'd place that code in another file which is used by all the platforms.

So far I've found that with my windowing system (for OpenGL) very little code which has both platform dependant AND independant mixed into it.

My window mgr is (apart from 3 functions which i'm about to remove) totaly abstract, not a single Win32 line of code in it.
My OpenGL mode checker is mostly dependant on WGL and Win32 with only 2 functions with a total of 3 lines of code between them are purely independant.
The Display mode checker is the same.
And the windowing code is very minimal and totaly dependant on Win32 code, so nuffin can be shared there.
So when I end up porting this to Linux the bits which need rewriting are all platform dependant.

For the Icon problem, assuming you've got some sort of management system, I'd wrap the Icons in a class of some sort (which is more than likely to be platform dependant) and then take a tip out of the WTL book and write an operator fucntion to pass the right bit into your platform dependant code (I use the same trick in my windowing code, my window class has an operator HWND() function which returns the internal HWND of the class, which is required for the OpenGL code to init the window.
Abstraction is never broken (application never sees the window anyways and in the Linux code wont require it as the Create function wont take an HWND for linux, it will do whatever is required by that platform) and the small cost is unimportant as this is a function which wont be called often and certainly not in a critical loop, of which the same is pretty much going to be true of nearly any windowing code, so the small over head of being virtual or routed via another class isnt, imo, that important.

Rambled slightly, but I'm hoping you get what i'm driving at.
What about doing it like this:

// BasicWindow.hclass BasicWindow{    // ...public:    static BasicWindow* createWindow();};// XWindow.hclass XWindow : public BasicWindow{ /* ... */};// Win32Window.hclass Win32Window : public BasicWindow{ /* ... */};// XWindow.cc#include "XWindow.h"BasicWindow* BasicWindow::createWindow(){    return new XWindow();}// Win32Window.cc#include "Win32Window.h"BasicWindow* BasicWindow::createWindow(){    return new Win32Window();}


If you link just one of the Files XWindow.cc and Win32Window.cc, there should be only one implementation of Window::createWindow(), which will return the correct type of window.
Quote:Original post by nmi
*** Source Snippet Removed ***

Ahh, this is very interesting. What I meant by Create() though, is not instantiating a class but calling platform API to create the actual window. As far as I can see, Create() doesn't need to be static for your method to work. I think this looks like a very good solution. I have to think about it some more and possibly play around with some test code to see if I find any problems with this solution.

This topic is closed to new replies.

Advertisement