1、整体分析OpenPLC的runtime编译st文件的过程

整体过程如上图所示,具体步骤如下。
(1)下载安装OpenPLC的runtime时需要运行以下指令
git clone https://github.com/thiagoralves/OpenPLC_v3.git
cd OpenPLC_v3
./install.sh [platform]
其中platform可以选择win/linux/docker/rpi/custom
(2)install.sh中,存储着执行background_installer.sh的命令
(3)OpenPLC文件夹中的background_installer.sh文件会根据传入的参数来选择不同的OpenPLC runtime的安装方式。
① 如果安装平台是win,则会执行如下代码:
echo "Installing OpenPLC on Windows"
cp ./utils/apt-cyg/apt-cyg ./
install apt-cyg /bin # 安装命令行工具
apt-cyg update
apt-cyg install lynx
apt-cyg install gcc-core gcc-g++ git pkg-config automake autoconf libtool make sqlite3 python3 # 安装OpenPLC所需的编译器、版本控制工具、构建工具和其他依赖库
lynx -source https://bootstrap.pypa.io/pip/get-pip.py > get-pip3.py # 安装Python包管理器pip
#Setting up venv
python3 -m venv "$VENV_DIR"
"$VENV_DIR/bin/python3" get-pip3.py
"$VENV_DIR/bin/python3" -m pip install flask==2.3.3 werkzeug==2.3.7 flask-login==0.6.2 pyserial pymodbus==2.5.3
echo ""
echo "[MATIEC COMPILER]"
cp ./utils/matiec_src/bin_win32/*.* ./webserver/ # 将编译器复制到webserver文件夹,包括iec2c.exe
if [ $? -ne 0 ]; then
echo "Error compiling MatIEC"
echo "OpenPLC was NOT installed!"
exit 1
fi
# 以上代码是在Windows上编译MatIEC编译器,并将编译后的文件复制到webserver目录
# 以下代码都是在执行定义的函数
install_st_optimizer
install_glue_generator
disable_opendnp3
install_libmodbus
finalize_install win
② 如果安装平台是linux,则会执行如下代码:
echo "Installing OpenPLC on Linux"
linux_install_deps sudo
install_py_deps
install_all_libs sudo # 函数中安装了matiec, optimizer, glue_generator...
[ "$2" == "ethercat" ] && install_ethercat
install_systemd_service sudo
finalize_install linux
(4)在搭建的Webserver中,接收到有关的st文件就会调用有关的函数进行编译
OpenPLC-Webserver文件夹中的webserver.py文件
【webserver.py】
import sqlite3 # 一种嵌入式关系型数据库管理系统
import openplc # 与OpenPLC软件配合使用的Python库,提供了与PLC通信和控制相关的功能
import flask # web应用框架,搭建网站
app = flask.Flask(__name__)
openplc_runtime = openplc.runtime()
@app.route('/compile-program', methods=['GET', 'POST']) # URL
def compile_program():
global openplc_runtime # 使用全局变量提前说明
st_file = flask.request.args.get('file') # 获得待编译文件
openplc_runtime.compile_program(st_file)
【openplc.py】
class runtime:
def compile_program(self, st_file):
a = subprocess.Popen(['./scripts/compile_program.sh', str(st_file)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # subprocess创建子进程,执行shell脚本,在主进程中获得子进程的输出
(5)具体是利用OpenPLC-Webserver-scripts文件夹中的compile_program.sh文件进行编译
【compile_program.sh】
#store the active program filename
echo "$1" > ../active_program # 即str(st_file)待编译文件
#compiling the ST file into C
cd .. # 切换上一级目录
echo "Optimizing ST program..."
./st_optimizer ./st_files/"$1" ./st_files/"$1" # 执行可执行文件st_optimizer,优化代码
echo "Generating C files..."
./iec2c -f -l -p -r -R -a ./st_files/"$1" # 编译成C文件
if [ $? -ne 0 ]; then
echo "Error generating C files"
echo "Compilation finished with errors!"
exit 1
fi
#compiling for each platform
cd core
if [……]
……
elif [ "$OPENPLC_PLATFORM" = "linux" ]; then
echo "Compiling for Linux"
echo "Generating object files..."
if [ "$OPENPLC_DRIVER" = "sl_rp4" ]; then
g++ -std=gnu++11 -I ./lib -c Config0.c -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w -DSL_RP4 # 使用g++编译器编译,链接一些库文件
else
g++ -std=gnu++11 -I ./lib -c Config0.c -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w
fi
echo "Generating glueVars..."
./glue_generator
echo "Compiling main program..."
if [ "$OPENPLC_DRIVER" = "sl_rp4" ]; then
g++ -std=gnu++11 *.cpp *.o -o openplc -I ./lib -pthread -fpermissive `pkg-config --cflags --libs libmodbus` -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w $ETHERCAT_INC -DSL_RP4 # 将之前生成的目标文件(objective files)链接起来,并生成可执行文件"openplc"
else
g++ -std=gnu++11 *.cpp *.o -o openplc -I ./lib -pthread -fpermissive `pkg-config --cflags --libs libmodbus` -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w $ETHERCAT_INC
fi
2. 源码仔细分析
安装OpenPLC runtime过程中的三个函数值得关注:install_st_optimizer,install_glue_generator和install_all_libs,这三个函数会编译产生可执行文件——./st_optimizer,./glue_generator,./iec2c
我们仔细分析st_optimizer和glue_generator的有关代码。
(1)在OpenPLC文件夹中的background_installer.sh文件中定义的安装函数
【background_installer.sh】
function install_all_libs { # 安装平台是linux时调用的函数
install_matiec "$1"
install_st_optimizer "$1"
install_glue_generator "$1"
install_opendnp3 "$1"
disable_ethercat "$1"
install_libmodbus "$1"
}
function install_matiec {
echo "[MATIEC COMPILER]"
cd "$OPENPLC_DIR/utils/matiec_src"
autoreconf -i # 自动更新configure脚本及相关文件
./configure # 运行configure脚本,配置编译环境
make # 读取Makefile 的文件,执行编译、链接等操作,生成可执行文件或者其他目标文件
cp ./iec2c "$OPENPLC_DIR/webserver/" || fail "Error compiling MatIEC"
cd "$OPENPLC_DIR"
}
function install_st_optimizer {
echo "[ST OPTIMIZER]"
cd "$OPENPLC_DIR/utils/st_optimizer_src"
g++ st_optimizer.cpp -o "$OPENPLC_DIR/webserver/st_optimizer" || fail "Error compiling ST Optimizer" # 使用g++编译器编译st_optimizer.cpp文件,并将编译后的可执行文件命名为"$OPENPLC_DIR/webserver/st_optimizer"
cd "$OPENPLC_DIR"
}
function install_glue_generator {
echo "[GLUE GENERATOR]"
cd "$OPENPLC_DIR/utils/glue_generator_src"
g++ -std=c++11 glue_generator.cpp -o "$OPENPLC_DIR/webserver/core/glue_generator" || fail "Error compiling Glue Generator" # 使用g++编译器编译glue_generator.cpp文件,并将编译后的可执行文件命名为"$OPENPLC_DIR/webserver/core/glue_generator"
cd "$OPENPLC_DIR"
}
(2)st_optimizer.cpp的源码
位置:OpenPLC-utils-st_optimizer_src文件夹中的st_optimizer.cpp文件
进行代码优化,主要是针对if语句进行优化
(3)glue_generator.cpp的源码
位置:OpenPLC-utils-glue_generator_src文件夹中的glue_generator.cpp文件
【glue_generator.cpp】
int mainImpl(int argc, char *argv[]) # 主函数
{
……
ifstream locatedVars(input_file_name, ios::in);
if (!locatedVars.is_open()) {
cout << "Error opening located variables file at " << input_file_name << endl;
return 1;
} # 通过ifstream对象locatedVars打开名为input_file_name的文件
ofstream glueVars(output_file_name, ios::trunc);
if (!glueVars.is_open()) {
cout << "Error opening glue variables file at " << output_file_name << endl;
return 2;
} # 通过ofstream对象glueVars打开名为output_file_name的文件,打开文件之前清空文件内容
generateHeader(glueVars);
# 将IEC程序中的变量与OpenPLC内存指针进行连接
generateBody(locatedVars, glueVars); # 根据输入流中的数据生成文件的主体部分,其中包括了IEC变量的名称和类型信息
generateBottom(glueVars); #将生成的底部代码添加到 C++ 源文件中,可以方便地引入时间更新功能,如模拟器、游戏引擎或实时数据处理系统
}
void generateHeader(ostream& glueVars)
{
#接受一个ostream类型的参数,即glueVars,然后将一段固定的头文件内容写入到这个输出流中
#定义了一些变量和缓冲区,用于将IEC程序中的变量与OpenPLC内存指针进行连接
}
void generateBody(istream& locatedVars, ostream& glueVars) {
while (parseIecVars(locatedVars, iecVar_name, iecVar_type))
{
glueVar(glueVars, iecVar_name, iecVar_type);
} # 通过parseIecVars函数从输入流locatedVars中解析IEC变量的名称和类型,然后将这些信息传递给glueVar函数,由glueVar函数将这些信息写入到输出流glueVars中
}
(4)install_matiec函数中的Makefile.am文件
Makefile 将会根据规则编译源文件,并链接相应的库文件,最终生成iec2c 和iec2iec两个可执行文件
【Makefile.am】
include common.mk # 定义C++编译器的编译选项
bin_PROGRAMS = iec2c iec2iec # 定义了两个可执行文件iec2c和iec2iec
SUBDIRS = absyntax absyntax_utils stage1_2 stage3 stage4
ACLOCAL_AMFLAGS=-I config
HGVERSION= $(shell hg -R $(top_srcdir) parents --template '{node|short}' 2> /dev/null || grep node $(top_srcdir)/.hg_archival.txt 2> /dev/null || true )
AM_CXXFLAGS += -DHGVERSION="\"${HGVERSION}\""
iec2c_LDADD = stage1_2/libstage1_2.a \
stage3/libstage3.a \
stage4/generate_c/libstage4_c.a \
absyntax/libabsyntax.a \
absyntax_utils/libabsyntax_utils.a # 定义链接时需要的库文件
iec2iec_LDADD = stage1_2/libstage1_2.a \ # 定义链接时需要的库文件
stage3/libstage3.a \
stage4/generate_iec/libstage4_iec.a \
absyntax/libabsyntax.a \
absyntax_utils/libabsyntax_utils.a
iec2c_SOURCES = main.cc # 编译main.cc文件
iec2iec_SOURCES = main.cc
(5)main.cc文件
解析命令行选项、设置运行时选项,并执行MATIEC编译器的4个阶段:
阶段1 - 词法分析器(使用flex实现)
阶段2 - 语法解析器(使用bison实现)
阶段pre3 - 填充符号表(为抽象符号树中的符号提供搜索的符号表)
阶段3 - 语义分析器(目前仅执行类型检查,还未实现)
阶段4 - 代码生成器(使用gcc、javac等工具,生成ANSI C代码)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律