Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Programación cruzada con C++

Iniciado por Marci, 14 de Abril de 2008, 11:44:07 PM

« anterior - próximo »

Marci

Estoy tratando de crear una aplicacion multiplataforma (en concreto windows y linux) usando c++. La mayor parte del código compilará en las dos plataformas. Por desgracia hay módulos específicos de cada plataforma. Algo tan sencillo como un timer, por ejemplo.

Hasta ahora la forma de ordenar el código que conozco seria algo como:

class CTimer
{
#ifdef LINUX
...
Codigo especifico de linux
...
#endif

#ifdef WIN32
...
Codigo especifico de windows
...
#endif
}

Organizado asi me resulta bastante lioso y nada intuitivo. Estaba pensando en intentar crear un CTimer_LINUX, un CTimer_WIN y una especie de interfaz CTimer. ¿Sabeis de algún sitio donde pueda encontrar información sobre esto?

Un saludo

shephiroth

Muy buenas. Se que parece un poco estupido, pero, y si utilizas programacion estructurada, creas una cabecera .h, y diferentes .c para cada plataforma??

SUERTE

Zaelsius

Un ejemplo simplificado:

timer.h


class Timer
{
protected:

Timer();

public:

static Timer* Create();

};



timer_linux.h

class TimerLinux : public Timer
{
protected:

TimerLinux();

};




timer_linux.cpp


Timer* Timer::Create()
{
return new TimerLinux();
}

TimerLinux::TimerLinux()
{
...
}





timer_win.h

class TimerWin  : public Timer
{
protected:

TimerWin();

};




timer_win.cpp


Timer* Timer::Create()
{
return new TimerWin();
}

TimerLinux::TimerWin()
{
...
}



Obviamente, en Windows no compilarias timer_linux.cpp/h y en Linux no compilarias timer_win.cpp/h.

De esta manera marcas una interfaz común para ambas implementaciones, y evitas el uso de macros.


Prompt

ZaelSiuS si lo haces así lo que no compila es el código que herede de TimerLinux o TimerWin, como no le metas una macro en el class T : public ...

Warchief

No son necesarios el patrón ni el create (lo mismo). El constructor puede ser público siempre que tenga la misma signatura para todas las plataformas.


// en timer.h
class CTimer
{
 public:
   CTimer();
}

// en linuxTimer.cpp
CTimer::CTimer()
{
 // lo que sea
}

// en winTimer.cpp
CTimer::CTimer()
{
 // lo que sea
}

// en timer.cpp
#if USE_WIN
#include "winTimer.cpp"
#else
#include "linuxTimer.cpp"
#endif


Ya que sólo uno de los cpps estará compilando.
(timer.cpp siempre está incluido en el proyecto, y según los defines, usa uno u otro cpp).


De esa forma se pueden usar objectos CTimer, sin tener que usar memoria dinámica por el patrón.

<edit>
PD: Lo común, si es interesante, puede ir en timer.cpp, si no en el .h.
</edit>

Prompt

Yo creo que lo más logico es lo que dice Warchief. Yo te apoyo Warchief.

#ifdef de includes! de toda la vida! :P

[Over]

Hola.

Tambien lo que se suele hacer es que tener dos ficheros diferentes segun la plataforma y compilas solo la que corresponda.

Es decir, tendrias timer.h y timer.cpp en dos carpetas distintas.

WIN32/timer.h y WIN32/timer.cpp y lo mismo para Linux.

Siempre y cuando ambos .h tengas las mismas funciones publicas que usas en el resto del codigo no tendrias porque ir guarreando el código con ifdef...

Hay quien prefiere tener #ifdef por ahi metidos... yo siempre que puedo prefiero tener ficheros iguales y solo compilo el corresponda según plataforma.

Es otro punto de vista.

Un saludo.

Zaelsius

En mi opinión...


Solución de Warchief:

// en timer.cpp
#if USE_WIN
#include "winTimer.cpp"
#else
#include "linuxTimer.cpp"
#endif


Eso es una guarrada :D , y además si tienes un proyecto para VisualStudio y  otro para Linux (un make p.ej.), te basta con no incluir el .cpp de la otra plataforma para no compilarlo.

Además, aunque la signatura de la clase sea idéntica, lo más probable es que tengas variables miembro distintas para cada implementación. Si no quieres guarrear la declaración de clase con macros y exponer detalles de implementación, no queda otra que tener una interfaz común y derivar de ella.


Solución de Over:

No existe una interfaz común explícita y cuando toques algo en el timer.h de Linux, tendrás que ir a cambiarlo en el de Windows también. En el día a dia, es normal que se te olvide cambiar los ficheros de la otra plataforma al hacer cambios en la tuya.. y hasta que uno de los programadores que trabaja en la otra plataforma no actualiza su repositorio e intenta compilar no se detecta el error.


Solución de LC0 (abstract factory pattern):

Esta me gusta más... pero en ese caso, ya que el tipo de instancia a crear no se resuelve hasta que no ejecutas la aplicación, es necesario compilar y enlazar ambas implementaciones en tu ejecutable. Para cosas del tipo Dispositivo_D3D y Dispositivo_GL puede tener sentido, pero no para código dependiente del SO (no puedes compilar código Linux en Windows, y aunque pudieses no tendría sentido enlazarlo si nunca se va a ejecutar).


Lo mismo me pierdo algo.. (hoy he dormido 4 horas :P)

LC0

Citar

