【DCS开源项目】—— Lua 如何调用 DLL、DLL 与 DCS World 的交互



1. Lua 调用 C++ DLL 的机制

入口与注册

  • 在 DCS World 的 Mods 目录下,Olympus 以插件形式加载,Lua 脚本(如 entry.lua)声明插件并初始化。
  • 主要 Lua 脚本(如 OlympusCommand.lua)负责加载 DLL,并通过 require("olympus")package.loadlib 方式调用 C++ 导出的 Lua C API。
entry.lua
local self_ID = "DCS-Olympus"

declare_plugin(self_ID,
{
	image		 = "Olympus.png",
	installed	 = true, -- if false that will be place holder , or advertising
	dirName		 = current_mod_path,
	binaries	 =
	{
--		'Olympus',
	},
	load_immediately = true,

	displayName	 = "Olympus",
	shortName	 = "Olympus",
	fileMenuName = "Olympus",

	version		 = "{{OLYMPUS_VERSION_NUMBER}}",
	state		 = "installed",
	developerName= "DCS Refugees 767 squadron",
	info		 = _("DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available."),

	Skins	=
	{
		{
			name	= "Olympus",
			dir		= "Theme"
		},
	},

	Options =
	{
		{
			name		= "Olympus",
			nameId		= "Olympus",
			dir			= "Options",
			CLSID		= "{Olympus-options}"
		},
	},
})

plugin_done()

DLL 导出接口

  • C++ 侧通过 extern "C" DllExport int luaopen_olympus(lua_State *L) 导出模块初始化函数,供 Lua 加载。
  • 在 olympus.cpp 中,注册了一系列 Lua 可调用的 C 函数(如 onSimulationStartonSimulationFramesetUnitsData 等),这些函数会被 Lua 脚本直接调用。
onSimulationStart代码
//olympus.cpp

static int onSimulationStart(lua_State* L)
{
    LogInfo(L, "Trying to load core.dll from " + modPath);
    SetDllDirectoryA(modPath.c_str());

    setLogDirectory(modPath);

    log("onSimulationStart callback called successfully");

    string dllLocation = modPath + "\\core.dll";
    
    log("Loading core.dll");
    hGetProcIDDLL = LoadLibrary(to_wstring(dllLocation).c_str());

    if (!hGetProcIDDLL) {
        LogError(L, "Error loading core DLL");
        goto error;
    }

    log("Core DLL loaded successfully");

    coreInit = (f_coreInit)GetProcAddress(hGetProcIDDLL, "coreInit");
    if (!coreInit) 
    {
        LogError(L, "Error getting coreInit ProcAddress from DLL");
        goto error;
    }

    coreDeinit = (f_coreDeinit)GetProcAddress(hGetProcIDDLL, "coreDeinit");
    if (!coreDeinit)
    {
        LogError(L, "Error getting coreDeinit ProcAddress from DLL");
        goto error;
    }

    coreFrame = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreFrame");
    if (!coreFrame) 
    {
        LogError(L, "Error getting coreFrame ProcAddress from DLL");
        goto error;
    }

    coreUnitsData = (f_coreUnitsData)GetProcAddress(hGetProcIDDLL, "coreUnitsData");
    if (!coreUnitsData)
    {
        LogError(L, "Error getting coreUnitsData ProcAddress from DLL");
        goto error;
    }

    coreWeaponsData = (f_coreWeaponsData)GetProcAddress(hGetProcIDDLL, "coreWeaponsData");
    if (!coreWeaponsData)
    {
        LogError(L, "Error getting coreWeaponsData ProcAddress from DLL");
        goto error;
    }

    coreMissionData = (f_coreMissionData)GetProcAddress(hGetProcIDDLL, "coreMissionData");
    if (!coreMissionData)
    {
        LogError(L, "Error getting coreMissionData ProcAddress from DLL");
        goto error;
    }

    coreDrawingsData = (f_coreDrawingsData)GetProcAddress(hGetProcIDDLL, "coreDrawingsData");
    if (!coreDrawingsData)
    {
        LogError(L, "Error getting coreDrawingsData ProcAddress from DLL");
        goto error;
    }

    coreInit(L, modPath.c_str());

    LogInfo(L, "Module loaded and started successfully.");

	return 0;

error:
    LogError(L, "Error while loading module: " + GetLastErrorAsString());
    return 0;
}
onSimulationFrame代码
static int onSimulationFrame(lua_State* L)
{
    if (coreFrame) 
    {
        coreFrame(L);
    }
    return 0;
}
setUnitsData代码
//olympus.cpp

