上一 篇 我们把 SDK 和环境 都 准备 好了,这篇 直接 进入 正题:新建一个 能被 X-Plane 加载 的 插件。很多人 卡在 这 一步, 不是 代码 写 不出来, 而是 不知道 一个 插件 项目 到底 该怎么 建、 该 配 哪些 参数、 编译 出来 的 文件 怎么 放。 这 篇 我 用 Windows + Visual Studio 2022 从 零 走 一遍, 最终 得到 一个 会 在 日志 里 留 下 痕迹 的 win.xpl 文件。 不管 你 之前 是 玩 过 IDEA 插件、 Qt 插件 还是 前端 工具 扩展, 只要 明白 “宿主 提供 API, 插件 实现 入口, 宿主 在 特定 时机 调用” 这套 模型, 学 X-Plane 插件 会 非常 顺畅。 教程 默认 你 有 一 点 C/C++ 基础, 至少 知道 函数、 指针、 编译 链接; 零 基础 也 可以 先 照 着 敲, 跑 通 之后 再 回头 补 细节。
1. 动手之前,先搞懂X-Plane插件到底是个啥
1.1 从“飞机里的一个小程序”讲起
X-Plane 插件 本质 上 就是 一个 动态 链接 库。 Windows 上 是 DLL, Mac 上 是 dylib, Linux 上 是 so。 它 没有 独立 的 main 函数, 也 不会 自己 启动, 而是 由 X-Plane 主 程序 在 启动 或 运行 中 动态 加载, 按照 约定 调用 里面 几个 固定 名字 的 导出 函数。 你 可以 把 X-Plane 想象 成 一架 飞机, 插件 是 你 塞 进 飞机 里 的 一台 小 设备; 设备 本身 不 会 飞, 是 飞机 给 它 供电、 提供 数据, 它 才能 工作。 SDK 就是 设备 和 飞机 之间 的 接口 规范。
“加载 进 进程” 这 件 事 很 重要。 插件 运行 在 X-Plane 的 进程 空间 里, 内存 和 崩溃 范围 都 和 主 程序 一致。 所以 插件 里 如果 写 了 一个 野 指针 越 界 访问, 崩 的 不是 插件 自己, 而是 整个 X-Plane。 这 和 写 独立 的 控制台 程序 完全不同, 和 写 浏览器 扩展、 IDE 插件 的 风险 模型 倒 是 很 像。
1.2 SDK 里有什么
下载 解压 之后, SDK 目录 下 有 几个 核心 部分: Include 目录 是 全部 头文件, Libraries 目录 是 各 平台 预编译 好 的 静态 导入 库, Examples 目录 有 官方 示例 代码。 头文件 里 最 常用 的 包括 XPLMPlugin.h、 XPLMUtilities.h、 XPLMDataAccess.h、 XPLMMenus.h。 文件 名 中 的 PLM 全称 Plugin Manager, 后来 SDK 沿用 了 这个 缩写。 你 可能 在 文档 里 看到 XPLM300 这种 版本 号, 它 表示 插件 API 版本 300, 对应 X-Plane 11/12。 我们 不 用 死记, 但 要 知道 宿主 和 SDK 版本 需要 兼容。
Libraries 目录 下 的 .lib 文件 是 导入 库, 里面 记录 了 X-Plane 主 程序 导出 的 API 符号。 写 代码 时 include 头文件, 链接 时 把 对应 的 .lib 告诉 链接 器, 生成 的 插件 体积 可以 很 小, 因为 真 正 的 逻辑 实现 都 在 X-Plane 主 程序 里。 这 也就是说, 插件 的 编译 流程 和 普通 DLL 基本 一样, 只是 需要 额外 导 出 几个 特定 函数。
1.3 五个必须导出的回调函数
无论 插件 功能 多 复杂, 都 必须 实现 5 个 回调 函数。 XPLMPlugin.h 里 有 明确 注释, 我 整理 成 表 方便 看:
| 函数 | 调用时机 | 必须 返回 | 主要 用途 |
|---|---|---|---|
| XPluginStart | 插件 被 加载 时 | int, 非 0 表示 初始化 成功 | 填写 插件 名称、 签名、 描述, 做 基础 初始化 |
| XPluginStop | 插件 被 卸载 或 X-Plane 退出 | void | 释放 资源, 清理 菜单、 注册 等 |
| XPluginEnable | 插件 被 启用 时 | int, 非 0 表示 启用 成功 | 可 以 开始 使用 功能, 比如 开启 计时器 |
| XPluginDisable | 插件 被 禁用 时 | void | 停止 功能, 但 不要 释放 全局 资源 |
| XPluginReceiveMessage | 收到 消息 时 | void | 处理 其他 插件 或 X-Plane 发来 的 消息 |
有 人 会 疑惑: 有 了 Start/Stop, 为什么 还要 Enable/Disable? 因为 X-Plane 允许 用户 在 运行 时 通过 “插件 管理器” 禁用 某个 插件。 禁用 时 XPluginDisable 被 调用, 插件 要 停下 所有 正在 干活 的 部分, 但 不能 把 内部 数据 结构 销毁 掉, 这样 再次 启用 时 XPluginEnable 被 调用, 能 快速 恢复。 这 种 设计 在 IDE 插件 的 activate/deactivate 概念 里 也 能 看到。 如果 你 只 写 了 Start 和 Stop, 没有 考虑 禁用 场景, 那么 用户 在 运行 中 禁用 再 启用 你 的 插件, 很可能 出现 状态 丢失 甚至 崩溃。
2. 新建项目:从零到能编译出win.xpl
2.1 环境说明与配置清单
我 的 环境 是 Windows 11 + Visual Studio 2022 Community + X-Plane 12 正式 版, SDK 从 developer.x-plane.com 下载 的 最新 稳定 版。 用 Xcode 或 Linux 工具 链 的 话, 思路 一模一样, 只是 .lib/.xpl 文件 名 和 导出 方式 略 有 不同。 我 建议 新 入 坑 的 朋友 先 在 Windows + VS 上 跑 通, 因为 社区 里 大 部分 踩坑 案例 都 是 Windows 的, 遇到 问题 容易 搜 到。
项目 目录 我 放在 D:\dev\xplane\MyFirstPlugin, SDK 解压 到 D:\dev\xplane\SDK。 这样 多个 插件 项目 可以 共用 一份 SDK, 不用 每个 项目 都 拷贝 一份 头文件 和 库。 如果 你 的 X-Plane 安装 在 C 盘 系统 目录, 建议 把 开发 目录 放 到 别 的 盘, 避免 权限 问题 影响 编译 和 文件 拷贝。
2.2 Visual Studio 空项目配置步骤
打开 VS2022, 创建 新项目, 选 “空项目”, 语言 选 C++, 项目 名 MyFirstPlugin。 创建 完 之后 项目 里 什么 都 没有, 需要 手工 加 文件、 改 属性。
第一, 添加 源码 文件。 项目 名 右键 -> 添加 -> 新建项 -> C++ 文件, 命名 main.cpp。 虽 然 我们 写 的 是 C 风格 代码, 但 用 .cpp 后缀 没 任何 问题, XPLM 头文件 有 extern “C” 保护。
第二, 配置 include 目录。 项目 属性 -> 配置属性 -> C/C++ -> 常规 -> 附加包含目录, 添加 D:\dev\xplane\SDK\Include。 注意 是 顶层 Include 目录, 里面 所有 XPLM*.h 都 在 那里。
第三, 配置 平台。 在 工具栏 上 把 解决方案 平台 从 Win32 切到 x64, 配置 选 Release。 Debug 模式 不是 完全 不行, 但 会有 额外 的 运行时 依赖, 之后 专门 讲。 新手 阶段 用 Release 最 稳。
第四, 配置 链接 库。 链接 器 -> 常规 -> 附加库目录, 添加 SDK 里 Libraries/Win/ 下 对应 编译器 版本 的 目录。 然后 链接 器 -> 输入 -> 附加依赖项, 添加 XPLM_64.lib。 具体 文件名 以 你 下载 的 SDK 为 准, 有的 版本 是 XPLM.lib 和 XPLM_64.lib 同时 存在, 选 带 _64 的 那个 就是 64 位 导入 库。 如果 之后 用到 控件 系统, 再 补 上 XPWidgets_64.lib。
2.3 最小插件的源码
新建 main.cpp, 输入 下面 代码。 这是 一 个 最小 可 运行 的 插件, 唯一 作用 就是 让 X-Plane 加载 它, 并且 在 日志 里 打印 几 行 字。
#include <string.h> #include "XPLMPlugin.h" #include "XPLMUtilities.h" PLUGIN_API int XPluginStart(char *outName, char *outSig, char *outDesc) { strcpy(outName, "MyFirstPlugin"); strcpy(outSig, "com.example.myfirstplugin"); strcpy(outDesc, "A minimal X-Plane plugin"); XPLMDebugString("MyFirstPlugin: XPluginStart called\n"); return 1; } PLUGIN_API void XPluginStop(void) { XPLMDebugString("MyFirstPlugin: XPluginStop called\n"); } PLUGIN_API int XPluginEnable(void) { XPLMDebugString("MyFirstPlugin: XPluginEnable called\n"); return 1; } PLUGIN_API void XPluginDisable(void) { XPLMDebugString("MyFirstPlugin: XPluginDisable called\n"); } PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void *inParam) { (void)inFrom; (void)inMsg; (void)inParam; }PLUGIN_API 是 XPLM 头文件 里 定义 的 宏。 Windows 上 它 会 展开 成 __declspec(dllexport), 保证 这 5 个 函数 出现在 DLL 的 导出 表 里。 Mac/Linux 上 它 会 对应 相应 的 可见性 修饰。 所以 跨 平台 代码 里 使用 这个 宏 就 够 了, 不用 自己 判断 平台。
XPluginStart 的 三个 参数 是 输出 缓冲区, 宿主 已经 分配 好 内存, 你 必须 用 strcpy 把 内容 拷 进去, 而 不是 直接 把 outName 指向 一个 局部 字符串。 如果 乱 指 指针, 宿主 拿 到 的 就是 栈 上 的 垃圾 数据, 插件 管理器 里 可能 显示 乱码 甚至 直接 加载 失败。
2.4 编译并检查输出
直接 点 生成 解决 方案。 如果 没有 报错, 在 项目 目录 的 Release/x64 下 会 出现 MyFirstPlugin.dll。 这 个 文件 要 改名 成 win.xpl 才 能 被 X-Plane 认识。 手动 复制 每次 都 容易 忘, 我 习惯 在 项目 属性 -> 生成事件 -> 后期生成事件 里 加 一条 命令:
xcopy /Y "$(OutDir)MyFirstPlugin.dll" "G:\X-Plane12\Resources\plugins\MyFirstPlugin\64\win.xpl"把 路径 换成 你 自己 的 X-Plane 安装 目录。 这样 每次 编译 完成 自动 拷贝, 省 掉 来回 拖 文件 的 时间。 注意 这个 命令 里 的 64 子目录 必须 存在, 可以 在 命令 里 先 mkdir, 或者 提前 建 好。
3. 配置过程里的关键细节与踩坑记录
3.1 平台位数:32还是64,为什么必须对齐
这 个 问题 我 觉得 值得 单独 说, 因为 踩 过 的 人 太多。 X-Plane 11 和 12 都 是 64 位 应用, 所以 插件 必须 是 64 位 DLL。 但 网上 很 多 老 教程 还 在 用 32 位 配置, 因为 X-Plane 10 时代 有 32 位 客户端。 如果 你 编译 出 32 位 win.xpl, 放 到 64 位 X-Plane 里, 日志 里 会 出现 类似 “Skipping plugin ... invalid architecture” 的 提示, 插件 根本 不会 加载。
VS 默认 的 解决方案 平台 可能 是 Win32, 必须 手动 切 到 x64。 否则 即便 你 代码 没 问题, 链接 器 拿 到 的 也 是 32 位 的 运行 库 和 导入 库。 想 确认 生成 的 DLL 是否 是 64 位, 可以 用 开发者 命令 提示 符 运行:
dumpbin /headers MyFirstPlugin.dll | findstr machine看到 x64 就是 对 的, 看到 x86 就要 重新 编译。 这 个 检查 在 排查 加载 问题 时 非常 有 用。
3.2 字符集与C运行时:两个容易翻车的坑
Visual Studio 新 项目 默认 使用 Unicode 字符 集。 但 X-Plane SDK 的 所有 接口 都 是 char* 单 字节 版本, 没有 宽 字符 重载。 把 项目 属性 -> 常规 -> 字符集 改 成 “使用 多字节 字符 集” 能 避免 很多 Windows API 泛型 别名 带来 的 编译 问题。 虽然 你 写 C++ 代码 可能 不用 直接 调用 Windows API, 但 插件 后期 通常 要 操作 文件、 注册 表、 消息 框, 提前 改 好 省 得 之后 突然 编译 报错。
另一个 更 隐蔽 的 坑 是 C 运行时 库 选择。 Release 配置 下 默认 是 “多线程 DLL (/MD)”, 这 个 一定 不要 改 成 “多线程 (/MT)”。 原因 很 简单: 插件 是 被 X-Plane 进程 加载 的, X-Plane 自己 用 的 是 动态 CRT。 如果 插件 静态 链 入 CRT, 那 插件 和 主 程序 各 有 一套 堆 管理, 插件 里 malloc 出来 的 内存 指针 要是 传 给 X-Plane 或 者 反向 传递, 就 可能 因为 堆 不 一致 而 崩溃。 这种 崩溃 特别 难 查, 因为 代码 看 起来 完全 正常, 但 偶尔 崩 一次。 所以 从 一 开始 就 锁 死 /MD。
3.3 导出符号:.def文件怎么配
有人 不 喜欢 依赖 PLUGIN_API 宏, 那 也 可以 用 模块 定义 文件 显式 导出。 在 项目 里 新建 一个 myplugin.def, 内容:
EXPORTS XPluginStart XPluginStop XPluginEnable XPluginDisable XPluginReceiveMessage然后 在 项目 属性 -> 链接器 -> 输入 -> 模块定义文件 里 指定 这 个 def。 这样 生成的 DLL 导出 表 就 只有 这 5 个 函数, 干净 且 可控。 但 我 个人 还是 推荐 用 PLUGIN_API 宏, 因为 代码 跨 平台 编译 时 不用 为 每 个 平台 维护 def。 不管 用 哪种 方式, 导出 函数 的 名称 一定 要 和 XPLM 文档 里 完全 一致, 大小 写 也不行。 X-Plane 加载 时 通过 GetProcAddress 找 这些 符号, 少 一个 字母 就 会 直接 说 “Unable to load”。
3.4 把插件装进X-Plane:目录命名有讲究
X-Plane 扫描 的 根目录 是 Resources/plugins。 每 个 插件 通常 是 一 个 子目录, 里面 按 平台 再 分 一层。 比如 我 的 目标 路径 是:
G:\X-Plane12\Resources\plugins\MyFirstPlugin\64\win.xpl其中 64 表示 64 位 架构, win.xpl 是 Windows 平台 的 文件 名。 如果 你 还 想 支持 Linux, 再 加 一个 64/lin.xpl; Mac 是 64/mac.xpl。 这 是 X-Plane 的 硬性 规定, 不 能 改。 有 时候 看到 别人 的 插件 目录 里 是 32/win.xpl, 那 是 为了 兼容 旧版 32 位 客户端, 对 现在 的 X-Plane 12 没 意义。
需要 注意, Resources/plugins 下 的 子目录 才是 插件, 直接 把 win.xpl 扔 到 plugins 根目录 通常 不会 被 加载。 子目录 名 可以 任意, 但 建议 用 插件 名, 方便 识别。 另外 如果 你 的 插件 文件 名 不是 win.xpl 而是 MyFirstPlugin.xpl, X-Plane 也 不会 认。 每次 拷贝 完 之后 检查 一下 文件 名 和 路径, 这种 低级 错误 真 的 会 浪费 半 小时。
4. 在X-Plane里跑起来:加载验证与调试技巧
4.1 启动X-Plane,看Log.txt
安装 好 之后 启动 X-Plane, 然后 打开 X-Plane 根目录 下 的 Log.txt。 这 个 文件 每次 启动 都 会 覆盖, 是 排 查 插件 问题 的 第一 入口。 搜索 你 的 插件 签名 com.example.myfirstplugin, 如果 看到:
Loaded: Resources/plugins/MyFirstPlugin/64/win.xpl (com.example.myfirstplugin).说明 加载 成功。 再 往 下 翻, 应该 能 找到 我们 用 XPLMDebugString 输出 的 “MyFirstPlugin: XPluginStart called”。 如果 没有 这 行, 说明 XPluginStart 没有 被 调用 或 者 调用 时 崩溃 了。 注意 Log.txt 日志 会 有 延迟 写入 的 情况 吗? X-Plane 通常 是 边 写 边 刷 新, 但 如果 崩溃 在 XPluginStart 内部, 日志 可能 停 在 加载 那一 行。 这时 就 要 用 下 面 的 调试 方法。
我 建议 开发 期间 把 Log.txt 固定 在 一个 编辑 器 标签 页, 每次 运行 前 清空 或 者 记住 当前 末尾 位置, 这样 插件 输出 能 第一时间 捕捉 到。
4.2 菜单项:让用户能点到你的插件
只 写 日志 的 插件 无法 交互, 我们 给 它 加 一个 菜单 项。 这 一步 会 让 你 看到 插件 完整 的 “加载 -> 用户 触发 -> 输出” 流程。 先 include XPLMMenus.h, 然后 在 XPluginStart 里 创建 菜单。
#include "XPLMMenus.h" static XPLMMenuID gMenuID; static int gMenuItem; void MyMenuCallback(void *inRefcon) { XPLMDebugString("MyFirstPlugin: menu clicked\n"); } // 在 XPluginStart 中, 原有 strcpy 之后加: XPLMMenuID pluginsMenu = XPLMFindPluginsMenu(); gMenuID = XPLMCreateMenu("My First Plugin", pluginsMenu, -1, NULL, NULL); gMenuItem = XPLMAppendMenuItem(gMenuID, "Click Me", (void *)1, MyMenuCallback);这 段 代码 的 含义: 找到 系统 的 Plugins 菜单, 在 里面 创建 一个 名为 “My First Plugin” 的 子菜单, 然后 在 这个 子菜单 里 加 一个 “Click Me” 条目。 当 用户 点击 这个 条目 时, MyMenuCallback 被 调用。 要 注意 的 是, XPLMCreateMenu 的 第 三 个 参数 传 -1 表示 直接 作为 Plugins 菜单 的 子 菜单 挂载; 如果 传 其他 值, 含义 是 父 菜单 中 某个 条目 的 索引。 新手 容易 把 这个 参数 搞 混。
在 XPluginStop 里 最好 销毁 菜单:
if (gMenuID) XPLMDestroyMenu(gMenuID);不 销毁 的 话, 插件 卸载 再 加载 时 可能 会 创建 出 重复 的 菜单 项。
4.3 读取飞机位置:和飞行模拟器对话
菜单 只是 入口, 插件 真正 的 价值 在 于 读写 X-Plane 的 数据。 这 一步 我们 让 插件 读取 当前 飞机 的 纬度 和 经度。 X-Plane 的 数据 访问 接口 是 XPLMDataAccess.h, 所有 数据 都 通过 字符串 路径 标识, 比如 sim/flightmodel/position/latitude。
在 XPluginStart 里 获取 DataRef 句柄:
#include "XPLMDataAccess.h" static XPLMDataRef latRef; static XPLMDataRef lonRef; // 在 XPluginStart 中: latRef = XPLMFindDataRef("sim/flightmodel/position/latitude"); lonRef = XPLMFindDataRef("sim/flightmodel/position/longitude");然后 改 菜单 回调:
void MyMenuCallback(void *inRefcon) { float lat = XPLMGetDataf(latRef); float lon = XPLMGetDataf(lonRef); char buf[256]; snprintf(buf, sizeof(buf), "MyFirstPlugin: lat=%.6f lon=%.6f\n", lat, lon); XPLMDebugString(buf); }这样 每次 点 菜单 项, 日志 里 就 会 出现 当前 飞机 位置。 XPLMFindDataRef 返回 的 句柄 在 插件 生命周期 内 一直 有效, 不用 每次 读取 都 重新 查 字符串。 这 是 一 个 很 重要 的 性能 习惯。 写 入 数据 也 是 类似, 用 XPLMSetDataf、 XPLMSetDatai 等, 后面 做 自动驾驶 辅助 时 会 用 到。
4.4 写日志和调试:别再弹MessageBox
新手 调试 插件 时 特别 喜欢 用 MessageBox 弹 窗。 在 普通 程序 里 没 什么, 但 插件 运行 在 X-Plane 进程 内, 弹 窗 会 阻塞 整个 模拟 器 的 渲染 循环, 全屏 模式 下 窗口 还 可能 在 后台, 导致 模拟 器 “假死”。 正确 的 做法 是 用 XPLMDebugString 输出 到 Log.txt, 或 者 自己 写 一个 简单 的 日志 文件 函数。 如果 你 实在 想 在 调试 器 里 看 输出, 可以 用 OutputDebugStringA, 在 VS 里 附加 到 X-Plane 进程 后 能 在 输出 窗口 看到, 但 这个 操作 对 新手 来说 反而 增加 复杂 度。
另外 一个 建议: 在 插件 里 预埋 几 个 调试 菜单 项, 比如 “输出 状态”、“重置 数据”。 这样 每次 改 完 代码 后 不用 重 启 X-Plane(如果 用 了 重载 插件 功能), 也 能 快速 验证 功能 是否 正常。
5. 常见问题:加载失败与异常行为速查表
5.1 插件不加载,Log里没有任何行
如果 Log.txt 里 完全 找 不到 你 的 插件 名, 先 按 下面 顺序 查:
| 检查项 | 正确 值 | 错误 示例 |
|---|---|---|
| 路径 | Resources/plugins/MyFirstPlugin/64/win.xpl | 直接 放 plugins 根目录 |
| 文件 名 | win.xpl | MyFirstPlugin.xpl |
| 架构 | x64 | x86 |
| 导入 库 | XPLM_64.lib | XPLM.lib(32 位) |
| 导出 函数 | 5 个 全部 存在 | 漏 了 一个 |
最 烦 人 的 是 路径 完全 正确 但 还 是 没 加载。 这时候 用 Dependency Walker 或者 Dependencies 工具 打开 win.xpl, 看 导入 表 里 是 否 有 不 存在 的 系统 DLL, 特别 是 VCRUNTIME140.dll。 如果 你 用 静态 CRT 编 译, 导入 表 里 可能 没有 这 个 dll, 但 也 可能 因此 加载 失败。 最 稳 妥 的 方案 就是 回到 /MD。
5.2 加载后崩溃,或与其他插件冲突
插件 加载 时 崩溃, 大部分 是 在 XPluginStart 里 做了 不该 做 的 事。 比如 尝试 创建 窗口、 访问 某些 飞行 模型 数据。 记住 XPluginStart 调用 时 X-Plane 的 插件 系统 才 刚刚 初始化 一部分, 不是 所有 功能 都 可用。 这 时候 只 做 变量 初始化和 资源 申请, 真 正 的 逻辑 放到 XPluginEnable 或 菜单 回调 里。
另外 插件 之间 可能 会 有 冲突。 比如 两个 插件 都 往 同一个 菜单 项 位置 插入, 或者 都 监听 某 全局 消息 并对 数据 做 修改。 排查 时 把 Resources/plugins 下 其他 插件 临时 移走, 只 留 你 的 插件, 复现 问题。 如果 这样 就 不 崩了, 那 就是 和 某个 插件 的 兼容 性 问题。 这种 冲突 很 难 从 代码 层面 完全 避免, 只能 在 设计 时 尽量 少 用 全局 状态, 利用 签名 机制 区分 插件。
5.3 函数没反应:生命周期理解偏差
常见 情况 是 菜单 创建 成功, 但 点击 没 反应。 原因 通常 是 回调 函数 没 注册 对。 如果 你 用 XPLMCreateMenu 时 传 了 NULL handler, 而 XPLMAppendMenuItem 时 又 没 传 handler, 那 这 个 菜单 项 就 没有 任何 处理器, 自然 没 反应。 我 当时 就 犯 过 这 个 错, 以为 XPLMCreateMenu 的 handler 会 自动 作用 于 所有 子 项, 结果 只是 默认 处理器, 子 项 可以 单独 覆盖。
还有 一种 情况: 插件 加载 时 显示 在 插件 管理 器 里 是 “禁用” 状态, XPluginEnable 没 被 调用。 此时 插件 的 某些 交互 可能 不 生效。 检查 Log 里 是否 有 XPluginEnable called 这 一行, 没有 的话 看 一下 是不是 XPluginStart 返回 了 0。 返回 0 表示 初始化 失败, 插件 会 被 直接 卸载, 所有 后续 回调 都 不会 发生。
5.4 插件无法卸载或重复加载
开发 时 最 频繁 的 操作 是 替换 win.xpl 然后 重 启 X-Plane。 但 Windows 下 DLL 被 进程 加载 后 会 锁住 文件, 如果 不 退出 X-Plane 就 覆盖 win.xpl, 系统 会 提示 文件 被 占用。 这 时候 要么 关 掉 X-Plane, 要么 用 X-Plane 自带 的 “开发者 菜单 -> Reload Plugins” 功能。 Reload Plugins 会 先 卸载 所有 插件, 再 重新 加载。 如果 你 的 插件 没有 在 XPluginStop 里 释放 全部 资源, 重 载 后 可能 出现 双 重 初始化 导致 的 状态 混乱。 所以 开发 插件 时 要 保证 Start 和 Stop 是 配对 的: Start 里 创建 的 东西, Stop 里 一定 要 释放。 我 的 习惯 是 快 速 迭代 时 用 Reload Plugins, 遇到 诡异 问题 就 老老实实 重启 X-Plane。
最后 再 分享 一个 小 技巧: 写 XPluginStart 时 把 插件 签名 起 得 唯 一 一点, 不要 用 什么 “hello123” 这种, 推荐 逆 域名 风格, 比如 com.yourname.pluginname。 这 不仅 是 为了 规范, 更 是 为了 在 Log 里 好 搜索, 也 减少 和 别人 插件 冲突 的 概率。 踩 过 几 次 坑 之后 你 会 发现, 新建 X-Plane 插件 本身 真 的 不 复杂, 复杂 的 是 那些 看 不见 的 配置 细节。 把 这 篇 的 环境 配置 和 生命周期 吃 透, 后面 再 做 仪表、 工具、 自动化 飞行 辅助, 都 是 在 这 个 架子 上 加 肉 的 事。