• בלוג
  • פיתוח ממשק ווב ליישום מבוסס Qt הפך פשוט מאי פעם

פיתוח ממשק ווב ליישום מבוסס Qt הפך פשוט מאי פעם

פיצ׳ר חדש של Qt מאפשר אינטגרציה חלקה בין כל יישום JavaScript לתוכנית C++, ופותח את הדרך לתוכניות שרת/לקוח מתקדמות. החל מפיתוח לוח בקרה חדשני ומבוסס ווב ועד רענון ממשק המשתמש הישן שלכם. ההסבר המלא ודוגמת קוד בפנים.

1. איך זה עובד

חיבור בין קוד C++ לממשק ווב מתבצע באחת משתי צורות: הפופולרית ביותר היא הפעלת תוכנית ה C++ כשרת HTTP. הדפדפן יכול להתחבר לשרת זה ולשלוח פקודות באמצעות בקשות HTTP. בארכיטקטורה זו אפילו לא חייבים לאחסן את הקבצים הסטטיים על אותו השרת, מאחר ודפדפנים מודרניים תומכים ב CORS וכך ניתן להפעיל את שרת ה C++ שלכם בתור REST API. זו אפשרות טובה אך קצת מגבילה, מאחר ו HTTP הוא פרוטוקול חד-כיווני. בנוסף ואולי חמור יותר, הטמעת שרת HTTP ביישום שלנו עשויה לשנות את הארכיטקטורה של הקוד ולדרוש שכתוב חלקים מהותיים ממנו.

אפשרות שניה לחיבור שהיתה קיימת ב Qt תקופה ארוכה היא טעינת קבצי ה HTML וה JavaScript מתוך קוד התוכנית ובאותו הפרוסס. כך אפשר לחשוף אובייקטי Qt בתור אובייקטים ב JavaScript ולקבל אינטגרציה חלקה בין שני רכיבי התוכנית. המגבלה הגדולה כאן היא שחייבים לטעון את קוד ה HTML מתוך אותו הפרוסס, מה שאומר שאתם לא יכולים להפריד את ממשק הבקרה למחשב אחר, או אפילו לעבוד בארכיטקטורה של שרת מקומי המופרד מהממשק שלו (הממשק הגרפי הופך לחלק מקוד השרת).

החל מגירסא 5.4 של Qt עבדו בדיגיה על שינוי הארכיטקטורה, וכעת ב 5.5 קיבלנו קונספט חדש ויציב לבניית יישום ווב ו C++, יחד עם Deprecation של שיטת העבודה הישנה המבוססת על שיתוף זכרון באותו הפרוסס. הפרוטוקול החדש מבוסס על Web Sockets ומציע העברת הודעות דו-כיוונית מלאה בין כל שני פרוססים, בין אם על אותה המכונה או על מכונות שונות.

המחלקה הראשית בארכיטקטורה נקראת QWebChannel. המחלקה מייצגת ערוץ תקשורת בין יישום C++ לממשק ווב בקוד JavaScript. במקום לאפשר העברת הודעות כמו ממשקי תקשורת משעממים אחרים, כל התקשורת ממומשת באמצעות אובייקטים: קוד ה C++ מגדיר אובייקטים שיהיו זמינים לקוד ה JavaScript באמצעות הפונקציה registerObject של הערוץ, ולאחר מכן קוד ה JavaScript יכול לקרוא למתודות של אובייקטים אלו, או לקבל מהם אירועים.

במילים אחרות, לאחר החיבור הראשוני כל המשך העבודה מתבצע כאילו קוד ה JavaScript רץ מתוך אותו הפרוסס של קוד ה C++, עם אותן היכולות שהיו לנו באפשרות ההפעלה באותו פרוסס הישנה — אבל וויתרנו על המגבלה ששני החלקים חייבים לרוץ על אותו הפרוסס.

2. בואו נראה קצת קוד

קוד הדוגמא זמין בגיטהאב בקישור:
https://github.com/ynonp/qt-webchannel-demo

