A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】39 / 56
■ 应用:轻传输(from WinSCP)(AirSCP)
■ 定位:SFTP/SCP 客户端(WinSCP)
■ 上游:WinSCP | 路线:无头core+ArkUI | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirSCP
WinSCP 是 Windows 平台上使用广泛的开源文件传输工具,围绕 SFTP 与 SCP 协议构建,同时覆盖 FTP、WebDAV、S3 等多种后端。它的双栏文件管理器布局、会话管理、传输队列和密钥工具,长期服务于服务器运维、网站部署和远程文件同步这类高频工作流。对经常在终端与远程主机之间搬文件的开发者来说,这类工具的价值不在于界面花哨,而在于传输链路可靠、断点与覆盖策略可控。
把 WinSCP 带到鸿蒙 PC 上有一个无法绕开的前提:它的图形界面构建在 Embarcadero C++Builder 的 VCL 可视化组件之上,这是 Windows 商业库,无法在 musl/clang 工具链下编译。AirSCP 工程因此选定了 PORTING.md 中记载的路线——无头 core + ArkUI 重写 UI:把 WinSCP 的传输引擎(core 子集)交叉编译为鸿蒙共享库,图形层按双栏文件管理器的固定交互模式用 ArkUI 重新实现。这条路线也回答了一个更一般的问题:当上游应用的可视层物理不可移植时,如何保住最有价值的内核资产。
当前鸿蒙工程的包名为 com.airscp.app,版本 1.0.0(versionCode 1000000),module.json5 当前声明 deviceTypes 为 phone,请求 ohos.permission.INTERNET 权限;PORTING.md 将 MateBook Pro 与 MatePad 列为目标形态,冒烟验证已在 Pura X 与 Mate 60 Pro 上完成。上游为 WinSCP 6.5.8(PORTING.md 核实过的最新稳定版),目标系统为 HarmonyOS 6.1.0(API 23),Native 产物为 arm64-v8a 的 libwinscpcore.so。
▍二、先划清边界:不把编译通过当成适配完成
WinSCP 的源码并不只是一个传输库。它的目录解析、进度回报、用户确认(覆盖、权限、主机键)都假定运行在一个 VCL 窗体应用里:ProcessGUI、QueryUser、PromptUser 这些宿主回调会阻塞等待界面答复,FTP 引擎依赖 Windows 窗口消息泵,会话数据读写走注册表。如果只把源码编译成动态库而不处理这些宿主假设,得到的是一个无法连接、无法应答、无法持久化的空壳。
因此 AirSCP 从第一天起就把边界写成文档:第一版只保留 SFTP 与 SCP 链路(WinSCP 的本源能力),FTP/WebDAV/S3 通过条件编译摘除;UI 全部由 ArkTS 侧重新实现;core 与界面之间只通过 extern "C" C ABI 加 TSFN 回调通信。各层的对应关系如下。
层次 原项目实现 鸿蒙侧当前方案
图形界面 VCL 可视化组件双栏窗体 ArkUI 重写(当前阶段为 core 冒烟页)
应用入口 wWinMain 与 VCL 应用对象 Stage 模型 EntryAbility
传输引擎 TTerminal/SFTP/SCP 全协议 core libwinscpcore.so 无头子集(SFTP/SCP)
SSH 底层 WinSCP 内嵌 PuTTY 的 windows/*.c 平台层 自研 OHOS 平台层(poll 网络、桥接存储)
基础运行库 Delphi/C++Builder RTL 与 VCL 非可视层 自研 VCL shim(UnicodeString/Format/TDateTime/SysUtils)
会话存储 注册表与 INI(THierarchicalStorage) TOhosConfiguration(stNul + TemporaryDir)
加密依赖 完整 openssl 或系统crypto 自编 no-asm libcrypto(仅 Cryptography.cpp 所需符号)
这张表同样说明了什么不在当前产物里:双栏界面、传输队列、会话持久化和密钥工具都还是 ArkTS 侧的规划项,不因 core 编译通过而宣称可用。
▍三、鸿蒙版的整体架构
ArkTS 壳负责登录会话页、双栏浏览器(本地栏走 @ohos.file.fs,远程栏走 core)、传输队列与进度;NAPI 桥刻意做薄,只做 dlopen/dlsym 与回调转发;真正的 WinSCP core、PuTTY、VCL shim、Win API shim 与 openssl 全部收进 libwinscpcore.so,经 entry/libs/arm64-v8a/ 整体通道打包。
text
EntryAbility / Index.ets(ArkTS 壳)
├── 登录/会话管理页、双栏文件管理器、传输队列/进度(规划中)
└── NAPI 桥(entry/src/main/cpp/,仅 dlopen + dlsym)
│ extern "C" C ABI + TSFN 回调
▼
libwinscpcore.so(entry/libs/arm64-v8a/)
├── WinSCP core 子集(SFTP/SCP;FTP/WebDAV/S3 条件编译摘除)
├── PuTTY(源码内嵌版 + 自研 OHOS 平台层,poll 版网络层)
├── VCL shim(UnicodeString 体系/TObject 链/TStrings/Format-TVarRec/TDateTime/SysUtils)
├── Win API shim(winsock2→BSD socket、Event/CS→pthread、shlobj→沙箱路径)
└── openssl(自编 no-asm libcrypto)
仓库主要目录如下:
text
AirSCP/
├── AppScope/ # 应用级配置(app.json5:包名与版本)
├── entry/ # 鸿蒙 entry 模块
│ └── src/main/
│ ├── ets/pages/Index.ets # core 冒烟页(dlopen/自检/初始化/SFTP 探测)
│ ├── cpp/ # 薄 NAPI 桥(hvigor CMake 只编 bridge)
│ └── libs/arm64-v8a/ # libwinscpcore.so 整体打包通道
├── native/
│ ├── env.sh # 交叉编译环境
│ ├── sources/winscp/ # WinSCP 上游源码(保持 pristine)
│ ├── shim/vcl/ 与 shim/win/ # 自研 VCL shim 与 Win API shim
│ ├── putty_ohos/ # PuTTY OHOS 平台层(noise/stubs)
│ ├── host/ # core 线程宿主(C ABI/宿主桥/桩)
│ ├── scripts/ # 构建脚本与 transform 补丁脚本
│ └── install/ # 交叉编译安装前缀
├── PORTING.md # 架构、补丁清单、风险登记册与里程碑实录
└── build-profile.json5 # SDK 与签名配置
▍四、核心适配过程
### 1. 条件编译裁出无头 core
第一版范围裁剪落在补丁 P4 与 P5 上:CoreMain.cpp 中 FileZilla/Neon 初始化改为条件跳过,FtpFileSystem、WebDAVFileSystem、S3FileSystem、NeonIntf、Http 与 libs3 依赖全部条件编译摘除。裁剪的直接动因是 FTP 引擎的 AsyncSocketEx 基于 WSAAsyncSelect 加 WindowProc 的窗口消息 socket 模型,musl 下不存在窗口消息泵。所有 transform 产物放在 build/transformed,native/sources/winscp 保持上游原貌,保证每一处偏离都可对照。
### 2. VCL shim:把 Delphi 语义搬进 C++/musl
native/shim/vcl/ 是工作量最大的一块:UnicodeString 保持 1-based 索引与 Length/c_str 语义;624 处 __property 由 scripts/transform_props.py 机械转换为 PropertyRO/PropertyRW<> 成员代理;48 处 __closure 以宏消除(回调赋值全部来自宿主层,core 内零成员函数绑定);FORMAT/ARRAYOFCONST 落到 TVarRec 体系;TDateTime 保持 double 天数、纪元 1899-12-30;SysUtils 约 40 个函数逐个补齐。
### 3. PuTTY 平台层 poll 化与事件循环等价重写
WinSCP 内嵌的 PuTTY 没有 unix 目录,24 个 windows/*.c 全部需要等价物。补丁 P7 以自研 OHOS 平台层替换:network.c 用 poll() 重写并保持 select_result 喂送语义(对齐 SecureShell.cpp 中的 EventSelectLoop 骨架),storage.c 计划桥接到 THierarchicalStorage,noise/unicode/agent 等裁剪为 stub(native/putty_ohos/stubs.c)。补丁 P6 把 SecureShell.cpp 的 WSAEventSelect 事件循环重写为 poll 版 EventSelectLoop。
### 4. NAPI 薄桥、专用 core 线程与 TSFN 回调
core 假定宿主提供消息泵:查询回调会阻塞等待用户答复。若把这些调用放进 ArkTS 主线程,对话框未弹出进程就先被看门狗击杀。工程因此用专用 core 线程跑全部 TTerminal 调用,查询回调经 TSFN 送到 ArkTS 侧,native 侧以 promise 加 condvar 等待答复,ArkTS 主线程零阻塞。entry/src/main/cpp/ 只编一个 bridge 文件,Index.ets 暴露的 coreVersion/selftest/coreInit/sftpProbe 四个入口即这条链路的冒烟面。
### 5. 打包链:五件套与链接期修正
针对 hvigor strip 与闭包吞 .so 的风险,打包五件套在第一天配齐:strip:false、compressNativeLibs:false、libs/arm64-v8a 整体通道、自研 elf_edit.py 处理 musl 严格性、拓扑序 dlopen。链接期修正记录在 PORTING.md:shim/win 全家改为 extern "C" 避免 C++ 符号 mangle;-nostdlib 杜绝 NEEDED libc.so;显式链接 libc++_shared.so 与 libclang_rt.builtins.a(补 __floatunsitf 等 128 位软浮点);stricmp/strnicmp 映射到 strcasecmp 族。最终 build_so.sh 链接出约 32MB 的 libwinscpcore.so。
▍五、适配中遇到的几个难点
### 难点一:Delphi 格式化语义偏差以乱码和崩溃收场
Terminal.cpp 中有 145 处 FORMAT 调用,%s/%d/%x/%.*g 与宽度控制全部走 Delphi 的 TVarRec 语义,任何偏差都表现为静默乱码或崩溃。shim 的 Format 按全套语义实现,%s 走 vtUnicodeString 分支,并配单元自测。冒烟页上的 name=AirSCP;num=42 输出就是这条链路的验收点。
### 难点二:UnicodeString 的 1-based 世界
core 源码里 &S[i] 取址贯穿目录解析,而 i 从 1 开始。shim 的 operator[] 返回 data()+i-1 的引用来对齐语义,并把首版协议面收敛到 SFTP 的结构化路径上,把依赖文本解析的 SCP 后置,避免在兼容层里埋下隐蔽的越界。
### 难点三:临界区不递归,两条重入链死锁
Windows 的 CRITICAL_SECTION 可重入,pthread 默认互斥量不可。真机初始化时 TUsage::Default→Set 与 putty random_ref→random_save_seed 两条重入链先后死锁,全部以 hilog/faultlog 行为签名定案。System.SyncObjs.hpp 与 win/windows.h 的临界区实现统一改用 PTHREAD_MUTEX_RECURSIVE 后解除。
### 难点四:FFRT 线程静默挂死,看门狗不响
SFTP 探测在 TCP 与 SSH banner 交换打通后停在认证深处,进程存活超过 60 秒却不触发 appfreeze——FFRT 工作线程静默挂死不在看门狗覆盖范围内。工程建立了阶段探针体系:airscp_trace 回调注入 bridge 转发 hilog,TTerminal::LogEvent 直通 hilog,边界针把挂点收敛到 TTerminal::Open() 内部早期(Open 前针有输出、后针无踪、会话日志层零输出)。下一轮攻坚目标即 EventSelectLoop poll 事件泵(风险 R3)与 QueryUser 认证链。
### 难点五:musl dlopen 与链接器的多重陷阱
这一类问题各不相同但后果一致——设备上加载失败:GetProcAddress 映射到 dlsym(RTLD_DEFAULT) 后找不到自身符号(.so 以 RTLD_LOCAL 加载),改为 dlopen(RTLD_NOLOAD) 取合法自句柄;openssl 的 SysV shm 探测失败改走 getentropy;WSADATA.wVersion 未填导致 putty 版本校验三连失败后 ESshFatal abort;GetFileVersionInfo 需要伪造合法版本块供 CompoundVersion 读取。全链路 .so 由 elf_edit.py 统一处理无版本 NEEDED。
▍六、编译、安装与启动
Native 层先于 HAP 构建:source native/env.sh 进入交叉环境,依次执行 native/scripts/build_putty.sh、build_core.sh 与 build_so.sh,产物 libwinscpcore.so 落入 entry/libs/arm64-v8a/。签名材料可用 devecocli signature generate 生成。ArkTS 侧按标准 hvigor 流程构建:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
产物位于 entry/build/default/outputs/default/entry-default-signed.hap。连接设备后安装并启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airscp.app
PORTING.md 记载的验证结果为:Pura X 与 Mate 60 Pro 双机同一 .so 冒烟全绿(dlopen 成功、Format 引擎与 TDateTime 自检通过、CoreInitialize 全链路 rc=0,冒烟页显示 core initialized)。
▍七、当前功能边界
当前工程已经覆盖的部分:
- libwinscpcore.so 全链路编译收敛(core 28/28、putty 166/166),32MB 产物经 libs/arm64-v8a 通道进入 HAP;
- Pura X 与 Mate 60 Pro 双机冒烟:dlopen、Format 引擎自检、TDateTime 自检通过;
- CoreInitialize 全链路打通,TOhosConfiguration 真配置后端 rc=0;
- SFTP probe 已完成 TCP 连接与 SSH banner 交换(ssh_verstring 栈实证);
- 诊断基建就位:trace 回调注入、会话日志直通 hilog、边界针定位方法。
尚未覆盖或仍在攻坚的部分:
- SFTP 认证与事件泵:probe 停在 TTerminal::Open() 内部早期,poll 版 EventSelectLoop(风险 R3)与 QueryUser 认证链待实连验证;
- ArkUI 双栏文件管理器、上传下载、传输队列、进度与覆盖提示弹窗(阶段目标 M2);
- 会话管理持久化、书签、keygen(阶段目标 M3;putty 的 storage.c 尚未桥接 THierarchicalStorage,会话存取仍走注册表空后端);
- SCP 协议的 ls 文本解析(首版以 SFTP 结构化路径为主)。
因此,当前版本更准确的定位是 WinSCP 传输内核的鸿蒙移植底座:M0(core 编译与初始化)已达成,M1(真机 SSH 连通)正处于事件泵攻坚阶段,距离可日常使用的双栏文件管理器还有 M2、M3 两个里程碑。
▍八、总结
AirSCP 的价值在于给出了一类问题的可复制解法:当上游应用的图形层绑定在不可移植的商业库上时,把传输内核完整保留、把界面按交互模式重写,仍能得到一个值得长期投入的鸿蒙工程。VCL shim、PuTTY OHOS 平台层与打包五件套这些资产,对后续任何 Delphi/C++Builder 血统应用的移植都有直接的参考价值。
后续工作的重心在 PORTING.md 里写得很具体:打通 TTerminal::Open 的 poll 事件泵与认证 QueryUser 链,把 storage.c 桥到 THierarchicalStorage,然后在真机 SFTP 连通的基础上推进双栏 UI 与传输队列。每一步都沿用同一验收方式——hilog 与截图双通道留证,风险登记册逐项销号。
■ 应用:轻传输(from WinSCP)(AirSCP)
■ 定位:SFTP/SCP 客户端(WinSCP)
■ 上游:WinSCP | 路线:无头core+ArkUI | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirSCP
WinSCP 是 Windows 平台上使用广泛的开源文件传输工具,围绕 SFTP 与 SCP 协议构建,同时覆盖 FTP、WebDAV、S3 等多种后端。它的双栏文件管理器布局、会话管理、传输队列和密钥工具,长期服务于服务器运维、网站部署和远程文件同步这类高频工作流。对经常在终端与远程主机之间搬文件的开发者来说,这类工具的价值不在于界面花哨,而在于传输链路可靠、断点与覆盖策略可控。
把 WinSCP 带到鸿蒙 PC 上有一个无法绕开的前提:它的图形界面构建在 Embarcadero C++Builder 的 VCL 可视化组件之上,这是 Windows 商业库,无法在 musl/clang 工具链下编译。AirSCP 工程因此选定了 PORTING.md 中记载的路线——无头 core + ArkUI 重写 UI:把 WinSCP 的传输引擎(core 子集)交叉编译为鸿蒙共享库,图形层按双栏文件管理器的固定交互模式用 ArkUI 重新实现。这条路线也回答了一个更一般的问题:当上游应用的可视层物理不可移植时,如何保住最有价值的内核资产。
当前鸿蒙工程的包名为 com.airscp.app,版本 1.0.0(versionCode 1000000),module.json5 当前声明 deviceTypes 为 phone,请求 ohos.permission.INTERNET 权限;PORTING.md 将 MateBook Pro 与 MatePad 列为目标形态,冒烟验证已在 Pura X 与 Mate 60 Pro 上完成。上游为 WinSCP 6.5.8(PORTING.md 核实过的最新稳定版),目标系统为 HarmonyOS 6.1.0(API 23),Native 产物为 arm64-v8a 的 libwinscpcore.so。
▍二、先划清边界:不把编译通过当成适配完成
WinSCP 的源码并不只是一个传输库。它的目录解析、进度回报、用户确认(覆盖、权限、主机键)都假定运行在一个 VCL 窗体应用里:ProcessGUI、QueryUser、PromptUser 这些宿主回调会阻塞等待界面答复,FTP 引擎依赖 Windows 窗口消息泵,会话数据读写走注册表。如果只把源码编译成动态库而不处理这些宿主假设,得到的是一个无法连接、无法应答、无法持久化的空壳。
因此 AirSCP 从第一天起就把边界写成文档:第一版只保留 SFTP 与 SCP 链路(WinSCP 的本源能力),FTP/WebDAV/S3 通过条件编译摘除;UI 全部由 ArkTS 侧重新实现;core 与界面之间只通过 extern "C" C ABI 加 TSFN 回调通信。各层的对应关系如下。
层次 原项目实现 鸿蒙侧当前方案
图形界面 VCL 可视化组件双栏窗体 ArkUI 重写(当前阶段为 core 冒烟页)
应用入口 wWinMain 与 VCL 应用对象 Stage 模型 EntryAbility
传输引擎 TTerminal/SFTP/SCP 全协议 core libwinscpcore.so 无头子集(SFTP/SCP)
SSH 底层 WinSCP 内嵌 PuTTY 的 windows/*.c 平台层 自研 OHOS 平台层(poll 网络、桥接存储)
基础运行库 Delphi/C++Builder RTL 与 VCL 非可视层 自研 VCL shim(UnicodeString/Format/TDateTime/SysUtils)
会话存储 注册表与 INI(THierarchicalStorage) TOhosConfiguration(stNul + TemporaryDir)
加密依赖 完整 openssl 或系统crypto 自编 no-asm libcrypto(仅 Cryptography.cpp 所需符号)
这张表同样说明了什么不在当前产物里:双栏界面、传输队列、会话持久化和密钥工具都还是 ArkTS 侧的规划项,不因 core 编译通过而宣称可用。
▍三、鸿蒙版的整体架构
ArkTS 壳负责登录会话页、双栏浏览器(本地栏走 @ohos.file.fs,远程栏走 core)、传输队列与进度;NAPI 桥刻意做薄,只做 dlopen/dlsym 与回调转发;真正的 WinSCP core、PuTTY、VCL shim、Win API shim 与 openssl 全部收进 libwinscpcore.so,经 entry/libs/arm64-v8a/ 整体通道打包。
text
EntryAbility / Index.ets(ArkTS 壳)
├── 登录/会话管理页、双栏文件管理器、传输队列/进度(规划中)
└── NAPI 桥(entry/src/main/cpp/,仅 dlopen + dlsym)
│ extern "C" C ABI + TSFN 回调
▼
libwinscpcore.so(entry/libs/arm64-v8a/)
├── WinSCP core 子集(SFTP/SCP;FTP/WebDAV/S3 条件编译摘除)
├── PuTTY(源码内嵌版 + 自研 OHOS 平台层,poll 版网络层)
├── VCL shim(UnicodeString 体系/TObject 链/TStrings/Format-TVarRec/TDateTime/SysUtils)
├── Win API shim(winsock2→BSD socket、Event/CS→pthread、shlobj→沙箱路径)
└── openssl(自编 no-asm libcrypto)
仓库主要目录如下:
text
AirSCP/
├── AppScope/ # 应用级配置(app.json5:包名与版本)
├── entry/ # 鸿蒙 entry 模块
│ └── src/main/
│ ├── ets/pages/Index.ets # core 冒烟页(dlopen/自检/初始化/SFTP 探测)
│ ├── cpp/ # 薄 NAPI 桥(hvigor CMake 只编 bridge)
│ └── libs/arm64-v8a/ # libwinscpcore.so 整体打包通道
├── native/
│ ├── env.sh # 交叉编译环境
│ ├── sources/winscp/ # WinSCP 上游源码(保持 pristine)
│ ├── shim/vcl/ 与 shim/win/ # 自研 VCL shim 与 Win API shim
│ ├── putty_ohos/ # PuTTY OHOS 平台层(noise/stubs)
│ ├── host/ # core 线程宿主(C ABI/宿主桥/桩)
│ ├── scripts/ # 构建脚本与 transform 补丁脚本
│ └── install/ # 交叉编译安装前缀
├── PORTING.md # 架构、补丁清单、风险登记册与里程碑实录
└── build-profile.json5 # SDK 与签名配置
▍四、核心适配过程
### 1. 条件编译裁出无头 core
第一版范围裁剪落在补丁 P4 与 P5 上:CoreMain.cpp 中 FileZilla/Neon 初始化改为条件跳过,FtpFileSystem、WebDAVFileSystem、S3FileSystem、NeonIntf、Http 与 libs3 依赖全部条件编译摘除。裁剪的直接动因是 FTP 引擎的 AsyncSocketEx 基于 WSAAsyncSelect 加 WindowProc 的窗口消息 socket 模型,musl 下不存在窗口消息泵。所有 transform 产物放在 build/transformed,native/sources/winscp 保持上游原貌,保证每一处偏离都可对照。
### 2. VCL shim:把 Delphi 语义搬进 C++/musl
native/shim/vcl/ 是工作量最大的一块:UnicodeString 保持 1-based 索引与 Length/c_str 语义;624 处 __property 由 scripts/transform_props.py 机械转换为 PropertyRO/PropertyRW<> 成员代理;48 处 __closure 以宏消除(回调赋值全部来自宿主层,core 内零成员函数绑定);FORMAT/ARRAYOFCONST 落到 TVarRec 体系;TDateTime 保持 double 天数、纪元 1899-12-30;SysUtils 约 40 个函数逐个补齐。
### 3. PuTTY 平台层 poll 化与事件循环等价重写
WinSCP 内嵌的 PuTTY 没有 unix 目录,24 个 windows/*.c 全部需要等价物。补丁 P7 以自研 OHOS 平台层替换:network.c 用 poll() 重写并保持 select_result 喂送语义(对齐 SecureShell.cpp 中的 EventSelectLoop 骨架),storage.c 计划桥接到 THierarchicalStorage,noise/unicode/agent 等裁剪为 stub(native/putty_ohos/stubs.c)。补丁 P6 把 SecureShell.cpp 的 WSAEventSelect 事件循环重写为 poll 版 EventSelectLoop。
### 4. NAPI 薄桥、专用 core 线程与 TSFN 回调
core 假定宿主提供消息泵:查询回调会阻塞等待用户答复。若把这些调用放进 ArkTS 主线程,对话框未弹出进程就先被看门狗击杀。工程因此用专用 core 线程跑全部 TTerminal 调用,查询回调经 TSFN 送到 ArkTS 侧,native 侧以 promise 加 condvar 等待答复,ArkTS 主线程零阻塞。entry/src/main/cpp/ 只编一个 bridge 文件,Index.ets 暴露的 coreVersion/selftest/coreInit/sftpProbe 四个入口即这条链路的冒烟面。
### 5. 打包链:五件套与链接期修正
针对 hvigor strip 与闭包吞 .so 的风险,打包五件套在第一天配齐:strip:false、compressNativeLibs:false、libs/arm64-v8a 整体通道、自研 elf_edit.py 处理 musl 严格性、拓扑序 dlopen。链接期修正记录在 PORTING.md:shim/win 全家改为 extern "C" 避免 C++ 符号 mangle;-nostdlib 杜绝 NEEDED libc.so;显式链接 libc++_shared.so 与 libclang_rt.builtins.a(补 __floatunsitf 等 128 位软浮点);stricmp/strnicmp 映射到 strcasecmp 族。最终 build_so.sh 链接出约 32MB 的 libwinscpcore.so。
▍五、适配中遇到的几个难点
### 难点一:Delphi 格式化语义偏差以乱码和崩溃收场
Terminal.cpp 中有 145 处 FORMAT 调用,%s/%d/%x/%.*g 与宽度控制全部走 Delphi 的 TVarRec 语义,任何偏差都表现为静默乱码或崩溃。shim 的 Format 按全套语义实现,%s 走 vtUnicodeString 分支,并配单元自测。冒烟页上的 name=AirSCP;num=42 输出就是这条链路的验收点。
### 难点二:UnicodeString 的 1-based 世界
core 源码里 &S[i] 取址贯穿目录解析,而 i 从 1 开始。shim 的 operator[] 返回 data()+i-1 的引用来对齐语义,并把首版协议面收敛到 SFTP 的结构化路径上,把依赖文本解析的 SCP 后置,避免在兼容层里埋下隐蔽的越界。
### 难点三:临界区不递归,两条重入链死锁
Windows 的 CRITICAL_SECTION 可重入,pthread 默认互斥量不可。真机初始化时 TUsage::Default→Set 与 putty random_ref→random_save_seed 两条重入链先后死锁,全部以 hilog/faultlog 行为签名定案。System.SyncObjs.hpp 与 win/windows.h 的临界区实现统一改用 PTHREAD_MUTEX_RECURSIVE 后解除。
### 难点四:FFRT 线程静默挂死,看门狗不响
SFTP 探测在 TCP 与 SSH banner 交换打通后停在认证深处,进程存活超过 60 秒却不触发 appfreeze——FFRT 工作线程静默挂死不在看门狗覆盖范围内。工程建立了阶段探针体系:airscp_trace 回调注入 bridge 转发 hilog,TTerminal::LogEvent 直通 hilog,边界针把挂点收敛到 TTerminal::Open() 内部早期(Open 前针有输出、后针无踪、会话日志层零输出)。下一轮攻坚目标即 EventSelectLoop poll 事件泵(风险 R3)与 QueryUser 认证链。
### 难点五:musl dlopen 与链接器的多重陷阱
这一类问题各不相同但后果一致——设备上加载失败:GetProcAddress 映射到 dlsym(RTLD_DEFAULT) 后找不到自身符号(.so 以 RTLD_LOCAL 加载),改为 dlopen(RTLD_NOLOAD) 取合法自句柄;openssl 的 SysV shm 探测失败改走 getentropy;WSADATA.wVersion 未填导致 putty 版本校验三连失败后 ESshFatal abort;GetFileVersionInfo 需要伪造合法版本块供 CompoundVersion 读取。全链路 .so 由 elf_edit.py 统一处理无版本 NEEDED。
▍六、编译、安装与启动
Native 层先于 HAP 构建:source native/env.sh 进入交叉环境,依次执行 native/scripts/build_putty.sh、build_core.sh 与 build_so.sh,产物 libwinscpcore.so 落入 entry/libs/arm64-v8a/。签名材料可用 devecocli signature generate 生成。ArkTS 侧按标准 hvigor 流程构建:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
产物位于 entry/build/default/outputs/default/entry-default-signed.hap。连接设备后安装并启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airscp.app
PORTING.md 记载的验证结果为:Pura X 与 Mate 60 Pro 双机同一 .so 冒烟全绿(dlopen 成功、Format 引擎与 TDateTime 自检通过、CoreInitialize 全链路 rc=0,冒烟页显示 core initialized)。
▍七、当前功能边界
当前工程已经覆盖的部分:
- libwinscpcore.so 全链路编译收敛(core 28/28、putty 166/166),32MB 产物经 libs/arm64-v8a 通道进入 HAP;
- Pura X 与 Mate 60 Pro 双机冒烟:dlopen、Format 引擎自检、TDateTime 自检通过;
- CoreInitialize 全链路打通,TOhosConfiguration 真配置后端 rc=0;
- SFTP probe 已完成 TCP 连接与 SSH banner 交换(ssh_verstring 栈实证);
- 诊断基建就位:trace 回调注入、会话日志直通 hilog、边界针定位方法。
尚未覆盖或仍在攻坚的部分:
- SFTP 认证与事件泵:probe 停在 TTerminal::Open() 内部早期,poll 版 EventSelectLoop(风险 R3)与 QueryUser 认证链待实连验证;
- ArkUI 双栏文件管理器、上传下载、传输队列、进度与覆盖提示弹窗(阶段目标 M2);
- 会话管理持久化、书签、keygen(阶段目标 M3;putty 的 storage.c 尚未桥接 THierarchicalStorage,会话存取仍走注册表空后端);
- SCP 协议的 ls 文本解析(首版以 SFTP 结构化路径为主)。
因此,当前版本更准确的定位是 WinSCP 传输内核的鸿蒙移植底座:M0(core 编译与初始化)已达成,M1(真机 SSH 连通)正处于事件泵攻坚阶段,距离可日常使用的双栏文件管理器还有 M2、M3 两个里程碑。
▍八、总结
AirSCP 的价值在于给出了一类问题的可复制解法:当上游应用的图形层绑定在不可移植的商业库上时,把传输内核完整保留、把界面按交互模式重写,仍能得到一个值得长期投入的鸿蒙工程。VCL shim、PuTTY OHOS 平台层与打包五件套这些资产,对后续任何 Delphi/C++Builder 血统应用的移植都有直接的参考价值。
后续工作的重心在 PORTING.md 里写得很具体:打通 TTerminal::Open 的 poll 事件泵与认证 QueryUser 链,把 storage.c 桥到 THierarchicalStorage,然后在真机 SFTP 连通的基础上推进双栏 UI 与传输队列。每一步都沿用同一验收方式——hilog 与截图双通道留证,风险登记册逐项销号。
楼主
8 天前