Chromium UI框架 macOS移植完全指南
作为一个10年PC客户端老兵,带你搞定Chromium UI的跨平台移植
背景
之前我们实现了《手写一个极简Chromium浏览器》,很多同学对Chromium的架构有了初步认识。今天来点更深入的——把整个 Chromium UI (chromium_ui) 框架移植到 macOS!
Chromium UI 是什么?
Chromium UI 是 Chromium 浏览器的UI层框架,包含:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ┌─────────────────────────────────────────────────────────────┐ │ chromium_ui 架构 │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────────────────────────────────────────┐ │ │ │ View Framework │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ View │ │ Window │ │ Widget │ │ Control│ │ │ │ │ │ 视图 │ │ 窗口 │ │ 控件 │ │ 组件 │ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ gfx (Graphics) │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ Bitmap │ │ Canvas │ │ Font │ │ Path │ │ │ │ │ │ 位图 │ │ 画布 │ │ 字体 │ │ 路径 │ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ base (基础库) │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ Thread │ │ Time │ │ Atomic │ │ String │ │ │ │ │ │ 线程 │ │ 时间 │ │ 原子操作 │ │ 字符串 │ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘
|
核心组件
| 组件 |
功能 |
Windows实现 |
| View Framework |
UI组件系统 |
Win32控件 |
| gfx/Graphics |
图形绘制 |
GDI/GDI+ |
| base |
基础库 |
Windows API |
| animation |
动画系统 |
Win32定时器 |
移植挑战
最大的问题是:Windows依赖太深!
1 2 3 4 5 6 7
| #include <windows.h>
typedef LONG Atomic32; typedef DWORD ColorRef; HWND hwnd;
|
主要障碍
- Windows头文件 - 几百个文件include
<windows.h>
- GDI+图形库 - 完全Windows特有
- 原子操作 - InterlockedCompareExchange 等
- 消息循环 - MSG、WNDPROC 等
- COM接口 - IUnknown、IClassFactory 等
解决方案:条件编译 + 独立实现
策略一:条件编译 (#ifdef)
1 2 3 4 5 6 7 8 9 10 11 12
| #ifdef _WIN32 typedef LONG Atomic32; #else typedef int32_t Atomic32; inline Atomic32 NoBarrier_CompareAndSwap(...) { return __sync_val_compare_and_swap(ptr, old_value, new_value); } #endif
|
策略二:独立实现 (Aura_mac)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #ifdef AURA_MAC #import <Cocoa/Cocoa.h>
namespace aura { namespace mac {
class ViewMac { NSView* nsview_; void SetBounds(int x, int y, int w, int h); };
} } #endif
|
移植实现
1. 基础库移植
atomicops.h - 原子操作
1 2 3 4 5 6 7 8 9 10
| inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, Atomic32 old_value, Atomic32 new_value) { return __sync_val_compare_and_swap(ptr, old_value, new_value); }
inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, Atomic32 increment) { return __sync_add_and_fetch(ptr, increment); }
|
time.h - 时间处理
1 2 3 4 5 6 7 8 9 10 11 12
| #ifndef _WIN32 #include <sys/time.h> #include <cstdint>
typedef uint64_t QWORD; typedef uint32_t DWORD; typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME; #endif
|
2. UI框架移植
View类 (macOS实现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class ViewMac { public: void SetBounds(int x, int y, int width, int height) { if (nsview_) { [nsview_ setFrame:NSMakeRect(x, y, width, height)]; } } void SetVisible(bool visible) { if (nsview_) { [nsview_ setHidden:!visible]; } } private: NSView* nsview_; };
|
Window类 (macOS实现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class WindowMac { public: bool Init() { window_ = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600) styleMask:NSTitledWindowMask | NSClosableWindowMask backing:NSBackingStoreBuffered defer:NO]; [window_ center]; return window_ != nil; } private: NSWindow* window_; };
|
3. 控件移植
1 2 3 4 5 6 7 8 9 10 11 12 13
| class ButtonMac : public ViewMac { public: void SetText(const std::wstring& text) { if (button_) { NSString* s = [NSString stringWithUTF8String: std::string(text.begin(), text.end()).c_str()]; [button_ setTitle:s]; } } private: NSButton* button_; };
|
Label标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class LabelMac : public ViewMac { void SetFont(NSFont* font) { if (label_ && font) { [label_ setFont:font]; } } void SetTextColor(NSColor* color) { if (label_ && color) { [label_ setTextColor:color]; } } private: NSTextField* label_; };
|
完整控件清单
| 控件 |
macOS实现 |
状态 |
| View |
NSView |
✅ |
| Window |
NSWindow |
✅ |
| Widget |
NSView+NSWindow |
✅ |
| Button |
NSButton |
✅ |
| Label |
NSTextField |
✅ |
| TextField |
NSTextField |
✅ |
| Checkbox |
NSButton(checkbox) |
✅ |
| ComboBox |
NSPopUpButton |
✅ |
| ProgressBar |
NSProgressIndicator |
✅ |
| Slider |
NSSlider |
✅ |
| TabControl |
NSTabView |
✅ |
| Menu |
NSMenu |
✅ |
| Toolbar |
NSToolbar |
✅ |
| StatusBar |
NSView |
✅ |
| TreeView |
NSOutlineView |
✅ |
测试效果
1 2 3 4 5 6 7 8 9
| ═══════════════════════════════════════════════════════════════ Chromium Aura UI - macOS Complete Port 🦞 ═══════════════════════════════════════════════════════════════ Window: 1100x750 Completed: View, Widget, Button, Label, TextField Completed: Checkbox, ComboBox, ProgressBar, Slider Completed: TabControl, Menu, Toolbar, StatusBar Completed: Dialog, TreeView ═══════════════════════════════════════════════════════════════
|
关键技巧
1. 宏开关隔离
1 2 3 4 5 6 7 8 9 10 11 12 13
| if(APPLE) set(CMAKE_CXX_FLAGS "-DAURA_MAC") endif()
#ifdef AURA_MAC #import <Cocoa/Cocoa.h> #else #include <windows.h> #endif
|
2. 类型桥接
1 2 3 4 5 6 7 8
|
#ifndef _WIN32 typedef void* HWND; typedef void* HDC; typedef void* HICON;
#endif
|
3. 逐步替换
不要试图一次性移植所有代码,而是:
- 先让基础库编译通过
- 再逐个移植UI控件
- 最后整合测试
代码结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| chromium_ui/ ├── CMakeLists.txt # 主构建(跨平台) ├── base/ │ ├── atomicops.h # 原子操作 ✅ │ ├── atomicops_mac.h # macOS实现 │ ├── time.h # 时间处理 ✅ │ └── win_types.h # Windows类型桥接 ├── gfx/ │ ├── point.h, rect.h # 图形基础 ✅ │ └── ... ├── view_framework/ │ ├── view_mac.h/cpp # View (macOS) │ ├── window_mac.h/mm # Window (macOS) │ ├── widget_mac.h/mm # Widget (macOS) │ └── controls/ │ ├── button_mac.h/cpp # Button │ ├── label_mac.h/cpp # Label │ └── textfield_mac.h/cpp └── aura_mac/ # 独立macOS实现 ├── view/ ├── window/ └── controls/
|
编译运行
1 2 3 4 5 6 7 8 9 10 11
| git clone https://github.com/shangsh/chromium_ui.git cd chromium_ui
mkdir build && cd build cmake .. make
../test_complete/test_complete
|
总结
通过这次移植,我们学会了:
- 条件编译 - 用
#ifdef 实现跨平台
- 抽象接口 - 把平台相关代码抽象出来
- 逐步移植 - 从基础库到UI层
- 宏开关控制 - 不影响原有Windows编译
chromium_ui现在可以在Windows和macOS双平台编译运行!
项目地址: https://github.com/shangsh/chromium_ui
测试Demo:
- test_complete - 完整控件测试
- test_interactive - 交互测试
- test_advanced - 高级功能
有问题欢迎评论区交流~