אתחיל ברעיון העבודה והחיבור בין שני רכיבי המערכת, ולאחר מכן נראה את הקוד שפותח ומאתחל את החיבור. אובייקט QObject רגיל ״מוחדר״ לעולם ה JavaScript. קוד JavaScript יכול להפעיל מתודות של האובייקט המוגדרות כ slots, וזה יגרום להפעלת המתודה הרלוונטית בקוד ה C++, ויכול להירשם על סיגנלים המוגדרים באובייקט כדי לקבל עדכונים כשסיגנלים אלו נשלחים.

השורות הבאות מדגימות הפעלה של קוד C++ מתוך JavaScript:

// from file: main.js

api.md5(txt);
api.sha256(txt);
api.sha3(txt);

האובייקט api הוא אובייקט ה C++ ש״הוחדר״ לקוד ה JavaScript. לקבלת עדכונים ב JavaScript נצטרך להירשם לסיגנלים שנשלחים מ C++ באופן הבא:

// main.js

api.notifyResult.connect(function(method, hex) {
    el[method].value = hex;
});

האובייקט notifyResult הוגדר בתור Signal ב C++, ולכן אפשר להפעיל עליו את הפוקנציית connect. כך נראית הכותרת של אובייקט ה C++ המתאים:

class Hasher : public QObject
{
    Q_OBJECT
public:
    explicit Hasher(QObject *parent = 0);

signals:
    void notifyResult(QString method, QString hex);

public slots:
    void md5(QString text);
    void sha256(QString text);
    void sha3(QString text);
};

החיבור הראשוני בין C++ ל JavaScript הוא מה ששונה כאן לעומת קוד שהיה בעבר. החיבור מתבצע דרך Web Sockets ולכן בצד ה C++ אנו מפעילים Web Sockket Server באופן הבא (מתוך main.cpp):

    // setup the QWebSocketServer
    QWebSocketServer server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode);
    if (!server.listen(QHostAddress::LocalHost, 12345)) {
        qFatal("Failed to open web socket server.");
        return 1;
    }

    // wrap WebSocket clients in QWebChannelAbstractTransport objects
    WebSocketClientWrapper clientWrapper(&server);

    // setup the channel
    QWebChannel channel;
    QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected,
                     &channel, &QWebChannel::connectTo);

    // setup the dialog and publish it to the QWebChannel
    Hasher hasher;
    channel.registerObject(QStringLiteral("hasher"), &hasher);

וקוד ה JavaScript מתחבר לשרת באמצעות השורות (מתוך הקובץ main.js):

var ws = new WebSocket("ws://localhost:12345");
ws.onopen = function() {
    el.status.innerHTML = "Connected";

    new QWebChannel(ws, function(channel) {
        // make dialog object accessible globally
        api = channel.objects.hasher;
    });
};

 

3. סיכום ונקודות להמשך

חיבור ממשק משתמש מבוסס ווב ליישום שלכם הוא כעת קל מאי פעם באמצעות Qt Web Channel. כדי להפעיל את הדוגמא תצטרכו כמובן את Qt5.5, ובנוסף קובץ API שנטען מתוך יישום צד הלקוח בשם qwebchannel.js שנמצא בדוגמא בתיקיית vendor.

הקוד שהוצג לא משתמש בתקשורת מאובטחת או בקרת גישה כלשהי לנתונים, מה שאומר שהוא פגיע למתקפות החל מ DoS ואולי אף תקיפות יותר חמורות. נראה שיש אופציה לעבודה בפרוטוקול wss המאובטח יותר, אך לפחות אצלי על המק היא לא עבדה. חוץ משימוש בפרוטוקול מאובטח תרצו לרוב גם להוסיף קוד שיוודא שהלקוח אכן מורשה לגשת ל API.

עבור לקוחות שכן רצים באותו הפרוסס יש אופטימיזציה שמשתמשת בחיבור ישיר (לא דרך Web Socket), ואז אתם לא מאבדים ביצועים לעומת קוד ישן.

לסיכום, יישומי Qt היברידיים ישנים ימשיכו לעבוד (עם שינויי קוד קלים עבור הגדרת החיבור הראשונית), ובנוסף קיבלנו אפשרות להפריד יישומים מקומיים לארכיטקטורת שרת מקומי ואף לפצל אותם לארכיטקטורת שרת/לקוח בין מכונות כמעט בלי מאמץ או שינוי קוד. 

קוד הדוגמא המלא זמין בגיטהאב בקישור:
https://github.com/ynonp/qt-webchannel-demo