使用腳本拓展CPP應用程序之——JS腳本

    在前面的一篇文章中,向大家介紹過《基於Qt實現的自定義簡易Lua腳本編輯器》,實際上就是使用Lua腳本拓展cpp應用程序。本文着重向大家分享使用javaScript腳本拓展CPP應用程序的方法。在接下來的多語言混合編程系列博文中,我還將和大家一起分享使用python、go以及shell(windows腳本和Linux腳本)拓展CPP應用程序的方法。

    開始正題前,咱們先演示下效果。

一、項目準備

編程環境:Qt4.8.3 + MingW 

編程語言:C++ 及JavaScript

二、項目實現(附主要代碼部分,需要源碼的可在文章結尾路徑下載或者私我)

1、新建一個Qt應用程序WxCPP,在pro文件中修改,添加 scipt 模塊

#-------------------------------------------------
#
# Project created by QtCreator 2020-05-23T12:11:00
#
#-------------------------------------------------

QT       += core gui script

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = WxCPP
TEMPLATE = app

INCLUDEPATH += ./

SOURCES +=\
        wxcpp.cpp \
    wxcpp_main.cpp \
    wxform.cpp

HEADERS  += wxcpp.h \
    wxform.h

FORMS += \
    wxform.ui

2、添加一個ui界面類,並完成主體的ui設計部分。參照文章開始的演示圖片。

3、實現js腳本拓展CPP應用程序的主要邏輯

// WxForm.h

#ifndef WXFORM_H
#define WXFORM_H

#include <QWidget>
#include <QString>
#include <wxcpp.h>
#include <QVariant>


namespace Ui {
class WxForm;
}

class WxForm : public QWidget
{
    Q_OBJECT

public:
    explicit WxForm(QWidget *parent = 0);
    ~WxForm();

    bool showScriptContext(QString fileName);
private slots:
    void on_openScript_pushButton_clicked();

    void on_execScript_pushButton_clicked();

    void on_getValueFromJs(QVariant variant);

    void on_closeScriptExec_pushButton_clicked();

    void on_save_pushButton_clicked();

    void on_saveAs_pushButton_clicked();

    void on_scriptFinished();

private:
    Ui::WxForm *ui;
    QString m_curScriptFilePath;
    WxCPP wxcpp;

    QVariant m_ret; 
};

#endif // WXFORM_H

 

// WxForm.cpp

#include "wxform.h"
#include "ui_wxform.h"
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QTranslator>
#include <QtScript>
#include <iostream>
#include <time.h>
#include <stdio.h>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#include <QMutex>
#include <QTextStream>

WxForm::WxForm(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::WxForm)
{
    ui->setupUi(this);
    ui->spinBox->setRange(1000,10000);
    ui->spinBox->setSingleStep(1000);

    m_ret = QVariant(0);
    QObject::connect(ui->scriptExecType_comboBox,SIGNAL(currentIndexChanged(int)),&wxcpp,SLOT(setLoopFlag(int)));
    QObject::connect(&wxcpp,SIGNAL(getValFromJs(QVariant)),this,SLOT(on_getValueFromJs(QVariant)));
    QObject::connect(&wxcpp,SIGNAL(scriptFinished()),this,SLOT(on_scriptFinished()));
    QObject::connect(ui->spinBox,SIGNAL(valueChanged(int)),&wxcpp,SLOT(setLoopInterval(int)));


}

WxForm::~WxForm()
{
    if(ui->scriptExecType_comboBox->currentIndex() == 1){ // 優雅的結束線程
        qDebug() << __FUNCTION__;
        wxcpp.setLoopFlag(0);
#ifdef Q_OS_WIN
        ::Sleep(1000);
#else
        sleep(1);
#endif
    }

    delete ui;
}


void WxForm::on_openScript_pushButton_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this,
                                                    tr("Open Script"), qApp->applicationDirPath(), tr("Script Files (*.js *.py *.lua)"));
    ui->scriptPath_lineEdit->setText(fileName);
    m_curScriptFilePath = fileName;
    showScriptContext(fileName);
}


bool WxForm::showScriptContext(QString fileName)
{
    QMutex mutex;
    mutex.lock();
    QFile file(fileName);
    if(!file.open(QIODevice::Text | QIODevice::ReadWrite)){
        mutex.unlock();
        return false;
    }
    ui->plainTextEdit->clear();
    ui->plainTextEdit->appendPlainText(file.readAll());
    file.close();
    mutex.unlock();
    return true;
}


void WxForm::on_execScript_pushButton_clicked()
{
    if(m_curScriptFilePath.isEmpty()){
        ui->textEdit->append("[error]: Please select one javaScript file at first!");
        return;
    }

    ui->execScript_pushButton->setEnabled(false);
    ui->scriptType_comboBox->setEnabled(false);
    ui->openScript_pushButton->setEnabled(false);

    wxcpp.setFileName(m_curScriptFilePath);
    wxcpp.setValue(m_ret);
    wxcpp.setLoopFlag(ui->scriptExecType_comboBox->currentIndex());
    wxcpp.setLoopInterval(ui->spinBox->value());
    wxcpp.setScriptType(ui->scriptType_comboBox->currentIndex());

    wxcpp.start();
}

void WxForm::on_getValueFromJs(QVariant variant)
{
    m_ret = variant;
    ui->textEdit->append(QString("[javaScript]: %1").arg(m_ret.toInt()));
}


void WxForm::on_closeScriptExec_pushButton_clicked()
{
    wxcpp.setLoopFlag(0);
}



