在前面的一篇文章中,向大家介紹過《基於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應用程序。感興趣小夥伴➕關注,喜歡的話也請點個贊。