前言:
個人習慣學習源碼先從編譯結構瞭解代碼模塊,所以先從編譯結構開始瞭解。
如果編譯角度搞不定,就代碼開始運行時,各個模塊初始化的角度開始瞭解。
學習源碼——編譯角度
下載源碼
git clone https://github.com/VincentWei/minigui.gitminigui
編譯
minigui使用autotools工具來生成Makefile文件,然後再執行make編譯。
一般操作流程是:
autoconf
auto make
./configure
make
但是不起作用啊,autoconf執行失敗。
於是直接運行autogen.sh,然後autoconf,automake,./configure都可運行了,但是make運行失敗。
想了想,還是放棄從編譯角度切入了吧
- 自動生成的Makefile太過複雜,難以解析
- autotools工具也不熟
學習源碼——運行角度
初始化入口:
MiniGUIAppMain
MiniGUIAppMain是用例的入口函數,所以從這個函數開始順騰摸瓜,瞭解代碼的模塊和結構
MiniGUIAppMain的定義:
#define MiniGUIMain \
MiniGUIAppMain (int args, const char* argv[]); \
int main_entry (int args, const char* argv[]) \
{ \
int iRet = 0; \
if (InitGUI (args, argv) != 0) { \
return 1; \
} \
iRet = MiniGUIAppMain (args, argv); \
TerminateGUI (iRet); \
return iRet; \
} \
int MiniGUIAppMain
從代碼可知,MiniGUIMain 會先運行InitGUI(),然後再運行MiniGUIAppMain,界面完了以後,還會調用TerminateGUI回收界面資源
模塊初始化
InitGUI()總共調用了下面20多個初始化函數,前置工作準備
初始化各個模塊初始化的函數,以及模塊的流程如下:
1. _sysvipc_mutex_sem_init
2. mg_InitSliceAllocator
3. mg_InitFixStr
4. mg_InitMisc
5. mg_InitGAL
6. InstallSEGVHandle
7. mg_InitSystemRes
8. mg_InitGDI
9. mg_InitScreenDC
10. license_create
11. mg_InitCursor
12. mg_InitLWEvent
13. mg_InitLFManager
14. mg_InitMenu
15. mg_InitControlClass
16. mg_InitAccel
17. mg_InitDesktop
18. mg_InitFreeQMSGList
19. createThreadInfoKey
20. SystemThreads
21. SetKeyboardLayout
22. SetCursor
23. SetCursorPos
24. mg_TerminateMgEtc
模塊作用瞭解
一般來說,每個模塊會有個小的說明文檔,但是沒有找到,所以算了。
看源碼應該大致能瞭解其作用,如果不能瞭解其作用,則跟着用例的創建窗口的代碼往下跑,看模塊是怎麼用的來學習他吧。
模塊作用總結如下:
1. _sysvipc_mutex_sem_init
信號量初始化,信號量是跨進程的,用於進程間共享內存鎖。
這個函數內部清空了許多sem鎖,然後重新創建。
功能:初始化鎖
2. mg_InitSliceAllocator
作用尚未了結清楚,初步理解爲窗口內存碎片整理
3. mg_InitFixStr
功能:字串補全,內存佔用2^n~2^(n+1)時,空間大小補全爲2^(n+1)
4. mg_InitMisc
功能:零零散散的雜項功能初始化,觀其實現時是存取配置項功能。還有個clipboard剪輯版,暫時不瞭解
5. mg_InitGAL
功能:創建一個video設備,並創建一個surface對象與video設備互相指向。video設備初始化時,會從底層map一段內存出來,然後由surface對其進行管理。
讀寫surface時會直接改變界面。
6. InstallSEGVHandle
功能:linux 系統信號監聽及處理
7. InitSystemRes
功能:建一張存數據的hash表,用於存儲資源。添加bmp內部資源,添加icon資源,添加樣式資源
8. InitGDI
全稱:global graphic interface
功能:包括了所有圖形操作的接口
9. InitScreenDC
功能:
1. 初始化InitBlockDataHeap(__mg_FreeClipRectList)
2. dc_InitClipRgnInfo,初始化dc池,後續取hdc就從這裏取
3. 初始化全局dc,__mg_screen_dc
4. 初始化全局dc,__mg_screen_sys_dc
10. license_create
功能:加載license相關資源,具體作用未明
11. InitCursor
功能:初始化鼠標資源
12. mg_InitLWEvent
功能:初始化IAL,事件輸入模塊
13. mg_InitLFManager
功能:初始化系統默認樣式
14. mg_InitMenu
功能:菜單相關
1. 初始化InitBlockDataHeap(MBHeap),作用未知
2. 初始化InitBlockDataHeap(MIHeap),作用未知
3. 初始化InitBlockDataHeap(TMIHeap),作用未知
15. mg_InitControlClass
功能:註冊系統默認樣式的控件
16. mg_InitAccel
功能:
1. 初始化InitBlockDataHeap(ACHeap),作用未知
2. 初始化InitBlockDataHeap(AIHeap),作用未知
17. mg_InitDesktop
功能:
1. 初始化z序的數據結構
2. 初始化InitBlockDataHeap(sg_FreeClipRectList)
3. 初始化InitBlockDataHeap(sg_FreeInvRectList)
4. 初始化__mg_hwnd_desktop,也就是DESKTOP_HWND
5. 初始化桌面的消息隊列,__mg_hwnd_desktop->pMessages = __mg_dsk_msg_queue
6. 設置系統默認的控件樣式
18. mg_InitFreeQMSGList
功能:初始化InitBlockDataHeap(QMSGHeap),作用未知
19. createThreadInfoKey
功能:初始化主線程的pthread_key,使主線程也有對應的線程標誌。
後續創建主窗口時,也是根據該線程標誌來獲取消息隊列的,也許還有很多和主線程pthread_key相關的模塊
20. SystemThreads
功能:創建兩個線程,一個是DesktopMain,另一個是EventLoop
DesktopMain:不停的接收並處理消息,凡是發給桌面的消息,都在該線程處理
EventLoop:運行IAL模塊,不停地獲取事件
21. SetKeyboardLayout
功能:初始化鍵盤佈局相關,作用未知
22. SetCursor&SetCursorPos
功能:預測設置光標,作用未知
23. mg_TerminateMgEtc
功能:結束配置管理模塊,作用未知
GAL模塊解析
按照從上向下的順序,第一我先詳細說明GAL模塊。
GAL模塊的初始化,我就不按初始化的流程來說明,我先將模塊拆解成一個個小模塊,然後介紹完小模塊以後,再說明整個初始化的流程。
video驅動管理模塊——video-drivers
video會有各種不同廠商的設備,不同廠商的設備會有不同的驅動,所以video-drivers模塊包含了很多video視頻設備的驅動
所有的這些video驅動模塊都保存在boot_strap(VideoBootStrap**)這個全局變量當中
src/newgal/video.c 中可以看到這個boot_strap這個全局變量包含了哪些驅動:
/* Available video drivers */
static VideoBootStrap *bootstrap[] = {
#ifdef _MGGAL_DUMMY
&DUMMY_bootstrap,
#endif
#ifdef _MGGAL_FBCON
&FBCON_bootstrap,
#endif
#ifdef _MGGAL_QVFB
&QVFB_bootstrap,
#endif
...
NULL
};
然後我們只挑FBCON_bootstrap,稍微深入瞭解下。
fbcon驅動模塊
fbcon模塊的源碼目錄在:src\newgal\fbcon\
我們先看fbvideo.c文件,FBCON_bootstrap的定義:
VideoBootStrap FBCON_bootstrap = {
"fbcon", "Linux Framebuffer Console",
FB_Available, FB_CreateDevice
};
GAL模塊初始化,選中FBCON_bootstrap驅動時,會調用FB_CreateDevice獲取一個video設備對象,我們稱之爲’video’
以後我們看看FB_CreateDevice,對返回的video設備對象做了哪些初始化:
this->VideoInit = FB_VideoInit;
this->ListModes = FB_ListModes;
this->SetVideoMode = FB_SetVideoMode;
this->SetColors = FB_SetColors;
this->VideoQuit = FB_VideoQuit;
this->AllocHWSurface = FB_AllocHWSurface;
this->CheckHWBlit = NULL;
this->FillHWRect = NULL;
this->SetHWColorKey = NULL;
this->SetHWAlpha = NULL;
this->UpdateRects = NULL;
this->FreeHWSurface = FB_FreeHWSurface;
this->GetFBInfo = FB_GetFBInfo;
this->free = FB_DeleteDevice;
這些函數就是video設備對象可以執行的操作了
操作很多也很複雜,但是我只瞭解了FB_VideoInit和FB_SetVideoMode兩個函數的實現
因爲初始化就用到了這兩個函數
FB_VideoInit
FB_VideoInit函數中,映射了一段內存到video->hidden->mapped_mem:
#define mapped_mem (this->hidden->mapped_mem)
mapped_mem = mmap(NULL, mapped_memlen,
PROT_READ|PROT_WRITE, MAP_SHARED, console_fd, 0);
以及計算了一個偏移地址:
ioctl(console_fd, FBIOGET_FSCREENINFO, &finfo)
/* Memory map the device, compensating for buggy PPC mmap() */
mapped_offset = (((long)finfo.smem_start) -
(((long)finfo.smem_start)&~(getpagesize () - 1)));
mapped_memlen = finfo.smem_len+mapped_offset;
FB_SetVideoMode
FB_SetVideoMode函數中,對傳入的參數current(也就是surface)進行了初始化
/* Set up the new mode framebuffer */
current->flags = (GAL_FULLSCREEN|GAL_HWSURFACE);
current->w = vinfo.xres;
current->h = vinfo.yres;
current->pitch = finfo.line_length;
current->pixels = mapped_mem+mapped_offset;
其實SetVideoMode就有點像告訴video驅動,我要要顯示的圖像的大小,格式,你給我一張合適的畫布放surface裏,我自己繪圖
總結:映射的這段buffer,會被video控制器以一定頻率不停的讀取顯示到video設備上
而這段內存又保存在了surface中,所以操作surface->pixels,就有點像直接在設備界面上繪圖
surface模塊
源碼位置:src/newgal/surface.c
surface模塊的初始化入口是GAL_CreateRGBSurface函數,這個函數裏面會new一個surface對象,
然後由GAL_VideoSurface這個全局指針接管。據我目前觀察,之後大多數情況都是使用的GAL_VideoSurface
GAL初始化流程
文件入口:src\newgal\newgal.c
mg_InitGAL:
GAL_VideoInit:
GAL_GetVideo:
FBCON_bootstrap->create:
FB_CreateDevice:
創建一個video設備對象,並返回
video->VideoInit:
映射一段buffer保存在video->hidden->mapped_mem中
初始化全局變量:
current_video = video
current_video->screen = NULL
GAL_VideoSurface = GAL_CreateRGBSurface(...):
創建一個surface
初始化全局變量:
GAL_VideoSurface->video = current_video;
__gal_screen = GAL_SetVideoMode:
video->SetVideoMode:
根據mode設置surface內容,並返回
初始化GDI模塊的全局變量:SysPixelIndex
IAL模塊解析
函數入口:mg_InitLWEvent->mg_InitIAL
文件入口:src/kernel/event.c->src/ial/ial.c
負責事件獲取,並轉成windows message上傳到設備端
IAL引擎
同video有衆多設備驅動一樣,IAL也有衆多引擎
根據配置裏的IAL引擎名稱,IAL模塊會選擇其中一個引擎賦值到全局變量__mg_cur_input中
然後使用__mg_cur_input來進行引擎操作,一下是IAL所有的引擎,我只取libinput說明一下
static INPUT inputs [] =
{
/* General IAL engines ... */
#ifdef _MGIAL_QVFB
{"qvfb", InitQVFBInput, TermQVFBInput},
#endif
...
#ifdef _MGIAL_LIBINPUT
{"libinput", InitLibInput, TermLibInput},
#endif
/* ... end of general IAL engines */
};
libinput
libinput的入口函數是InitLibInput,IAL獲取到libinput之後,便會調用該接口
這裏我只說明兩個函數,一個是初始化函數InitLibInput,以及wait_event_ex
因爲初始化會用到InitLibInput,以及之後讀取事件會調用wait_event_ex
InitLibInput
InitLibInput對於input引擎的初始化:
...
input->update_mouse = mouse_update;
input->get_mouse_xy = mouse_getxy;
input->set_mouse_xy = mouse_setxy;
input->get_mouse_button = mouse_getbutton;
input->set_mouse_range = mouse_setrange;
input->suspend_mouse= input_suspend;
input->resume_mouse = input_resume;
input->update_keyboard = keyboard_update;
input->get_keyboard_state = keyboard_getstate;
input->suspend_keyboard = input_suspend;
input->resume_keyboard = input_resume;
input->set_leds = set_leds;
input->wait_event_ex = wait_event_ex;
...
wait_event_ex
wait_event_ex可以獲取事件,並返回事件的類型
IAL初始化流程
mg_InitIAL:
InitLibInput:
初始化__mg_cur_input,並給其接口賦初值,之後便是用這個全局對象獲取事件
SystemThreads模塊解析
函數入口:SystemThreads
文件入口:src/kernel/init.c
該模塊主要是運行起桌面模塊和IAL模塊,將這兩者關聯到一塊
創建線程DesktopMain
初始化桌面
調用init_desktop_win函數,初始化桌面參數,如下:
...
static MAINWIN desktop_win;
pDesktopWin = &desktop_win;
pDesktopWin->pMessages = __mg_dsk_msg_queue;
pDesktopWin->MainWindowProc = DesktopWinProc;
...
pDesktopWin->pMainWin = pDesktopWin;
pDesktopWin->we_rdr = __mg_def_renderer;
__mg_hwnd_desktop = (HWND)pDesktopWin;
__mg_dsk_win = pDesktopWin;
...
注意:
#define HWND_DESKTOP __mg_hwnd_desktop
也就是說HWND_DESKTOP就是桌面本身,類型是MAINWIN
消息處理
獲取並處理消息的步驟如下:
while (GetMessage(&Msg, HWND_DESKTOP)) {
...
DesktopWinProc (HWND_DESKTOP,
Msg.message, Msg.wParam, Msg.lParam);
...
}
創建線程EventLoop
循環讀消息
IAL_WaitEvent
發消息
發送消息到HWND_DESKTOP桌面的毀掉函數中
control模塊解析
函數入口:mg_InitControlClass
文件入口:src\gui\ctrlclass.c
button控件
文件入口:src\control\button.c
函數入口:RegisterButtonControl
控件有很多自定義模塊,我這裏就專門挑button說下
控件的繪製是在WinProc中的WM_PAINT中進行的,RegisterButtonControl函數中會
將WNDCLASS類型的對象填上一些參數,然後調用AddNewControlClass。具體代碼如下:
BOOL RegisterButtonControl (void)
{
WNDCLASS WndClass;
WndClass.spClassName = CTRL_BUTTON;
WndClass.dwStyle = WS_NONE;
WndClass.dwExStyle = WS_EX_NONE;
WndClass.hCursor = GetSystemCursor (IDC_ARROW);
WndClass.iBkColor =
GetWindowElementPixel (HWND_NULL, WE_MAINC_THREED_BODY);
WndClass.WinProc = ButtonCtrlProc;
return AddNewControlClass (&WndClass) == ERR_OK;
}
AddNewControlClass函數會根據WndClass的內容創建一個CTRLCLASSINFO結構的對象,然後將其保存到ccitable鏈表中
初始化流程
BOOL mg_InitControlClass ()
{
int i;
for (i=0; i<LEN_CCITABLE; i++)
ccitable[i] = NULL;
// Register system controls here.
#ifdef _MGCTRL_STATIC
if (!RegisterStaticControl ())
return FALSE;
#endif
#ifdef _MGCTRL_BUTTON
if (!RegisterButtonControl())
return FALSE;
#endif
...
}
GDI模塊初始化
入口函數有兩個:mg_InitScreenDC,mg_InitGDI
入口文件:src/newgdi/gdi.c
前面模塊瞭解部分已經初步學習,所以就不寫了
mg_InitScreenDC
dc_InitClipRgnInfo
初始化DC池,將每一個DC和surface掛鉤,具體初始化如下:
for (i=0; i<DCSLOTNUMBER; i++) {
/* Local clip region */
InitClipRgn (&DCSlot[i].lcrgn, &__mg_FreeClipRectList);
MAKE_REGION_INFINITE(&DCSlot[i].lcrgn);
/* Global clip region info */
DCSlot[i].pGCRInfo = NULL;
DCSlot[i].oldage = 0;
/* Effective clip region-- 有效剪輯區,暫時不瞭解 */
InitClipRgn (&DCSlot[i].ecrgn, &__mg_FreeClipRectList);
}
dc_InitScreenDC
調用兩次,初始化__mg_screen_dc和__mg_screen_sys_dc
具體初始化的內容,我提煉了一下,如下:
1. pdc->DataType = TYPE_HDC;
2. 初始化bkcolor
3. 初始化畫筆顏色,和樣式
4. 初始化畫刷顏色
5. 初始化DevRC,設備可顯示屏幕大小
6. pdc->surface = surface;
7. pdc->cur_dst = pdc->surface->pixels;
...