void saveTextToFile(QString& text,QString filename){
    QMutex mutex;
    mutex.lock();
    QFile file(filename);
    if(!file.open(QIODevice::Text | QIODevice::WriteOnly)) {
        mutex.unlock();
        return;
    }
    QTextStream os(&file);
    os << text;
    file.close();
    mutex.unlock();
}

void WxForm::on_save_pushButton_clicked()
{
    QString context = ui->plainTextEdit->toPlainText();
    saveTextToFile(context,m_curScriptFilePath);
}

void WxForm::on_saveAs_pushButton_clicked()
{
    QString context = ui->plainTextEdit->toPlainText();
    QString fileName = QFileDialog::getSaveFileName(this,
                                                    tr("Save Script"), qApp->applicationDirPath(), tr("Script Files (*.js *.py *.lua)"));

    if(fileName.isEmpty()) return;
    saveTextToFile(context,fileName);

}

void WxForm::on_scriptFinished()
{
    ui->execScript_pushButton->setEnabled(true);
    ui->scriptType_comboBox->setEnabled(true);
    ui->openScript_pushButton->setEnabled(true);
}

 

腳本執行部分,放在獨立的線程中執行,線程中將執行結果傳遞給GUI主線程,並在輸出控件中顯示輸出結果。

// wxcpp.h

#ifndef WXCPP_H
#define WXCPP_H

#include <QObject>
#include <QThread>
#include <QString>
#include <QVariant>

class WxCPP : public QThread
{
    Q_OBJECT

public:
    WxCPP(QObject *parent = 0);
    ~WxCPP();

    void run();
    static int loadJavaScript(QString filename, QVariant &ret);

    void setFileName(QString filename){ m_fileName = filename; }
    void setValue(QVariant& variant){ m_variant = variant;}
    void setScriptType(int type = 0){ m_scriptype = type;}
public slots:
    void setLoopFlag(int flag = 0){ m_execFlag = flag;}
    void setLoopInterval(int interval = 1000){m_interval = interval ;}
signals:
    void getValFromJs(QVariant variant);
    void scriptFinished();
public:
    QString m_fileName;
    QVariant m_variant;
    int m_scriptype;
    int m_execFlag;
    int m_interval;
};

#endif // WXCPP_H

// wxcpp.cpp


#include "wxcpp.h"
#include <QTextStream>
#include <QTranslator>
#include <QtScript>
#include <QMessageBox>
#include <QDebug>
#include <stdlib.h>
#include <iostream>
#include <time.h>
#include <stdio.h>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#include <QMutex>

WxCPP::WxCPP(QObject *parent)
    : QThread(parent)
{

}

WxCPP::~WxCPP()
{

}

void WxCPP::run()
{
    do{
        switch (m_scriptype) {
        case 0: // javascript
             loadJavaScript(m_fileName,m_variant);
            break;
        default:
            break;
        }

        emit getValFromJs(m_variant);
        if(m_execFlag){
#ifdef Q_OS_WIN
            ::Sleep(m_interval);
#else
            sleep(m_interval/1000);
#endif
        }
    }while(m_execFlag);

    emit scriptFinished();
}

int WxCPP::loadJavaScript(QString filename,QVariant& ret)
{
    QScriptEngine engine;
    engine.installTranslatorFunctions();
    //! [1]

    //! [2]
    QScriptValue iValue = engine.newVariant(ret);
    engine.globalObject().setProperty("i", iValue);

    //! [2]

    //! [3]
    QMutex mutex;
    mutex.lock();
    QFile scriptFile(filename);
    scriptFile.open(QIODevice::ReadOnly);
    QTextStream stream(&scriptFile);
    QString contents = stream.readAll();
    scriptFile.close();
    mutex.unlock();

    //! [3]


    //! [4]
    QScriptValue result = engine.evaluate(contents, filename);
    ret = result.toVariant();
    qDebug() << "javaScript exec result:" << ret.toInt();
    //! [4]

    //! [5]
    if (result.isError()) {
        QMessageBox::critical(0, "Java Script",
                              QString::fromLatin1("%0:%1: %2")
                              .arg(filename)
                              .arg(result.property("lineNumber").toInt32())
                              .arg(result.toString()));
        return -1;
    }
    engine.abortEvaluation();
}

三、項目總結

1、本篇核心爲JS腳本拓展CPP應用程序,即CPP應用程序(後面簡稱應用程序)在運行過程中,可動態檢測js腳本,執行腳本文件,並將執行結果傳遞給CPP程序。本文以傳遞一個QVariant類型的變量爲例,實際上QtScript模塊可用於與JS交互的還可以是一個對象(包括自定義的繼承於QObject類的對象),並在JS中對對象進行操作。

2、拓展腳本的運行我們考慮放在獨立的線程中執行,可以避免主GUI線程卡界面的問題,軟件更加流暢,效率更好。其次實現腳本的兩種運行模式:普通腳本(單次執行),循環腳本(指定間隔時間循環執行),狀態可動態切換,這裏也運用了主流的優雅開啓和關閉Qt線程的方法之一。

3、JS腳本動態修改,程序動態執行和運行。即可以在腳本運行過程中修改腳本內容,保存後,將自動執行最新的腳本程序。這裏其實稍微重要的地方就是對文件的讀寫加鎖。其他的小細節不做過多介紹了。有需要的朋友可以自行學習涉及到的功能模塊,或者就相關問題留言。

下篇,我們接着講如何用python拓展CPP應用程序。感興趣小夥伴➕關注,喜歡的話也請點個贊。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章