static int setUnitsData(lua_State* L)
{
    if (coreUnitsData)
    {
        coreUnitsData(L);
    }
    return 0;
}

生命周期与回调

  • Lua 脚本在仿真开始、每帧、仿真结束等时机,调用 C++ DLL 的接口(如 onSimulationStartonSimulationFrameonSimulationStop),这些接口内部会进一步调用核心 DLL(如 core.dll)的导出函数(如 coreInitcoreFramecoreDeinit 等)。
onSimulationStart代码
//olympus.cpp

static int onSimulationStart(lua_State* L)
{
    LogInfo(L, "Trying to load core.dll from " + modPath);
    SetDllDirectoryA(modPath.c_str());

    setLogDirectory(modPath);

    log("onSimulationStart callback called successfully");

    string dllLocation = modPath + "\\core.dll";
    
    log("Loading core.dll");
    hGetProcIDDLL = LoadLibrary(to_wstring(dllLocation).c_str());

    if (!hGetProcIDDLL) {
        LogError(L, "Error loading core DLL");
        goto error;
    }

    log("Core DLL loaded successfully");

    coreInit = (f_coreInit)GetProcAddress(hGetProcIDDLL, "coreInit");
    if (!coreInit) 
    {
        LogError(L, "Error getting coreInit ProcAddress from DLL");
        goto error;
    }

    coreDeinit = (f_coreDeinit)GetProcAddress(hGetProcIDDLL, "coreDeinit");
    if (!coreDeinit)
    {
        LogError(L, "Error getting coreDeinit ProcAddress from DLL");
        goto error;
    }

    coreFrame = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreFrame");
    if (!coreFrame) 
    {
        LogError(L, "Error getting coreFrame ProcAddress from DLL");
        goto error;
    }

    coreUnitsData = (f_coreUnitsData)GetProcAddress(hGetProcIDDLL, "coreUnitsData");
    if (!coreUnitsData)
    {
        LogError(L, "Error getting coreUnitsData ProcAddress from DLL");
        goto error;
    }

    coreWeaponsData = (f_coreWeaponsData)GetProcAddress(hGetProcIDDLL, "coreWeaponsData");
    if (!coreWeaponsData)
    {
        LogError(L, "Error getting coreWeaponsData ProcAddress from DLL");
        goto error;
    }

    coreMissionData = (f_coreMissionData)GetProcAddress(hGetProcIDDLL, "coreMissionData");
    if (!coreMissionData)
    {
        LogError(L, "Error getting coreMissionData ProcAddress from DLL");
        goto error;
    }

    coreDrawingsData = (f_coreDrawingsData)GetProcAddress(hGetProcIDDLL, "coreDrawingsData");
    if (!coreDrawingsData)
    {
        LogError(L, "Error getting coreDrawingsData ProcAddress from DLL");
        goto error;
    }

    coreInit(L, modPath.c_str());

    LogInfo(L, "Module loaded and started successfully.");

	return 0;

error:
    LogError(L, "Error while loading module: " + GetLastErrorAsString());
    return 0;
}
onSimulationFrame代码
//olympus.cpp

static int onSimulationFrame(lua_State* L)
{
    if (coreFrame) 
    {
        coreFrame(L);
    }
    return 0;
}
onSimulationStop代码
//olympus.cpp

static int onSimulationStop(lua_State* L)
{
    log("onSimulationStop callback called successfully");
    if (hGetProcIDDLL)
    {
        log("Trying to unload core DLL");
        if (coreDeinit)
        {
            coreDeinit(L);
        }

        if (FreeLibrary(hGetProcIDDLL))
        {
            log("Core DLL unloaded successfully");
        }
        else
        {
            LogError(L, "Error unloading DLL");
            goto error;
        }

        coreInit = nullptr;
        coreDeinit = nullptr;
        coreFrame = nullptr;
        coreUnitsData = nullptr;
        coreWeaponsData = nullptr;
        coreMissionData = nullptr;

        coreDrawingsData = nullptr;
    }

    hGetProcIDDLL = NULL;

    return 0;

error:
    LogError(L, "Error while unloading module: " + GetLastErrorAsString());
    return 0;
}
coreInit代码
//core.cpp

