使用脚本拓展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应用程序。感兴趣小伙伴➕关注,喜欢的话也请点个赞。

 

 

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