Skip to content

跨平台开发概述

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.hwinuser.hgdi.himm.hcommctrl.hshlobj.hobjbase.h 等),并用各平台的原生能力去实现其中的函数:

能力 代表 API swinx 源码位置
窗口与消息 CreateWindow / DestroyWindow / ShowWindow / GetMessage / PostMessage / SetTimer / SetCapture src/wnd.cppsrc/winuser.cpp、各平台 SConnection
GDI 绘图 BitBlt / TextOut / DC、位图、画刷、字体、区域 src/gdi/src/sdc.cppsrc/region.cpp
输入法 ImmGetContext / ImmSetCompositionWindow 各平台 imm.cpp / imm.mm
剪贴板与拖放 OpenClipboard / SetClipboardData / OLE 拖放 各平台 SClipboardSDragdrop
通用对话框 文件 / 颜色 / 字体对话框 各平台 dlghelper
通用控件与 COM ListView / TreeView 等,CoCreateInstanceVARIANT src/cmnctl32/src/objbase.cppsrc/variant.cpp
系统杂项 特殊路径、音效、进程、同步对象、配置文件 src/sysapi.cppsrc/mmsystem.cppsrc/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 自建:SwinXApplicationNSApplication 子类)+ AppDelegateos_state.mm
iOS src/platform/ios swinx 自建:swinx_ios_entry() 内部调用 UIApplicationMaindidFinishLaunching 中异步回调宿主的 _tWinMainios_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.cppimm.cppSClipboard.cpp),以及少量 #if defined(__ANDROID__) || defined(__OHOS__) 分支(sysapi.cppGetTempPathAshellobj.cpp 的特殊目录、mmsystem.cppPlaySound)。
  • 注册方是两个桥接库:soui-android-lib/src/main/cpp/src/AndroidPlatformAPIReg.cppsoui-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.hRender_GdiRender_Skia(默认)、Render_D2d;其中 Render_GdiRender_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 注册)

下一步

  • 桌面端:Windows / Linux / macOS 的共性、入口与资源加载
  • 移动端:Android / iOS / 鸿蒙的桥接方式与共享机制