主页 博客 (1)- Qt+Lua环境搭建 (2)- 读写C变量 (3)- 调用C函数 (4)- 运行Lua脚本 (5)- 文件操作 (6)- 编辑器高亮显示 (7)- 行号更新 (8)- Lua与C共享数组
本项目是为公司机器人控制器扩展用户脚本写的Lua移植集成,原本机器人运动控制是专门开发的指令及编程环境,其中没有预留可以用户写自定义函数的方法。过去机器人应用多是简单的搬运、焊接、装配、喷涂等,都是点到点运动,并且也没有复杂的控制逻辑。
但是现在有客户应用在医疗机器人上面,现有方案使用的是Kuka机器人,其中有许多已经写好用户函数,其功能大多不涉及运动控制,只是逻辑比较复杂。现有架构已经定型,无法实现自定义函数功能,如果替代Kuka的话,要么是完全重写底层架构,要么就需要针对客户所需的功能进行底层开发集成,优点则是效率高,但是这样做的话有几个缺点:
(1) 极大的增加底层复杂性;
(2) 降低控制器稳定性;
(3)由于客户也无法给出具体需要多少函数和具体功能需求,所以导致底层开发同事也不能有效的满足实际情况。
原来见过优傲UR机器人除了常规的指令外,有一个脚本解释器,而越疆DOBOT则直接采用Lua脚本和scratch图形化编程,其他厂家则基本是底层架构支持自定义函数功能。 综合比较,最终确定采用Lua脚本单独做用户自定义函数扩展,而运动控制继续使用当前编程环境。
-------------------------------------------------------------------
原项目直接在机器人底层中嵌入了Lua函数包,所有的变量或者函数功能都是已经在底层写好,Lua脚本只需要用户在电脑端写好脚本程序,然后通过U盘或者ssh远程传输脚本文件到控制器中,最后在控制程序或者后台任务程序中添加调用Lua脚本指令即可。
此文主要示例下如何使用Qt做编辑器界面(包含Lua文件打开/读写/保存、关键字高亮、行号更新等功能),调用集成的Lua库,运行脚本实现读写C中的变量/共享数组,调用C中的系统函数。
=======================================================================
我另外一篇blog中有相关的 Lua学习记录 ;
或者参考 Lua开发环境搭建 ;
网上有人已经将各个版本的Lua编译好,可以直接下载对应的文件,就无需自己编译了。
具体可以参考这个链接: Lua的依赖库dll、解释器lua.exe、编译器luac.exe
此处仅仅简单记录一下我的Qt+Lua的编程环境搭建步骤:
步骤一: 使用VS2015编译出Lua 5.4.3的库文件,(参考 搭建C++调用Lua环境 )。
(1):从 Lua官网 下载源代码:
(2): 打开VS2015 创建一个新的工程,这里我创建的是一个win32控制台应用项目(Win32 console application):
(3): 确定后下一步,选择DLL和空项目,点击完成:
(4): 添加源文件,右键选择添加Add->现有项ExistingItem,选择Lua 源代码文件夹里的所有内容,去掉里面的lua.c 和luac.c:
(5): 打开项目属性,在c++高级里把编译改为C代码(/TC): :
(5): 或者修改属性-> C/C++ -> Preprocessor -> Preprocessor Definitions 为 LUA_BUILD_AS_DLL:
(6): 直接编译即可,注意选择所需的输出的平台(x86/x64)和配置(release/debug),编译结果如下:
(7): 编译后会生成.lib和dll库,.lib用于开发链接使用,dll用于运行使用:
(8): (可选)提取出以后调用dll/lib库所需头文件(lauxlib.h/lua.h/lua.hpp/luaconf.h/lualib.h):
注:除lua.hpp都是C风格头文件,如果要在c++中使用记住加上extern "C",或者直接使用lua.hpp。其实lua.hpp就是加了extern "C"。
至此,库编译就结束了。
-------------------------------------------------------------------
步骤二:测试调用Lua DLL:
(1):我使用的是vs2015写的测试用例,首先建立一个空的C++控制台应用程序,然后在里面创建一个lua-test.cpp文件和一个test.lua文件,目录结构如下:
(2):添加项目包含目录和依赖项。我是把安装的Lua文件直接拷贝到新建的项目工程内的,这样做的好处,是方便把测试工程给大家,不需要安装Lua,工程就可以直接运行。
此处使用前一步中编译出的dll/lib和部分h头文件。
(3):在test.lua文件内添加如下代码:
print "Hello, Lua!"
(4):在lua-test.cpp文件内添加如下代码:
#include <iostream>
#include <stdio.h>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
int main(int argc, char *argv[])
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dofile(L, "test.lua");
lua_close(L);
system("pause");
return 0;
}
(5):如果一切顺利,此时你点击运行(F5),应该会弹窗如下窗口,说明你环境搭建成功了。
-------------------------------------------------------------------
=======================================================================
我另外一篇blog中有相关的 Lua学习记录 ;
或者参考 Lua开发环境搭建 ;
具体使用方法就不详细写了,可以参考上面两个链接,此处仅简单写下代码。
test.lua中脚本如下:
print("Lua: cvar = " .. cvar)
cvar = 321;
print("Lua:Change cvar = " .. cvar)
C代码如下所示:
#include <iostream>
#include <stdio.h>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
int main(int argc, char *argv[])
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
int cvar = 1;
lua_pushnumber(L, cvar); // push cvar to top of stack
lua_setglobal(L, "cvar"); // name the top of stack
cout << "C: cvar = " << cvar << endl;
if (luaL_loadfile(L, "test.lua")
|| lua_pcall(L, 0, 0, 0))
{
const char * error = lua_tostring(L, -1);
cout << string(error) << endl;
}
lua_getglobal(L, "cvar"); //从lua变量空间将var1变量放入栈中
if (!lua_isnumber(L, -1)) // 通常需要对栈中元素类型做下检测
printf("is not number");
cvar = lua_tonumber(L, -1); // 从栈中读取刚才压入的变量,此时并不会将变量出栈
cout << "C:AfterRunLua: cvar = " << cvar << endl;
lua_close(L);
system("pause");
return 0;
}
点击运行(F5),应该会弹窗如下窗口,
=======================================================================
我另外一篇blog中有相关的 Lua学习记录 ;
或者参考 Lua开发环境搭建 ;
具体使用方法就不详细写了,可以参考上面两个链接,此处仅简单写下代码。
一: 下面这个例子仅仅是Lua脚本中调用C中函数输出信息:
test.lua中脚本如下:
fun()
C代码如下所示:
#include <iostream>
#include <stdio.h>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
LUALIB_API int fun(lua_State *L)
{
if (L == nullptr)return 0;
cout << "this is a message from C++" << endl;
return 0;
}
int main(int argc, char *argv[])
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, fun); // push cvar to top of stack
lua_setglobal(L, "fun"); // name the top of stack
if (luaL_loadfile(L, "test.lua")
|| lua_pcall(L, 0, 0, 0))
{
const char * error = lua_tostring(L, -1);
cout << string(error) << endl;
}
lua_close(L);
system("pause");
return 0;
}
点击运行(F5),应该会弹窗如下窗口,
-------------------------------------------------------------------
一: 下面这个例子是Lua脚本中调用C中函数进行计算:
test.lua中脚本如下:
local a,s = add_sub(100,1)
print("addResult = " .. a .. "; subResult = " .. s)
C代码如下所示:
#include <iostream>
#include <stdio.h>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
LUALIB_API int add_sub(lua_State *L)
{
if (L == nullptr)
{
return 0;
}
int n = lua_gettop(L);
if (n != 2)
{
lua_pushstring(L, "Error:invalid number of parameters.");
lua_error(L);
return 0;
}
int firstValue = luaL_checkinteger(L, -2);
int secondValue = luaL_checkinteger(L, -1);
cout << "-------- C++ output ---------" << endl;
cout << "firstValue = " << firstValue << "; secondValue = " << secondValue << endl;
cout << "-----------------------------" << endl;
int firstResult = firstValue + secondValue;
int secondResult = firstValue - secondValue;
lua_pushnumber(L, firstResult);
lua_pushnumber(L, secondResult);
// this function has two value return to lua
return 2;
}
int main(int argc, char *argv[])
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, add_sub); // push cvar to top of stack
lua_setglobal(L, "add_sub"); // name the top of stack
if (luaL_loadfile(L, "test.lua")
|| lua_pcall(L, 0, 0, 0))
{
const char * error = lua_tostring(L, -1);
cout << string(error) << endl;
}
lua_close(L);
system("pause");
return 0;
}
点击运行(F5),应该会弹窗如下窗口,
=======================================================================
运行脚本基本二种方法:dostring() 和 dofile(); 有时为了更好的控制程序运行和错误检测输出,一般将上面两个函数进行分解为:loadstring()/loadfile()/pcall()。
比如:
const char* cLuaCode = "print(\"Hello,Lua!\")";
size_t len = strlen(cLuaCode);
if(luaL_loadstring(L, cLuaCode) != 0) {
cout << "load_error:" << (lua_tostring(L, -1)) << endl;
}
if(lua_pcall(L, 0, LUA_MULTRET, 0) != 0) {
cout << "pcall_error:" << (lua_tostring(L, -1)) << endl;
}
if (luaL_loadfile(L, "test.lua")
|| lua_pcall(L, 0, 0, 0))
{
const char * error = lua_tostring(L, -1);
cout << string(error) << endl;
}
=======================================================================
此处简单介绍下Qt中文件操作方法,具体可以参考Qt官网文档 QFile Class 。
1、文件操作 QFile
QFile 类提供了 一个用于读/写文件的接口,它可以用来读/写文本文件、二进制文件和 Qt 资源的 I/0 设备。
一般在构建 QFile 对象时便指定文件名,当然也可以使用 setFileName ()进行设置。可以使用 exists() 来检查文件是否存在使用 remove()来删除一个文件 。
一个文件可以使用 open() 打开,使用 close()关闭,使用 flush()刷新 ,文件的读写可以使用 read ()、 readLine ()、 readAll ()和 write (),可以使用自size()函数来获取文件的大小,使用 seek ()来定位到文件 的任意位置,使用 pos ()来获取当前的位置,使用 atEnd ()来判断是否到达了文件的末尾 。
还是可以用以下学过FILE,fstream这些来进行文件操作
打开并写入文件,文件操作流程 打开>写入/读取>关闭
访问一个设备以前,需要使用 open ()函数打开该设备,而且必须指定正确的打开模式,不同的打开模 式之间可以使用“|”符号同时使用。
打开设备后可以使用 write()或者 putChar()来进行写人,可以使用seek函数进行文件指针移动(如果 有),使用read ()、 readLine ()或者 readAll ()进行读取,最后使用 close()关闭设备。
//----已读方式打开文件--------//
QFile file(filcnamc);//填充文件路径
file.open(QIODevice::ReadOnly);//以读的方式打开文件
//-------一次性全部读出文件内容------------------//
QByteArray arary=file.readAll();//一次性全部读出文件内容
//--------------------------------------//
//----------------每次读取一行--------------//
QByteArray arary_2;
while (!file.atEnd()) {
//file.atEnd()判断还有没有可以读取的也就是判断是否为空
arary_2+=file.readLine();//读取一行
}
//-------------------关闭文件-----------------------//
file.close();//关闭文件
//------------以写入的方式打开文件------------------//
file.open(QIODevice::Append);//以在末尾追加写入的方式打开文件
file.write("I love you!");//写入的内容
file.close();//关闭文件
其他操作
获取的文件大小:size()
更改文件的名字:rename("新名字")
删除文件:remove()
获取一行中的某个位子的字符:先用readLine()读完这行,然后通过 string的mid函数处理
mid(5,1)从第五个位置,获取1个字符
移动文件指针:seek(5)
手动获取文件路径:
#include <QFileDialog> QString filcnamc=QFileDialog::getOpenFileName(this,"Open Files","C:");
获取文件属性
#include<QFileInfo> //查看文件属性
#include<QDebug>
#include<QDateTime> //用来输出文件生成时间
2代码
QFile file("test.lua");
QFileInfo info(file);
qDebug() << "绝对路径: " << info.absoluteFilePath();
qDebug() <<"文件名:" << info.fileName();
qDebug() << "后缀名:" << info.suffix();
qDebug() << "创建时间" << info.created();
qDebug() << "创建时间" << info.created().toString("yyyy.MM.dd hh:mm:ss");
qDebug() << "文件大小:" << info.size();
定位读取:
QFile file("test.lua");
QFileInfo info(file);
file.open(QIODevice::ReadOnly); //以只读的方式打开文件
file.seek(0); //定位文件指针位置为0
QByteArray array=file.read(5); //读取到第5个
qDebug() << "前5个字节: " << array;
qDebug() << "当前位置: " << file.pos();
file.seek(15);
array=file.read(5);
qDebug() << "第16到第20个字节:" << array;
file.close();
内容文件和文件夹的显示和监视文件或者文件夹有无发生改变
#include<QDebug>
#include<QDir> //操作文件夹
#include<QFileSystemWatcher> //文件夹监视器
1.显示选顶文件夹下的所有文件和目录
QDir myDir("D:\\xnj");//设置当前文件夹
myDir.absolutePath();//显示当前路劲下的文件和文件夹
myDir.entryList();//显示
2.指定文件后缀名显示选顶文件夹下的文件
QDir myDir("D:\\xnj");//设置当前文件夹
myDir.setNameFilters(QStringList("*.cpp"));//只显示后缀名为.cpp的
myDir.entryList();//显示
3.在当前选定目录下生成文件夹
QDir myDir("D:\\xnj");//设置当前文件夹
myDir.mkdir("mydir"); //在当前路径创建一个文件夹
4.文件文件夹监视器
//-------------------声明一个槽函数------------------------//
private slots:
void showMessage(const QString &path);//文件监控槽函数
//--------------------------------------------------------//
//-------------------定义槽函数----------------------------//
void MainWindow::showMessage(const QString &path)//文件夹监视槽函数
{
if(path=="D:\\xnj\\mydir")
{
//文件夹
}
else if(path=="D:\\xnj\\yy.txt")
{
//文件
}
}
//--------------------操作代码--------------------------//
QDir myDir("D:\\xnj");//设置当前文件夹
connect(&myWatcher,&QFileSystemWatcher::directoryChanged,this,&MainWindow::showMessage);//监视文件夹有没有发生改变
connect(&myWatcher,&QFileSystemWatcher::fileChanged,this,&MainWindow::showMessage);
//监视文件有没有发生改变
myWatcher.addPath("D:\\xnj\\mydir");
myWatcher.addPath("D:\\xnj\\yy.txt");
//------------------------------------------------------//
文本流与数据流(传输文件)
#include<QTextStream> //文本流
#include<QDataStream> //数据流
1.文本流的读取
//-----------------------------文本流读取---------------------------------------------//
QString filcnamc=QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\ym\\Desktop\\zy"); QFile file(filcnamc);//填充文件路径
file.open(QIODevice::ReadOnly);//以读的方式打开文件
QTextStream stream(&file);
stream.setCodec("utf-8");
QByteArray arary_2;
while (!stream.atEnd()) //stream.atEnd()判断还有没有可以读取的也就是判断是否为空
{
arary_2+=stream.readLine();//读取一行
}
file.close();
//----------------------------数据流读取写------------------------------------------//
QFile file_2("C:\\Users\\1.txt");
file_2.open(QIODevice::WriteOnly);//以写的方式打开
QDataStream data_stream(&file);//创建数据流
data_stream<<QString("jfesfesf")<<(qint32)65;
file_2.close();
file_2.open(QIODevice::WriteOnly);//以读取的方式打开
QDataStream data_in_stream(&file);//创建数据流
QString str;
qint32 n;
data_in_stream >> str >> n;
//-----------------------------------------------------------------------------------//
总结:我本流与操作系统有关系可能会出现乱码,大小端不一致,而数据流与操作系统无关所以适合文件传输
=======================================================================
本来计划直接使用VS Code或者Notepad++进行Lua脚本的编辑,然后程序中打开对应的文件开始运行就可以了。
后来想着将脚本文件各种操作都做在Qt中,打开、编辑、传输、调试等等,所以准备做个Lua界面编辑器,网上查了很多Qt扩展插件,比如:QScintilla/textosaurus/notepad等,但是因为移植有点麻烦,并且要么功能太多,要么不稳定,或者有些功能还无法修改实现。最终还是采用Qt的Syntax Highlighter进行关键字高亮显示,而自动补全提示目前暂时没有实现。
Syntax Highlighter具体使用方法和案例完全可以参考文档 Syntax Highlighter Example 。
对于Lua要修改的容易出错是多行注释,因为在Lua中需要“--[[” 和 “]]”进行配对实现多行注释,所以要使用\转意符。 比如下述代码:
multiLineCommentFormat.setForeground(Qt::green);
commentStartExpression = QRegularExpression(QStringLiteral("--\\[\\["));
commentEndExpression = QRegularExpression(QStringLiteral("--\\]\\]|\\]\\]"));
其他要修改的地方就是将例程中C++关键字,替换成Lua所使用的关键字。
具体效果如下图所示:
=======================================================================
此处使用了两个QTextEdit进行同步更新,根据脚本编辑区txtLuaScript的信号(textChanged、cursorPositionChanged)更新txtLineNum中的内容,具体代码可参考下面:
void MainWindow::updateLineNum()
{
// change lineNum area width
int digits = 1;
int lines = totalLines;
while (lines >= 10)
{
lines /= 10;
++digits;
}
int lineNumAreaWidth = 15 + fontMetrics().horizontalAdvance(QLatin1Char('8')) * digits;
ui->txtLineNum->setMinimumSize(lineNumAreaWidth ,600);
// update line number of left-linenum-area
QString strLineNum = "";
for(int i=1;i<totalLines;i++)
strLineNum += QString::number(i) + '\n';
ui->txtLineNum->setText(strLineNum);
ui->txtLineNum->verticalScrollBar()->value() ui->txtLineNum->verticalScrollBar()->setValue(ui->txtLuaScript->verticalScrollBar()->value());
}
void MainWindow::on_txtLuaScript_textChanged()
{
totalLines = ui->txtLuaScript->document()->lineCount();
updateLineNum();
}
void MainWindow::on_txtLuaScript_cursorPositionChanged()
{ ui->txtLineNum->verticalScrollBar()->setValue(ui->txtLuaScript->verticalScrollBar()->value());
}
=======================================================================
因为机器人编程中需要读写很多的系统变量,最简单的方法就是使用前面所写的 (2)- 读写C变量 ,但是因为很多变量在C中是数组,如果按2中所示,需要将所有变量都一个个的注册,非常麻烦。
后来想直接将C中的数组共享给Lua,这样在脚本中读写就更简单了,网上查询了差不多2周时间(能力有限:-(...),找到一个帖子 share-array-between-lua-and-c ,完全就是我想要的效果.
注意:帖子中的Answer代码需要根据自己的版本进行修改一下。
此处分享转载如下:
I have really Googled this question but I never really got an solution.
I want to share an Array between C and Lua, for performance I will avoid copying Arrays to and from Lua.
So I want to pass a pointer to the Array from C to Lua. And then from Lua I want to set/modify values in this array directly.
Example in C code
I want to define my array
int mydata[] = {1,2,3,4}
set it global to access it from Lua with the name mydata.
In Lua
I want to change the values like this
mydata[3] = 9
and when I return to C, mydata[3] is 9 because it is a pointer to the array.
How is this possible?
------------ Downside is the best answer ------------------
You can expose arbitrary data to Lua via userdata. If you give your userdata values a metatable, you can define the behavior for various operators/operations on those userdata. In this case, we want to expose an array to Lua and define what to do in the case of array[index] and array[index] = value.
We expose the array to Lua by creating a userdata buffer large enough to hold the address of the array. We define the indexing/assignment behavior by created a metatable with the __index and __newindex methods.
Below is a complete, working example that exposes a static array to Lua. Your program will probably have some other call for returning the array to Lua. Note, there's no boundschecking at all; if you try to index outside the array bounds, you'll crash. To make this more robust, you'd want to change the userdata to a structure which has the array pointer and the array size, so you can do boundschecking.
#include "lauxlib.h"
// metatable method for handling "array[index]"
static int array_index (lua_State* L) {
int** parray = luaL_checkudata(L, 1, "array");
int index = luaL_checkint(L, 2);
lua_pushnumber(L, (*parray)[index-1]);
return 1;
}
// metatable method for handle "array[index] = value"
static int array_newindex (lua_State* L) {
int** parray = luaL_checkudata(L, 1, "array");
int index = luaL_checkint(L, 2);
int value = luaL_checkint(L, 3);
(*parray)[index-1] = value;
return 0;
}
// create a metatable for our array type
static void create_array_type(lua_State* L) {
static const struct luaL_reg array[] = {
{ "__index", array_index },
{ "__newindex", array_newindex },
NULL, NULL
};
luaL_newmetatable(L, "array");
luaL_openlib(L, NULL, array, 0);
}
// expose an array to lua, by storing it in a userdata with the array metatable
static int expose_array(lua_State* L, int array[]) {
int** parray = lua_newuserdata(L, sizeof(int**));
*parray = array;
luaL_getmetatable(L, "array");
lua_setmetatable(L, -2);
return 1;
}
// test data
int mydata[] = { 1, 2, 3, 4 };
// test routine which exposes our test array to Lua
static int getarray (lua_State* L) {
return expose_array( L, mydata );
}
int __declspec(dllexport) __cdecl luaopen_array (lua_State* L) {
create_array_type(L);
// make our test routine available to Lua
lua_register(L, "array", getarray);
return 0;
}
Usage:
require 'array'
foo = array()
print(foo) -- userdata
-- initial values set in C
print(foo[1])
print(foo[2])
print(foo[3])
print(foo[4])
-- change some values
foo[1] = 2112
foo[2] = 5150
foo[4] = 777
-- see changes
print(foo[1])
print(foo[2])
print(foo[3])
print(foo[4])
=======================================================================
=======================================================================
=========================================================================================