GacUI源码简析(一)
本文中介绍的 GacUI 源代码来自于 https://github.com/vczh-libraries/GacUI。
在 GacUI 中,`WinMain` 开始后,第一个执行的函数为 `SetupWindowsDirect2DRenderer` :
int SetupWindowsDirect2DRenderer()
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
HINSTANCE hInstance=(HINSTANCE)GetModuleHandle(NULL);
WinDirect2DApplicationDirect2DObjectProvider objectProvider;
SetWindowsDirect2DObjectProvider(&objectProvider);
return WinMainDirect2D(hInstance, &RendererMainDirect2D);
}
可以通过 `WinDirect2DApplicationDirect2DObjectProvider` 获取到许多 factory(如 D2D1Factory,DWriteFactory etc.)但是注意的是,这些资源并不存储在 `WinDirect2DApplicationDirect2DObjectProvider` 中。接着将目光转到 `WinMainDirect2D`:
int WinMainDirect2D(HINSTANCE hInstance, void(*RendererMain)())
{
EnableCrossKernelCrashing();
// create controller
INativeController* controller=CreateWindowsNativeController(hInstance);
SetCurrentController(controller);
{
// install listener
Direct2DWindowsNativeControllerListener listener;
controller->CallbackService()->InstallListener(&listener);
direct2DListener=&listener;
// main
RendererMain();
// uninstall listener
direct2DListener=0;
controller->CallbackService()->UninstallListener(&listener);
}
// destroy controller
DestroyWindowsNativeController(controller);
return 0;
}
可以注意到,首先它调用了`CreateWindowsNativeController` 得到了一个 Controller,这个 Controller 真可谓是 GacUI 的核心!但是先不急,我们先来看看 `SetCurrentController` 干了什么:
INativeController* currentController=0;
INativeController* GetCurrentController()
{
return currentController;
}
void SetCurrentController(INativeController* controller)
{
currentController=controller;
}
可以看出,在 GacUI 中有一个全局变量 `currentController` 。现在看看这个 Controller 到底是何方神圣:
class WindowsController : public Object, public virtual INativeController, public virtual INativeWindowService
{
protected:
WinClass windowClass;
WinClass godClass;
HINSTANCE hInstance;
HWND godWindow;
Dictionary<HWND, WindowsForm*> windows;
INativeWindow* mainWindow;
HWND mainWindowHandle;
WindowsCallbackService callbackService;
WindowsResourceService resourceService;
WindowsAsyncService asyncService;
WindowsClipboardService clipboardService;
WindowsImageService imageService;
WindowsScreenService screenService;
WindowsInputService inputService;
WindowsDialogService dialogService;
可以看出:
-
它掌管了窗口的
WinClass
以及
GodWindow
的
WindowClass
; -
它还掌管了众多
Service
,例如
InputService
,处理输入;
ClipBoardService
,处理剪贴板; -
维护了一个
Dictionary<HWND,WinForm*>
,以便在处理
WndProc
时根据
hWnd
迅速找出对应的
WinForm
。
`WinClass`是对 `WNDCLASSEX` 的一个包装,但是要重点注意这里:
WindowsController(HINSTANCE _hInstance)
:hInstance(_hInstance)
,windowClass(L"VczhWindow", false, false, WndProc, _hInstance)
,godClass(L"GodWindow", false, false, GodProc, _hInstance)
,...
可以看到,`VczhWindow` 的窗口过程注册为 `WndProc`:
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
WindowsController* controller=dynamic_cast<WindowsController*>(GetCurrentController());
if(controller)
{
LRESULT result=0;
if(controller->HandleMessage(hwnd, uMsg, wParam, lParam, result))
{
return result;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
也就是说,当消息来到时,先获取上文提到的,全局存储的 Controller,再将真正的消息处理转交给 Controller。而在 Controller 中就可以看到,最后将消息转发给了 `WinForm`:
WindowsForm* window=windows.Values().Get(index);
skipDefaultProcedure=window->HandleMessage(hwnd, uMsg, wParam, lParam, result);
`WinForm` 拿到消息后,会转发给 Listener:
case WM_MBUTTONDBLCLK:
{
NativeWindowMouseInfo info=ConvertMouse(wParam, lParam, false, nonClient);
for(vint i=0;i<listeners.Count();i++)
{
listeners[i]->MiddleButtonDoubleClick(info);
}
}
break;
消息就这样转发出去了!
而
Direct2DWindowsNativeControllerListener
则存储了
Direct2D、3D
需要的一些 Factory:
class Direct2DWindowsNativeControllerListener : public Object, public INativeControllerListener
{
public:
Dictionary<INativeWindow*, Ptr<Direct2DWindowsNativeWindowListener>> nativeWindowListeners;
ComPtr<ID2D1Factory> d2dFactory;
ComPtr<IDWriteFactory> dwrite;
ComPtr<ID3D11Device> d3d11Device;
...
至于`direct2DListener`,不言自明,又是全局变量(这里插一句:可能 Javaer 又要批判没有使用所谓的 Singleton 了吧:)
接着,会调用 `renderMain()`,可以看到,`RendererMainDirect2D` 又调用了 `GuiApplicationMain`,`GuiApplicationMain` 本身又调用了 `GuiApplicationInitialize`,似乎终于要到了跑起来的时候了!
void GuiApplicationInitialize()
{
Ptr<theme::ITheme> theme;
{
WString osVersion=GetCurrentController()->GetOSVersion();
vint index=osVersion.IndexOf(L';');
if (index == -1)
{
theme=new win8::Win8Theme;
}
else
{
WString osMainVersion=osVersion.Sub(0, index);
if(osMainVersion==L"Windows 8" || osMainVersion==L"Windows Server 2012")
{
theme=new win8::Win8Theme;
}
else
{
theme=new win7::Win7Theme;
}
}
}
GetCurrentController()->InputService()->StartTimer();
GuiApplication app;
application=&app;
GetPluginManager()->Load();
GetGlobalTypeManager()->Load();
theme::SetCurrentTheme(theme.Obj());
GuiMain();
theme::SetCurrentTheme(0);
DestroyPluginManager();
DestroyGlobalTypeManager();
ThreadLocalStorage::DisposeStorages();
}
}
}
}
-
首先,根据系统版本选择主题(目前 GacUI 还没有兼容 Windows 10,因为
VerifyVersionInfo
又被微软废了。。。); -
开始
Timer
。
Timer
由
InputService
负责:
void WindowsInputService::StartTimer()
{
if(!IsTimerEnabled())
{
SetTimer(ownerHandle, 1, 16, NULL);
isTimerEnabled=true;
}
}
void WindowsInputService::StopTimer()
{
if(IsTimerEnabled())
{
KillTimer(ownerHandle, 1);
isTimerEnabled=false;
}
}
而
Timer
消息的处理又由
CallbackService
负责:
void WindowsCallbackService::InvokeGlobalTimer()
{
for(vint i=0;i<listeners.Count();i++)
{
listeners[i]->GlobalTimer();
}
}
顺便说一句,GacUI 的渲染不是在
WM_PAINT
中完成的,是使用这个
Timer
,每 16ms 触发一次。
-
老伎俩,定义一个
GuiApplication
,并把全局的
application
设置好; -
执行
GuiMain
。通常在
GuiMain
中,会出现:
GetApplication()->Run(&window);
这个
Run
十分直白:
void GuiApplication::Run(GuiWindow* _mainWindow)
{
if(!mainWindow)
{
mainWindow=_mainWindow;
GetCurrentController()->WindowService()->Run(mainWindow->GetNativeWindow());
mainWindow=0;
}
}
接着会调用 Controller 的
Run
:
void Run(INativeWindow* window)
{
mainWindow=window;
mainWindowHandle=GetWindowsForm(window)->GetWindowHandle();
mainWindow->Show();
MSG message;
while(GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
asyncService.ExecuteAsyncTasks();
}
}
呼,程序终于
Run
了起来。