Sign in to follow this  

Cross platform architecture of a class

This topic is 4835 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
What about doing it like this:


// BasicWindow.h
class BasicWindow
{
// ...
public:
static BasicWindow* createWindow();
};

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

// Win32Window.h
class 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by CoffeeMug
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.


There doesn't need to be base type.

Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid
There doesn't need to be base type.

Yes, but in this case I have to A) copy/paste the same code over and over again for each implementation (I mean basic get/set code and other code that may be shared accross implementations) and B) I can't *enforce* each implementation to implement a certain standard interface. nmi's solution looks very appealing, I have to think about it a little more.

Share this post


Link to post
Share on other sites
BTW, there just *have* to be some resources on the intarweb but I couldn't find any. Seems like a fairly common thing, I'm surprised no articles come up on google after a simple search.

I think before I make a choice I'll open up some cross-platform GNU projects and see how they do it. Perhaps I'll pick up some ideas from there.

Share this post


Link to post
Share on other sites
I always try to avoid using #defines for platform specific stuff.
I prefer to have all the platform specifc source files in a directory called "platform" and just include them normally.

For classes I write an abstract base class from which I inherit platform specific clases. Having code for every platform in a single source file is kinda messy, at least for me.

Share this post


Link to post
Share on other sites
Quote:
Original post by owl
For classes I write an abstract base class from which I inherit platform specific clases.

You don't mind having to pay runtime penalty for virtual functions even though everything can be determined at compile time? What about time critical platform specific code?

Share this post


Link to post
Share on other sites
Quote:
Original post by CoffeeMug
Quote:
Original post by owl
For classes I write an abstract base class from which I inherit platform specific clases.

You don't mind having to pay runtime penalty for virtual functions even though everything can be determined at compile time? What about time critical platform specific code?


You should know what your needs are. If you need speed then code for speed. If you need organization and scalability code apropiately for that as well.

Share this post


Link to post
Share on other sites
Hi CoffeMug

I'm just starting off down the same road. I haven't written a single line of code yet, but I have been thinking about the organisation and class heirarchy.

Personally I'm leaning towards two methods mentioned already; Virtual base class member funcs and living with any overhead, and seperate files/folders for seperate implementations. But I can't help much with the facts or pro's and cons.

I've posted because I've spotted something that's missing here-- or at least has been touched upon in a way that I'm trying to avoid.

The organisation of classes such as class BaseWindow; class Win32Window or class XWindow will lead to you having several versions of your application code. Look at the main() in snk_kid's example - it's platform specific. I know it's just an example, but I saw it as a trap to be avoided :)

Going with seperate files and folders I hope to have a common class BaseWindow, and several versions of class Window.

I'm really aiming for the application code to be as portable as possible and I don't see any reason why it can't be 100% so, given the right interfaces.

-- Matt

Share this post


Link to post
Share on other sites
Quote:
Original post by matibee
The organisation of classes such as class BaseWindow; class Win32Window or class XWindow will lead to you having several versions of your application code.


The problem is win32 & X window system work completely different, there isn't much you can share in implemenation apart from say a string for the window title.

Quote:
Original post by matibee
Look at the main() in snk_kid's example - it's platform specific. I know it's just an example, but I saw it as a trap to be avoided :)


Are sure you even understand the code, its not platform specific, sure currently the window imps doesn't share any implementation buts its not hard to factor out commonality into a base type or begin with a base type and then specialize to sub-types.

Even if you go the sub-type polymorphic route clients must still specify which platform there going to be on unless you detect which platform clients are on.

Quote:
Original post by matibee
I'm really aiming for the application code to be as portable as possible and I don't see any reason why it can't be 100% so, given the right interfaces.


To achieve something like this you need to completely abstract each feature you support in each API your going support like the example of the icons.

EDIT: i would agree with DigitalDelusion some kind of bridge pattern would probably be the best way to go, you can still share implementation in a commmon base you don't have a pointer to the base type.

[Edited by - snk_kid on September 18, 2004 4:13:12 AM]

Share this post


Link to post
Share on other sites
Hey snk_kid,

I think we're probably aiming for different things here. Complete control over any number of platform specific systems is likely to end up with either a lot of platform code sneaking into the app code, or an awful lot of encapsulation.

In the context of game-dev'ing tho, I'd be happy to forfeit a lot of control over sub-systems as long as they are tuned to make a good job of running on the platform they are coded for.

So my intention is to have a WindowSys, SoundSys, RenderSys, NetworkSys, InputSys (etc) each with limited (but crafted with experience and testing) interfaces- this is not something I claim to be able to do right now, but it is my ultimate goal.

I get what your code is doing (well, just about ;) ) but Your main() IS platform specific. Of course this is just the init'ing of the app and maybe it's best left to platform specifics.

The biggest thing I wanted to avoid here was a complete nonsense of class naming that might lead to 'Win32insertclassnamehere' being spread all over the app code.

-- Matt

Share this post


Link to post
Share on other sites
Quote:
Original post by matibee
I think we're probably aiming for different things here. Complete control over any number of platform specific systems is likely to end up with either a lot of platform code sneaking into the app code, or an awful lot of encapsulation.


Its difficult how far do you go, for games there isn't much of the windowing system you need, just to be able to get either fullscreen/windowed app, rendering contexts, pixel formats, handling OS messages.

anything more than this such as dialogs etc it maybe be best to use a cross-platform widget library instead of trying to write our own its a big job.

Quote:
Original post by matibee
I get what your code is doing (well, just about ;) ) but Your main() IS platform specific. Of course this is just the init'ing of the app and maybe it's best left to platform specifics.


Oh okay you mean't the app's entry point, yeah that was just a silly example.

Share this post


Link to post
Share on other sites

This topic is 4835 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this