extern "C" DllExport int coreInit(lua_State* L, const char* path)
{
    instancePath = path;

    log("Initializing core.dll with instance path " + instancePath);

    sessionHash = random_string(16);

    log("Random session hash " + sessionHash);

    unitsManager = new UnitsManager(L);
    weaponsManager = new WeaponsManager(L);
    server = new Server(L);
    scheduler = new Scheduler(L);

    registerLuaFunctions(L);

    server->start(L);

    unitsManager->loadDatabases();

    initialized = true;
    return(0);
}
coreFrame代码
//core.cpp

extern "C" DllExport int coreFrame(lua_State* L)
{
    if (!initialized)
        return (0);

    /* Lock for thread safety */
    lock_guard<mutex> guard(mutexLock);

    frameCounter++;

    const std::chrono::duration<double> executionDuration = std::chrono::system_clock::now() - lastExecution;
    if (executionDuration.count() > (20 * FRAMERATE_TIME_INTERVAL)) {
        if (executionDuration.count() > 0) {
            scheduler->setFrameRate(frameCounter / executionDuration.count());
            frameCounter = 0;
        }

        lastExecution = std::chrono::system_clock::now();
    }

    if (scheduler != nullptr) 
        scheduler->execute(L);

    return(0);
}
coreDeinit代码
//core.cpp

extern "C" DllExport int coreDeinit(lua_State* L)
{
    if (!initialized)
        return (0);

    log("Olympus coreDeinit called successfully");

    server->stop(L);

    delete unitsManager;
    delete weaponsManager;
    delete server;
    delete scheduler;

    log("All singletons objects destroyed successfully");

    return(0);
}

2. DLL 与 DCS World 的交互

Lua State 共享

  • DCS World 本身支持 Lua 脚本扩展,Olympus 插件运行在 DCS 的 Lua 虚拟机环境中。
  • C++ DLL 的所有接口都接收 lua_State* L 参数,直接操作 DCS 的 Lua 虚拟机,实现与 DCS 内部数据和函数的交互。
//lua.h

typedef struct lua_State lua_State;

typedef int (*lua_CFunction) (lua_State *L);
示例
// 一个简单的 C 函数,返回两个值:a+b 和 a*b
static int add_and_multiply(lua_State *L) {
    double a = luaL_checknumber(L, 1);  // 获取第一个参数
    double b = luaL_checknumber(L, 2);  // 获取第二个参数
    
    lua_pushnumber(L, a + b);  // 压入第一个返回值 (a+b)
    lua_pushnumber(L, a * b);  // 压入第二个返回值 (a*b)
    
    return 2;  // 告诉 Lua 此函数返回两个值
}

// 注册到 Lua 中
lua_register(L, "add_and_multiply", add_and_multiply);
sum, product = add_and_multiply(3, 4)
print(sum, product)  --> 7   12

这两个定义是 Lua C API 的基础:

  • lua_State 是 Lua 与 C 交互的桥梁,管理解释器状态。
  • lua_CFunction 允许你用 C 编写函数并注册到 Lua 中,实现高性能扩展。

理解这两个概念是开发 Lua C 扩展库(如游戏引擎、网络库)的关键。

数据交互方式

  • 数据采集:C++ 通过调用 DCS 导出的 Lua 函数(如 Export.LoGetWorldObjects),获取仿真世界中的单位、武器等数据,并转换为 C++ 的 JSON 结构。
    • 例如 getAllUnits 函数,调用 Lua 全局的 Export.LoGetWorldObjects,遍历 Lua 表,将其转为 C++ 可用的数据结构。
  • 命令下发:C++ 可以通过 dostring_in 等函数,在 DCS 的不同 Lua 环境(如 net)中执行 Lua 命令,实现对仿真世界的控制(如生成单位、移动、攻击等)。
  • 日志与调试:C++ 通过调用 Lua 的日志接口(如 log.write),将信息输出到 DCS 的日志系统,便于调试和监控。
dcstools.cpp
#include "dcstools.h"

void LogInfo(lua_State* L, string message)
{
    STACK_INIT;

    lua_getglobal(L, "log");
    lua_getfield(L, -1, "INFO");
    int infoLevel = (int)lua_tointeger(L, -1);
    STACK_POP(1);

    STACK_CLEAN;

    Log(L, message, infoLevel);
}

void LogWarning(lua_State* L, string message)
{
    STACK_INIT;

    lua_getglobal(L, "log");
    lua_getfield(L, -1, "WARNING");
    int warningLevel = (int)lua_tointeger(L, -1);
    STACK_POP(1);

    STACK_CLEAN;

    Log(L, message, warningLevel);
}