Esta me gusta más... pero en ese caso, ya que el tipo de instancia a crear no se resuelve hasta que no ejecutas la aplicación, es necesario compilar y enlazar ambas implementaciones en tu ejecutable. Para cosas del tipo Dispositivo_D3D y Dispositivo_GL puede tener sentido, pero no para código dependiente del SO (no puedes compilar código Linux en Windows, y aunque pudieses no tendría sentido enlazarlo si nunca se va a ejecutar).


Razón tienes. Pero puedes hacer que, para cada implementación en concreto, las clases que necesiten dependencias de otra plataforma que no sea la actual, sean vacías. Vamos, que al final entraríamos también a usar #ifdef's y demás :D :


class CTimerLinux : public CTimer
{
   public:
       void hazArgo()
       {
           #ifdef LINUX
               std::cout << "Mira mamá! Estoy haciendo cosas raras en Linux! << std::endl;
           #endif
       }
}


davur

Boost propone una clasificación de mecanismos para tratar con varias implementaciones de una misma interfaz.

En tu caso, te interesa uno que ya se ha mencionado en este hilo:

Cita de: "Boost"Separate files

A library component can have multiple variations, each contained in its own separate file or files. The files for the most appropriate variation are copied to the appropriate include or implementation directories at installation time.

The way to provide this approach in boost libraries is to include specialized implementations as separate files in separate sub-directories in the .ZIP distribution file. For example, the structure within the .ZIP distribution file for a library named foobar which has both default and specialized variations might look something like:

foobar.h                // The default header file
foobar.cpp              // The default implementation file
readme.txt              // Readme explains when to use which files
self_contained/foobar.h // A variation with everything in the header
linux/foobar.cpp        // Implementation file to replace the default
win32/foobar.h          // Header file to replace the default
win32/foobar.cpp        // Implementation file to replace the default

Appropriate: When different platforms require different implementations, or when there are major performance differences between possible implementations.

Not appropriate: When it makes sense to use more that one of the variations in the same installation.

zupervaca

Yo uso otra solucion que es mezcla de varias:

itimer.h
class ITimer
{
public:
  virtual void get() = 0;
};

timer_linux.h
class Timer_Linux : public ITimer
{
  void get(){...};
};

timer_win.h
class Timer_Win : public ITimer
{
  void get(){...};
};

timer_api.h
#ifdef windows
  #include "timer_win.h"
  typedef Timer Timer_win;
#end
#ifdef linux
  #include "timer_linux.h"
  typedef Timer Timer_linux;
#endif

En definitiva ITimer te queda como clase abstracta para crear todas las plataformas que quieras y despues creas una nuevo tipo de dato que sera Timer que es el que maneja el usuario desde el include timer_api.h
Para que queda mas bonito puedes usar namespaces y crear carpetas diferentes, es decir:

system
  time
     api
        win32
           timer.h
        linux
           timer.h
     api.h
     itimer.h
...

Un ejemplo mas claro lo tienes en la libreria multiplataforma y multiapi que he realizado hace unos años.
La mayor ventaja de este sistema es que aunque uses funciones virtuales, estas, son resueltas en tiempo de compilacion ya que solo tendras una clase Timer final.
Otra nota importante antes que se me olvide es que si vas a hacer algo multiplataforma es que llames a todos los archivos que hagas en minusculas, no pongas mayusuclas ya que hay compiladores que no se los tragan, a parte el visual studio a veces te cambie los nombres a minusculas.

Saludos

Prompt

ZaelSiuS si dices que es una warrada, tu solución es un atropello al rendimiento xP

De warrada nada, al contrario es lo más limpio. Tu proposición provoca que tengas que poner #ifdef de todas formas, encima de todo solo te funciona si el interface definido en el .h es el mismo. Y como metas inline a meter más #ifdef.

Lo de incluir en la solución del proyecto unos ficheros y otros no... vais a mantener vosotros el make file! xD con lo facil que es darle a compilarlo TODO y que se quede un makefile de 4 lineas y media.

Para mi, personalmente, 1 de 2, #ifdef de includes, #ifdef de alguna función o pedacito de codigo. Estos 2 ultimos, sin pasarse eh! :D hehehe que sino si que se warrea todo. Si se separa por un poco de código acaba todo duplicado y no es mantenible por 3 lineas de código.

Lo que hace zupervacatambién me gusta es un hibrido válido. Lo que no me gusta es el nombre "_api.h" pero es un ejemplo y bueno se cambia para darle más coherencia. De cara a la programación:

ITimer.h ( o TimerBase.h o TimerInterface.h )
TimerLinux.h TimerWin.h

Timer_api.h == Timer.h

Creo que así queda bastante elegante, optimo y diferenciado. Esta solución está muy bien, zupervacuno! way way! :)

"Enmascarar" los #ifdef en un archivo que no parece que tenga nada de SO, Timer.h y un nombre apropiado para la clase Base o Interface de Linux y Win32.

Un saludete!

Buffon

Cita de: "Prompt"
Lo de incluir en la solución del proyecto unos ficheros y otros no... vais a mantener vosotros el make file! xD con lo facil que es darle a compilarlo TODO y que se quede un makefile de 4 lineas y media.

autotools

Prompt

Es bonito para IDEs... preSioso!

Hay un IDE deprecated (desgraciadamente) que tenia "Generate MakeFile" era un "makefile" limpio, extremly clean! un gustazo. Nada de decenas de carpetas y este tema...






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.