文章目录
一、概述
演示代码工程位置:lua_call_cpp
在编写OpenResty代码时,经常需要调用自己编写的C++模块。本文介绍了两种常用的调用方法:
- 基于LuaJIT的CFFI调用,适用于调用阻塞时间较短的C++模块。
- 基于OpenResty的shell模块调用,对应于Lua的
os.execute
,适用于调用阻塞时间较长的C++模块。
在C++多进程master-worker工作机制较完整的实现,边端和云端协同工作实现博客中提到,在x86服务器端进行轨迹叠加时,需要Lua代码调用C++封装的视频和轨迹叠加模块。最初使用LuaJIT的CFFI机制,但由于视频上的轨迹叠加任务消耗大量CPU资源,导致阻塞了Nginx的工作进程。因此后来转而采用OpenResty的异步调用方法,避免了对Nginx工作进程的阻塞。
二、Lua基于CFFI调用C++模块,适用于OpenResty代码
在封装C++模块时,需要使用extern "C"
将其封装成C模块。传入的C++参数只能使用POD格式,不支持C++的STL,支持C语言的结构体struct
,但不支持含有指针的class
。
示例代码
// video_bbox_drawer.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int drawBBoxOnVideo(const char *inputVideo, const char *bboxString, int bboxStrLen, const char *outputVideo);
#ifdef __cplusplus
}
#endif
-- Lua CFFI调用C++模块示例
local ffi = require("ffi")
ffi.cdef[[
int drawBBoxOnVideo(const char *inputVideo, const char *bboxString, int bboxStrLen, const char *outputVideo, int outputWidth, int outputHeight, int frameRate);
]]
local lib = ffi.load('video_bbox_drawer')
local function read_file(fileName)
local f = assert(io.open(fileName,'r'))
local content = f:read('*all')
f:close()
return content
end
local function test_draw()
local content = read_file("detect_result.txt")
print(#content)
lib.drawBBoxOnVideo("face_1280_720.h264", content , #content, "face_1280_720_lua.mp4")
end
test_draw()
三、Lua异步调用C++可执行程序(OpenResty代码使用shell模块调用)
使用Lua的os.execute
调用方法如下:
local function read_file(fileName)
local f = assert(io.open(fileName,'r'))
local content = f:read('*all')
f:close()
return content
end
local function test_draw()
local content = read_file("detect_result.txt")
print(#content)
local cmd = "./bin/video_bbox_drawer" .. " " ..
"face_1280_720.h264" .. " " ..
content .. " " ..
#content .. " "..
"face_1280_720_output.h264"
os.execute(cmd)
-- 在OpenResty代码中应使用shell.run调用,而非os.execute,以避免阻塞Nginx工作进程
-- 如:local ok, stdout, stderr, reason, status = shell.run(cmd, nil, timeout)
end
test_draw()
推荐在OpenResty代码中使用官方提供的shell模块进行调用,使用方法类似于os.execute
:
local shell = require("resty.shell")
local function test_draw()
local content = read_file("detect_result.txt")
print(#content)
local cmd = "./bin/video_bbox_drawer" .. " " ..
"face_1280_720.h264" .. " " ..
content .. " " ..
#content .. " "..
"face_1280_720_output.h264"
local ok, stdout, stderr, reason, status = shell.run(cmd, nil, timeout)
-- 处理调用结果
end
test_draw()
四、同一份代码快速适配不同的调用方式
观察lua_call_cpp代码,可以看出,无论是通过CFFI调用的C++库还是通过C++可执行程序,实际上都是同一份C++代码。只需在编译时使用不同的CMake配置即可实现适配:
project(video_bbox_drawer)
cmake_minimum_required( VERSION 3.0 )
aux_source_directory(src DIR_SRCS)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 编译为C++可执行程序供Lua调用
#add_executable(${PROJECT_NAME} ${DIR_SRCS})
# 编译为C动态库供Lua调用
add_library(${PROJECT_NAME} SHARED ${DIR_SRCS})
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
target_link_libraries(${PROJECT_NAME} PRIVATE
pthread
opencv_core
opencv_highgui
opencv_imgproc
opencv_videoio
)
endif()
在CMakeLists.txt中,通过注释掉add_executable
并启用add_library
,即可分别编译出可执行程序和动态库供Lua调用。
# 编译为C++可执行程序供Lua调用
#add_executable(${PROJECT_NAME} ${DIR_SRCS})
# 编译为C动态库供Lua调用
add_library(${PROJECT_NAME} SHARED ${DIR_SRCS})