跨平台开发概述¶
Warning
The current page still doesn't have a translation for this language.
You can read it through google translate.
SOUI(当前主版本 5.x)用同一份 C++ 业务代码覆盖六个平台:
桌面端:Windows / Linux / macOS
移动端:Android / iOS / 鸿蒙(HarmonyOS / OHOS)
SOUI 的跨平台思路¶
SOUI 并没有把自己移植到各平台的原生 UI 框架上,而是走了相反的路线——在各个平台上把 Windows API 实现一遍,让 SOUI 核心以为自己始终运行在 Windows 上。承担这件事的模块叫 swinx。
Windows :直接使用系统自带的 Win32 API,不编译 swinx
其它平台 :由 swinx 提供一套 Windows API 实现
这在根 CMakeLists.txt 里可以直接看到:
if (CMAKE_SYSTEM_NAME MATCHES Windows)
add_definitions(-DWCHAR_SIZE=2) # 用系统 Win32,不引入 swinx
else()
add_subdirectory(swinx) # 非 Windows 一律引入 swinx
add_definitions(-DWCHAR_SIZE=4)
include_directories(${PROJECT_SOURCE_DIR}/swinx/include)
endif()
顺带说明一个常见疑问:WCHAR_SIZE 之所以在 Windows 上是 2、其它平台是 4,是因为非 Windows 平台的 wchar_t 为 4 字节,swinx 的字符串 API 需要按实际宽度处理。
swinx 是什么¶
swinx 是在各平台实现 Windows API 的框架。它自带一整套 Windows 风格头文件(windows.h、winuser.h、gdi.h、imm.h、commctrl.h、shlobj.h、objbase.h 等),并用各平台的原生能力去实现其中的函数:
| 能力 | 代表 API | swinx 源码位置 |
|---|---|---|
| 窗口与消息 | CreateWindow / DestroyWindow / ShowWindow / GetMessage / PostMessage / SetTimer / SetCapture |
src/wnd.cpp、src/winuser.cpp、各平台 SConnection |
| GDI 绘图 | BitBlt / TextOut / DC、位图、画刷、字体、区域 |
src/gdi/、src/sdc.cpp、src/region.cpp |
| 输入法 | ImmGetContext / ImmSetCompositionWindow |
各平台 imm.cpp / imm.mm |
| 剪贴板与拖放 | OpenClipboard / SetClipboardData / OLE 拖放 |
各平台 SClipboard、SDragdrop |
| 通用对话框 | 文件 / 颜色 / 字体对话框 | 各平台 dlghelper |
| 通用控件与 COM | ListView / TreeView 等,CoCreateInstance、VARIANT |
src/cmnctl32/、src/objbase.cpp、src/variant.cpp |
| 系统杂项 | 特殊路径、音效、进程、同步对象、配置文件 | src/sysapi.cpp、src/mmsystem.cpp、src/profile.cpp |
所以对 SOUI 核心和业务代码而言,Windows 与非 Windows 的差别几乎为零。
平台实现分两类¶
swinx 按目标系统选择要编译的平台目录(swinx/CMakeLists.txt):
if(CMAKE_SYSTEM_NAME MATCHES "OHOS|OpenHarmony|HarmonyOS|Android")
include(mobile.cmake) # src/platform/mobile + src/gdi/cairo
elseif(CMAKE_SYSTEM_NAME MATCHES "iOS")
include(ios.cmake) # src/platform/ios + src/gdi/apple
elseif(CMAKE_SYSTEM_NAME MATCHES Darwin)
include(macos.cmake) # src/platform/cocoa + src/gdi/apple
else()
include(linux.cmake) # src/platform/linux + src/gdi/cairo
endif()
按"平台 API 由谁实现"划分,这些平台分成两类。
第一类:swinx 内部自实现(Linux / macOS / iOS)¶
这三个平台允许进程拥有自己的入口与事件循环,因此 swinx 在模块内部就能把平台 API 实现完整,不需要外部注册任何东西:
| 平台 | 平台层目录 | 启动器与事件泵 |
|---|---|---|
| Linux | src/platform/linux |
swinx 自建:xcb_connect() 建立连接,poll() + xcb_poll_for_event() 驱动消息 |
| macOS | src/platform/cocoa |
swinx 自建:SwinXApplication(NSApplication 子类)+ AppDelegate(os_state.mm) |
| iOS | src/platform/ios |
swinx 自建:swinx_ios_entry() 内部调用 UIApplicationMain,didFinishLaunching 中异步回调宿主的 _tWinMain(ios_main.mm) |
应用侧只要链上 swinx、写一个 _tWinMain 就能跑(iOS 多一层 swinx_ios_entry 包装)。
第二类:需要外部注册平台实现(Android / 鸿蒙)¶
Android 与鸿蒙的应用没有属于自己的启动器:进程由系统的 Java Activity / ArkTS UIAbility 拉起,窗口、事件循环、输入法、剪贴板全部归系统运行时(JVM / ArkTS 引擎)所有,C++ 侧无法自行创建。
这是这两个平台唯一特殊的地方,也正是 platform_api 存在的唯一原因。swinx/include/platform_api.h 定义了一组 C 风格函数指针表,让运行在系统运行时一侧的桥接库把能力"注册"进 swinx:
struct PlatformAPI {
int version; // PLATFORM_API_VERSION,注册时校验
struct PlatformClipboardAPI clipboard;
struct PlatformWindowAPI window; // createWindow / setTimer / invalidRect / setCapture ...
struct PlatformIMEAPI ime;
struct PlatformAudioAPI audio;
struct PlatformPathAPI path;
};
extern struct PlatformAPI g_platformAPI;
BOOL PlatformAPI_Init(struct PlatformAPI *api); // 由桥接库在启动时调用
void PlatformAPI_Deinit(void);
- 消费方只有移动端平台层
src/platform/mobile/(SConnection.cpp、imm.cpp、SClipboard.cpp),以及少量#if defined(__ANDROID__) || defined(__OHOS__)分支(sysapi.cpp的GetTempPathA、shellobj.cpp的特殊目录、mmsystem.cpp的PlaySound)。 - 注册方是两个桥接库:
soui-android-lib/src/main/cpp/src/AndroidPlatformAPIReg.cpp与soui-ohos-lib/src/main/cpp/src/OhosPlatformAPIReg.cpp。
常见误解
g_platformAPI 不是 swinx 的通用平台抽象机制。Windows 完全不编译 swinx;Linux / macOS / iOS 的平台 API 全部在 swinx 内部实现,不经过 g_platformAPI。它只服务于 Android 与鸿蒙。
Android 与鸿蒙还共用 mobile.cmake 中打开的 -DENABLE_VIRTUAL_HWND:允许把系统运行时侧的原生控件(如 Android EditText)注册为一个"虚拟 HWND"参与 SOUI 的窗口体系(RegisterVirtualHWND / UnregisterVirtualHWND,见 swinx/include/wnd.h)。
架构总览¶
graph TB
A["SOUI 应用:同一份 C++ 业务代码"]
B["SOUI Core:控件 / 布局 / 渲染工厂 / 消息处理"]
W["Windows:系统原生 Win32 + GDI<br/>(不编译 swinx)"]
S["swinx:在各平台实现 Windows API"]
L["Linux 平台层<br/>xcb + Cairo"]
M["macOS 平台层<br/>Cocoa + Core Graphics"]
I["iOS 平台层<br/>UIKit + Core Graphics"]
P["mobile 平台层<br/>Cairo + g_platformAPI"]
AD["soui-android-lib<br/>JNI 注册 PlatformAPI"]
OH["soui-ohos-lib<br/>N-API 注册 PlatformAPI"]
A --> B
B --> W
B --> S
S --> L
S --> M
S --> I
S --> P
AD --> P
OH --> P
两个"渲染"概念不要混淆¶
这是文档与讨论中最容易出错的一处,两者处于不同层次:
- swinx 的 2D 后端:负责把 GDI 调用翻译成平台的 2D 绘制。只有两套实现——
src/gdi/apple(Core Graphics,macOS / iOS)与src/gdi/cairo(Cairo,Linux / Android / 鸿蒙);Windows 直接用系统 GDI,不涉及翻译。 - SOUI 的渲染工厂(Render Factory):SOUI 在更上层绘制 UI 时使用的绘图引擎,可插拔,由应用在入口处通过
SAppCfg::SetRender(...)选择。取值定义在SOUI/include/SAppCfg.h:Render_Gdi、Render_Skia(默认)、Render_D2d;其中Render_Gdi与Render_D2d依赖 Windows 组件,跨平台工程一般统一用Render_Skia。
也就是说:「UI 长什么样、怎么画」由 SOUI 渲染工厂决定,「最终怎么落到平台画布上」由 swinx 的 2D 后端完成。二者相互独立——例如 games/cnchess/client 在全部六个平台上都用 Render_Skia,但底层 2D 后端在 macOS 是 Core Graphics、在 Android 是 Cairo。
| 平台 | swinx 2D 后端 | 常用 SOUI 渲染工厂 |
|---|---|---|
| Windows | 系统原生 GDI(无 swinx) | Render_Skia / Render_Gdi / Render_D2d |
| Linux | Cairo | Render_Skia |
| macOS | Core Graphics | Render_Skia |
| iOS | Core Graphics | Render_Skia |
| Android | Cairo | Render_Skia |
| 鸿蒙 OHOS | Cairo | Render_Skia |
统一的入口模型¶
除 Android / 鸿蒙外,所有平台都共用 Win32 风格入口 _tWinMain,非 Windows 平台只需一个极薄的 main() 转发。games/cnchess/client/main.cc 就是这么写的:
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpstrCmdLine, int)
{
// 业务代码:注册皮肤/窗口类、SAppCfg 配置、创建主窗口、app.Run(...)
}
#if defined(__IOS__)
int main(int argc, char **argv)
{
return swinx_ios_entry(argc, argv, _tWinMain); // 由 swinx 内部拉起 UIApplicationMain
}
#elif !defined(_WIN32) || defined(__MINGW32__)
int main(int argc, char **argv)
{
HINSTANCE hInst = GetModuleHandle(NULL);
return _tWinMain(hInst, 0, NULL, SW_SHOWNORMAL); // Linux / macOS 直接转发
}
#endif
Android 与鸿蒙因为入口必须落在 JNI / N-API 函数里,才单独提供 android_entry.cc / ohos_entry.cc——但它们内部初始化 SOUI 的方式与 _tWinMain 完全一致。
平台一览¶
| 平台 | 是否编译 swinx | 平台层 | 启动器归属 | 是否用 platform_api |
|---|---|---|---|---|
| Windows | 否 | 系统原生 | 应用自己(WinMain) |
否 |
| Linux | 是 | src/platform/linux |
swinx 内部(xcb 事件泵) | 否 |
| macOS | 是 | src/platform/cocoa |
swinx 内部(NSApplication) |
否 |
| iOS | 是 | src/platform/ios |
swinx 内部(UIApplicationMain) |
否 |
| Android | 是 | src/platform/mobile |
系统(Java Activity) |
是(soui-android-lib 注册) |
| 鸿蒙 OHOS | 是 | src/platform/mobile |
系统(ArkTS UIAbility) |
是(soui-ohos-lib 注册) |