Come usare un’applicazione Qt da qualsiasi browser
La possibilità di interagire da remoto con un’applicazione apre scenari interessanti, come l’assistenza remota.
Descrizione del problema
Supponiamo di essere nelle prime fasi di sviluppo di un progetto in cui il team di sviluppo è lontano dal team di design, che non ha le competenze e il software necessario per fare una build dell’applicazione. Dobbiamo metterci in condizione di fare iterazioni veloci di review con il team di design, possibilmente senza spedire loro hardware o richiedere l’installazione di macchine virtuali o altri strumenti di sviluppo. Come possiamo fare?
Negli ultimi anni il framework Qt ha maturato una serie di opzioni per interagire con un’applicazione da remoto. Le opzioni disponibili oggi sono:
Sia VNC che WebGL sono plugin del framework QPA, per cui possono essere usate con qualsiasi applicazione esistente senza ricompilare.
Possiamo usare il plugin VNC con l’opzione -platform vnc
. All’avvio, il plugin istanzia un server VNC a cui possiamo collegarci per interagire con la nostra applicazione.
Similmente, possiamo usare il plugin WebGL con l’opzione -platform webgl
. Questo plugin lancia un server web minimale che fornisce una HTML che disegna la nostra applicazione tramite comandi OpenGL.
Infine, abbiamo l’opzione WebAssembly, che è sì un’opzione per rendere remota la nostra applicazione, ma non è un platform plugin e quindi richiede interventi abbastanza estesi sul codice.
Vediamo in breve quali sono i pro e i contro di ciascuna soluzione:
VNC
- Funziona con tutte le UI Qt
- Funziona senza modifiche al codice
- Richiede un software particolare installato sulla macchina client (client VNC)
- Richiede una buona connessione di rete, sia in termini di banda che latenza
- Supporta al massimo un utente alla volta
WebGL
- Funziona su ogni browser
- Funziona senza modifiche al codice
- Supporta solamente UI QtQuick
- Richiede una connessione di rete con bassa latenza
- Supporta al massimo un utente alla volta
WebAssembly
- Funziona su ogni browser
- È multi utente per costruzione
- Richiede consistenti modifiche al codice dell’applicazione. Ad esempio, tutto il codice che parla con il filesystem o con socket va rivisto.
In questo articolo, ci concentriamo sulla soluzione WebGL perché permette di avere una remotizzazione dell’applicazione su un browser, consentendoci quindi di collegarsi tramite pc, smartphone o tablet senza software aggiuntivi.
Rendere il codice multi utente
Se avete già fatto una prova del plugin WebGL con una vostra applicazione (lanciandola con l’argomento -platform webgl
), probabilmente avrete notato che il plugin webgl supporta solo un utente per volta. Questo non dovrebbe sorprenderci, visto che un’applicazione UI Qt supporta un solo utente per volta.
La chiave per superare questa limitazione, senza dover riscrivere in modo consistente il sorgente, è avere più processi, uno per ogni utente, il cui stato sia la replica di un’applicazione master.
Qt Remote Objects
Qt Remote Objects è un modulo di Qt che consente di replicare un qualsiasi QObject o QAbstractItemModel tra un processo e l’altro. Non è un modo generico per scambiare dati tra processi, ma è sufficiente per replicare lo stato di una qualsiasi applicazione Qt Quick.
I concetti fondamentali esposti dal modulo Qt Remote Objects sono:
- Node: un processo in esecuzione sulla macchina che partecipa alla sincronizzazione
- Source: un oggetto che vive nel processo Host
- Replica: un oggetto proxy per un oggetto Source che vive in un processo Client
Node
I processi che usano Qt Remote Objects formano una rete in cui sono presenti due tipi di nodi: i nodi Host contengono gli oggetti Source che vengono condivisi, i nodi Client acquisiscono una Replica di un oggetto Source.
Le reti così composte sono reti peer-to-peer, in cui ogni nodo Client deve avere una connessione con il nodo Host che ospita la sorgente richiesta.
Source
Le sorgenti sono istanze di QObject o QAbstractItemModel che vogliamo condividere con gli altri processi. L’istanza di un oggetto Source vive in un nodo di tipo Host e deve essere esplicitamente condivisa dal nodo prima di essere visibile sulla rete.
Condividere un oggetto è semplice:
ControlUnit *o = new ControlUnit; // create your object as usual
QRemoteObjectHost srcNode(QUrl("local:ControlUnit")); // set the node address
srcNode.enableRemoting(o, “MyControlUnit”); // set object name on the node
Replica
Gli oggetti Replica sono dei proxy per gli oggetti Source all’interno dei processi client. L’applicazione client li vede esattamente come gli oggetti originali e quindi possono essere usati all’interno del QML senza problemi.
Per acquisire una Replica di un oggetto possiamo usare questo codice:
QRemoteObjectNode repNode;
repNode.connectToNode(QUrl("local:ControlUnit")); // connect to host node
QSharedPointer<QRemoteObjectDynamicReplica> ptr; // hold the replica
ptr.reset(repNode.acquireDynamic("MyControlUnit")); // acquire object by name
// The replica is ready after initialized()
//connect(ptr.data(), SIGNAL(initialized(), this, SLOT(replicaReady()));
Mentre per acquisire una Replica di un modello usiamo questo codice:
QRemoteObjectNode repNode;
repNode.connectToNode(QUrl("local:ControlUnit")); // connect to host node
QSharedPointer<QAbstractItemModelReplica> repPtr;
repPtr.reset(repNode.acquireModel("MyModel"));
Cerchi un corso su Qt?
Scopri i nostri corsi per aziende
Ottenere informazioni sulla API di un oggetto
Gli esempi sopra funzionano bene se esportiamo l’oggetto acquisito al QML, ma sono scomodi da usare in C++ perché non abbiamo accesso alla API dell’oggetto. Questo vuol dire che i modelli acquisiti tramite la acquireModel()
permettono di accedere solo alla API di QAbstractItemModel; segnali o slot aggiuntivi non vengono visti automaticamente dalla replica. Questo è il prezzo che paghiamo per usare API generiche come acquireDynamic()
.
Per avere le informazioni di tipo dobbiamo usare un altro strumento, chiamato repc
(REPlica Compiler), che genera un file C++ che contiene le informazioni sulla API del nostro oggetto.Ad esempio, supponiamo di voler aggiungere un metodo addItem(QString)
al nostro modello. Il primo passo è creare un file .rep che descrive la API del modello:
class MyModel
{
SLOT(void addItem(const QString &name));
};
Poi dobbiamo aggiungere questo file alla variabile REPC_REPLICA
del nostro progetto QMake:
REPC_REPLICA = myModel.rep
Il compilatore repc crea una classe che usiamo per esportare la API del modello:
Source node:
srcNode.enableRemoting(&model, "ModelAPI")
Replica node:
QSharedPointer<MyModelReplica> ptr(repNode.acquire<MyModelReplica>("ModelAPI"));
engine.rootContext()->setContextProperty("modelAPI", ptr.data());
Abbiamo quindi due oggetti da esportare all’engine QML, l’oggetto che contiene il modello vero e proprio e l’oggetto che contiene eventuali API aggiuntive del modello.
Considerazioni finali
Abbiamo visto che è possibile sincronizzare QObject e modelli Qt tra due processi con poco codice e come replicare la UI su un qualsiasi browser mediante il plugin WebGL. Questo ci consente di interagire con l’applicazione remota come se fosse in locale.
Riguardo a Qt Remote Objects abbiamo soltanto scalfito la superficie di quello che è possibile fare. Ecco alcuni casi per cui vi rimando alla documentazione ufficiale:
- Ci sono molti modi per esportare le informazioni di tipo al C++
- Qt Remote Objects non è una soluzione ottimale se dobbiamo sincronizzare migliaia di nodi, ma funziona egregiamente per un numero limitato di repliche
- Nel caso in cui ci interessi avere maggiore sicurezza per canale su cui avviene la sincronizzazione è comunque possibile usare connessioni cifrate.
Rimani aggiornato!
Ricevi novità su Develer e sulle tecnologie che utilizziamo iscrivendoti alla nostra newsletter.