void LogError(lua_State* L, string message)
{
    STACK_INIT;

    lua_getglobal(L, "log");
    lua_getfield(L, -1, "ERROR");
    int errorLevel = (int)lua_tointeger(L, -1);
    STACK_POP(1);

    STACK_CLEAN;

    Log(L, message, errorLevel);
}

void Log(lua_State* L, string message, unsigned int level)
{
    STACK_INIT;

    lua_getglobal(L, "log");
    lua_getfield(L, -1, "write");
    lua_pushstring(L, "Olympus.dll");
    lua_pushnumber(L, level);
    lua_pushstring(L, message.c_str());
    lua_pcall(L, 3, 0, 0);

    STACK_CLEAN;
}

void getAllUnits(lua_State* L, map<unsigned int, json::value>& unitJSONs)
{
    unsigned int res = 0;

    STACK_INIT;

    lua_getglobal(L, "Export");
    lua_getfield(L, -1, "LoGetWorldObjects");
    res = lua_pcall(L, 0, 1, 0);

    if (res != 0)
    {
        LogError(L, "Error retrieving World Objects");
        goto exit;
    }

    if (!lua_istable(L, 2))
    {
        LogError(L, "Error retrieving World Objects");
        goto exit;
    }
    else
    {
        lua_pushnil(L);
        while (lua_next(L, 2) != 0)
        {
            unsigned int ID = static_cast<unsigned int>(lua_tonumber(L, -2));
            if (unitJSONs.find(ID) == unitJSONs.end())
                unitJSONs[ID] = json::value::object();
            luaTableToJSON(L, -1, unitJSONs[ID]);
            STACK_POP(1)
        }
    }

exit:
    STACK_CLEAN;
    return;
}

int dostring_in(lua_State* L, string target, string command)
{
    lua_getglobal(L, "net");
    lua_getfield(L, -1, "dostring_in");
    lua_pushstring(L, target.c_str());
    lua_pushstring(L, command.c_str());
    return lua_pcall(L, 2, 0, 0);
}

unsigned int TACANChannelToFrequency(unsigned int channel, char XY)
{
    unsigned int basef = (XY == 'X' && channel > 63) || (XY == 'Y' && channel < 64) ? 1087 : 961;
    return (basef + channel) * 1000000;
}

线程与同步

  • C++ 侧通过互斥锁(mutexLock)保证多线程环境下的数据一致性,防止 Lua/C++ 混合调用时出现竞态。

3. 典型流程举例

  1. 仿真启动时

    • Lua 脚本调用 olympus.onSimulationStart(),C++ 加载 core.dll,初始化各个管理器(单位、武器、调度、服务器等),注册自定义 Lua 函数,启动 HTTP 服务器用于前端交互。
  2. 每帧更新

    • Lua 脚本每帧调用 olympus.onSimulationFrame(),C++ 执行调度器逻辑,处理命令队列、更新单位和武器状态。
  3. 数据同步

    • Lua 脚本定期调用 olympus.setUnitsData()olympus.setWeaponsData() 等,将 Lua 世界中的最新数据同步到 C++,C++ 解析 Lua 表并更新内部状态。
  4. 命令执行

    • 前端通过 HTTP 请求下发命令,C++ 服务器解析后,通过 dostring_in 在 DCS 的 Lua 环境中执行相应的 Lua 脚本,实现对仿真世界的实时控制。
    //dcstools.cpp
    
    int dostring_in(lua_State* L, string target, string command)
    {
        lua_getglobal(L, "net");
        lua_getfield(L, -1, "dostring_in");
        lua_pushstring(L, target.c_str());
        lua_pushstring(L, command.c_str());
        return lua_pcall(L, 2, 0, 0);
    }
    

4. 总结

  • Lua 作为胶水层,负责插件声明、生命周期管理、数据同步和命令下发。
  • C++ DLL 作为核心逻辑层,负责高性能的数据处理、复杂逻辑实现、与 DCS Lua 环境的深度交互,并对外提供 HTTP 服务。
  • 与 DCS World 的交互,本质是通过共享 Lua 虚拟机和调用 DCS 导出的 Lua API 实现的。

这种架构实现了 DCS World 的深度二次开发和实时控制,既保证了灵活性,也兼顾了性能和可维护性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PH_modest

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值