Autore: Fabio Di Matteo
Ultima revisione: 27/03/2013
In questo articolo vedremo come sviluppare sulla nostra Linux-box un'applicazione gtk+ per Windows. Facendo in modo tale, che una volta lanciata su windows, non richieda l'installazione delle librerie Gtk+. Distribuiremo infatti le librerie assieme alla nostra applicazione, portandoci dietro tutto quello che serve (senza compilare staticamente) .
Avremo come risultato una cartella con una struttura simile alla seguente:
drwx------ 2 fabio fabio 4096 2011-10-21 12:22 bin (qui dentro ci sara' l'eseguibile della nostra applicazione) drwx------ 5 fabio fabio 4096 2011-10-21 11:23 etc drwx------ 13 fabio fabio 4096 2011-10-21 11:23 include drwx------ 6 fabio fabio 4096 2011-10-21 11:23 lib drwx------ 3 fabio fabio 4096 2011-10-21 11:23 man drwx------ 2 fabio fabio 4096 2011-10-21 11:23 manifest drwxrwxr-x 2 fabio fabio 4096 2011-10-21 12:21 mytest-src drwx------ 10 fabio fabio 4096 2011-10-21 11:23 share drwx------ 3 fabio fabio 4096 2011-10-21 11:23 src
Si puo' certo notare che così il pacchetto della nostra applicazione risulti un po' troppo grande. Questo perchè stiamo includendo ogni dipendenza. Si puo' tuttavia distribuire l'eseguibile senza tutto il resto delegando all'utente l'installazione delle Gtk+ .
Considerato che il bundle gtk+ servira' soltanto per far andare a runtime la nostra applicazione possiamo togliere dallo stesso alcuni file
non necessari a runtime. Ecco come fare:
.exe
dalla cartella bin/ tranne quello della nostra applicazione;Infine potremmo perfino fare un archivio autoestraente sfx che estragga tutto in una cartella temporanea e avvii in automatico la nostra applicazione. Avendo così alla fine un solo file per la nostra applicazione gtk+ su windows. (sempre che tutto cio' faccia parte del nostro obbiettivo).
Per la cross-compilazione dell'eseguibile windows ci affideremo al crosscompilatore Mingw versione per Linux. Su ubuntu è comodamente pacchettizzato (mingw32). Inoltre ci servira' il pacchetto All-in-one bundles delle Gtk+ contenente sia i runtime che i sorgenti delle Gtk.
Scarichiamo il pacchetto All-in-one bundles delle Gtk+ in una cartella a nostra scelta.
Ricaviamo i riferimenti agli header e alle librerie grazie al programma pkg-config.exe (eseguendolo con wine) contenuto nella sottocartella bin/ e mettiamo tutto su un file di testo (riferimenti.txt).
wine ../bin/pkg-config.exe --cflags --libs gtk+-2.0 > riferimenti.txt
Grazie ad un editor di testo, sed o altro sostituiamo ai percorsi stile windows, il nostro percorso relativo in stile unix. Ecco come si presenta il mio file riferimenti.txt (tutto su una riga):
riferimenti.txt
-mms-bitfields -I../include/gtk-2.0 -I../lib/gtk-2.0/include -I../include/atk-1.0 -I../include/cairo -I../include/gdk-pixbuf-2.0 -I../include/pango-1.0 -I../include/glib-2.0 -I../lib/glib-2.0/include -I../include -I../include/freetype2 -I../include/libpng14 -L../lib -lgtk-win32-2.0 -lgdk-win32-2.0 -latk-1.0 -lgio-2.0 -lpangowin32-1.0 -lgdi32 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lpango-1.0 -lcairo -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lglib-2.0 -lintl
Aggiungiamo il flag -mwindows se non vogliamo l'apertura del prompt della shell all'avvio ella nostra applicazione.
main.c
#include <gtk/gtk.h> //costanti enumerative con i nomi delle colonne enum { COL_ID , COL_COGNOME , COL_NOME, COL_CITTA, NUM_COLS } ; /*Con questa funzione prepariamo la struttura dati *che sara' contenuta nel TreeView*/ static GtkTreeModel * create_and_fill_model (void) { GtkListStore *store; //contenitore dati GtkTreeIter iter; // una specie di segnalibro che tiene il conto della posizione corrente //Creo il contenitore dei dati, specificando il tipo per ogni colonna store = gtk_list_store_new (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING,G_TYPE_STRING, G_TYPE_STRING); /* Aggiungiamo una riga */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_ID, 1, COL_COGNOME, "Di Matteo", COL_NOME, "Fabio", COL_CITTA, "Palermo", -1); /* Aggiungiamo una seconda riga */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_ID, 2, COL_COGNOME, "Di Matteo", COL_NOME, "Gioacchino", COL_CITTA, "Palermo", -1); //prima di uscire la funzione ritorna il modello di tipo GtkTreeModel return GTK_TREE_MODEL (store); } static GtkWidget * create_view_and_model (void) { GtkCellRenderer *renderer; //la cella GtkTreeModel *model; // il modello contenente la struttura dei dati GtkWidget *view; // puntatore ad un widget generico view = gtk_tree_view_new (); //il widget generico diventa un GtkTreeView /*Disegno le colonne nel GtkTreeView (colonne, non righe! :) )*/ /* --- Colonna 1 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "ID", renderer, "text", COL_ID, NULL); /* --- Colonna 2 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Cognome", renderer, "text", COL_COGNOME, NULL); /* --- Colonna 3 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Nome", renderer, "text", COL_NOME, NULL); /* --- Colonna 4 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Citta'", renderer, "text", COL_CITTA, NULL); /*Associamo al puntatore **locale** il valore di ritorno di create_and_fill_model () , * ovvero la struttura creata nella funzione precedente a questa */ model = create_and_fill_model (); /*Facciamo acuisire al TreeView "view" la struttura dati "model" . * quando distruggeremo il "view" "model" adra' distrutto con esso */ gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); g_object_unref (model); return view; } int main (int argc, char **argv) { GtkWidget *window;//la finestra del programma GtkWidget *view; //il widget che diventera' il GtkTreeView gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "delete_event", gtk_main_quit, NULL); /* dirty */ view = create_view_and_model (); gtk_container_add (GTK_CONTAINER (window), view); gtk_widget_show_all (window); gtk_main (); return 0; }
Il make file mettera il nostro eseguibile dentro la sottocartella bin/ dove sono contenute altre dipendenze dello stesso.
CPP = /usr/bin/i586-mingw32msvc-gcc # OPTS ottenuto lanciando il comando 'wine ../bin/pkg-config.exe --cflags --libs gtk+-2.0' # e sostituendo al percorso windows il nostro percorso relativo in stile unix. OPTS = -mwindows -mms-bitfields -I../include/gtk-2.0 -I../lib/gtk-2.0/include -I../include/atk-1.0 -I../include/cairo -I../include/gdk-pixbuf-2.0 -I../include/pango-1.0 -I../include/glib-2.0 -I../lib/glib-2.0/include -I../include -I../include/freetype2 -I../include/libpng14 -L../lib -lgtk-win32-2.0 -lgdk-win32-2.0 -latk-1.0 -lgio-2.0 -lpangowin32-1.0 -lgdi32 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lpango-1.0 -lcairo -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lglib-2.0 -lintl all: $(CPP) main.c -o ../bin/mytest.exe $(OPTS) clean: rm ../bin/mytest.exe
Andremo a impostare la variabile OPTS con il contenuto del file riferimenti.txt appena ottenuto.
Gtk+ supporta i temi anche per windows cosicché se vogliamo dare un aspetto piu gradevole alla nostra applicazione possiamo creare e inserire il file gtkrc in etc/gtk-2.0/ .
gtkrc
gtk-theme-name = "MS-Windows"
E' possibile distribuire i nostri progetti gtk su windows direttamente in un unnico file eseguibile tramite una versione modificata delle gtk (meno nota) static-gtk2-mingw32 . La peculiarita di questa versine delle librerie(oltre a permetterci di distribuire soltanto un unico file .exe) è che di default compila con il tema per windows, quindi non ci sara' bisogno di preoccuparci delle direttive per tema grafico.
Per prima cosa scarichiamo il pacchetto dal sito del progetto ed estraiamolo :static-gtk2-mingw32.
Come sorgente usiamo lo stesso che abbiamo usato in precedenza in questo caso inutilmente rinominato (mytest.c) . e per make file il seguente:
makefile
CPP = /usr/bin/i486-mingw32-gcc # OPTS ottenuto lanciando il comando 'wine ../bin/pkg-config.exe --cflags --libs gtk+-2.0' # e sostituendo al percorso windows il nostro percorso relativo. OPTS = -mwindows -mms-bitfields -mms-bitfields -I./include/gtk-2.0 -I./lib/gtk-2.0/include \ -I./include/atk-1.0 -I./include/cairo -I./include/gdk-pixbuf-2.0 -I./include/pango-1.0 \ -I./include/glib-2.0 -I./lib/glib-2.0/include -I./include/pixman-1 -I./include -I./include/freetype2 \ -I/gtk-dev/include/libpng14 -L./lib -L/gtk-dev/lib -lgtk-win32-2.0 -lgdk-win32-2.0 -limm32 -lshell32 \ -luuid -latk-1.0 -lpangocairo-1.0 -lgio-2.0 -lshlwapi -ldnsapi -lgdk_pixbuf-2.0 -ltiff -ljpeg -ljasper \ -lpangoft2-1.0 -lpangowin32-1.0 -lusp10 -lfontconfig -lexpat -lpango-1.0 -lcairo -lpixman-1 -lfreetype \ -lmsimg32 -lgdi32 -lpng -lz -lm -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lglib-2.0 -lintl -liconv -lws2_32 -lole32 all: $(CPP) mytest.c -o mytest.exe $(OPTS) clean: rm mytest.exe
Ecco il contennuto della cartella:
drwx------ 2 fabio users 4,0K 27 mar 12.46 bin drwx------ 16 fabio users 4,0K 27 mar 12.46 include drwx------ 9 fabio users 4,0K 27 mar 12.46 lib -rw-r--r-- 1 fabio users 1,6K 27 mar 13.24 makefile -rw-r--r-- 1 fabio users 4,2K 27 mar 12.58 mytest.c -rwxr-xr-x 1 fabio users 9,9M 27 mar 13.00 mytest.exe
Per ridurre le dimensioni dell'eseguibile possiamo stripparlo in questo modo:
i486-mingw32-strip mytest.exe
si arriva così a 7,5 MB circa(nel mio caso). E se non siamo ancora contenti delle dimensioni possiamo comprimerlo con upx:
upx mytest.exe
si arriva così a 2,6 MB circa(nel mio caso).
N.B. Mi è capitato con alcuni antivirus di avere dei falsi positivi in seguito alla compressione con upx.
Sui sistemi windows è possibile dotare gli eseguibili di un'icona personalizzata (in formato .ico) al momento della compilazione. In questo articolo (Dotare di icona personalizzata i nostri eseguibili windows tramite crosscompilazione) vedremo come fare cio' al momento della crosscompilazione su una linuxbox .
Il nostro wiki installa solamente cookie tecnici necessari al funzionamento della piattaforma "Dokuwiki". Niente analitics, statistiche, tracciamenti o altro.