当前位置:编程学习 > C/C++ >>

用c++11封装win32界面库

0. 前言

  你是否也和我一样是一个业余c++玩家,经常用c++写一些带界面的小程序呢?每次都在vs里用鼠标拖各种控件,然后copy / paste一大堆win32的api?没用过mfc,wtl,qt,只用sdk? 本文不是介绍各api的用法,而是用抽象的方法来对这堆api进行封装,弄一个界面库方便自己使用,当然前提是对这些api有基本的了解。

  之前看过些界面库源码,尤其是egui,好多东西都是从它那学来的。这些库大多用到其他第三方的库,比如boost,一个原因是当时c++自带语法不完善,比如没有shared_ptr,lambda,functional, 现在的c++11包含了这大部分东西,也就不需要第三方库了,但需要较新的编译器。用vs编译的话,最低版本是vs2012+update 1 CTP 补丁。
 
 


1. 介绍

  就称这界面库叫 _gui 吧,整个 _gui 可以分为以下几部分

  1. thunk  用来把wnd_proc这种回调函数封装到class内部

  2. property 类似vb的属性,比如要把窗口灰掉(disable)


[cpp]
win->enabled = false; 

 win->enabled = false;
  3. event  事件,如按钮点击


[cpp]
btn->event.click += []() { cout << "button clicked" << endl; }; 

btn->event.click += []() { cout << "button clicked" << endl; };  4. initor 创建时初始化,比如 


[cpp]
wnd<edit> edt_psw = new<edit>().text('admin').size(200,30).password_type(true); 

wnd<edit> edt_psw = new<edit>().text('admin').size(200,30).password_type(true);  5. layout 布局

  如下图的垂直分割布局,拖动中间那条分隔条可以改变左右大小

先看个例子吧

 

\

 

 

2. thunk

  win32的窗口消息都是发送给该窗口类的wnd_proc,注册窗口类时都是给个全局函数:


[cpp]
WNDCLASS cls; 
... 
cls.lpfnWndProc = wnd_proc; // 则所有该类窗口的所有消息都会发送到这个 wnd_proc 

WNDCLASS cls;
...
cls.lpfnWndProc = wnd_proc; // 则所有该类窗口的所有消息都会发送到这个 wnd_proc但如果封装控件的话就有个问题,比如我们希望button类被点击时候执行 on_click() 成员函数

[cpp]
LRESULT button_proc(HWND, UINT msg, WPARAM, LPARAM) { 
    if(msg == WM_CLICK) 
        btn->on_click(); //无法调用,因为无法获得btn是哪个实例  
}; 
struct button { 
    void on_click() {} 
}; 
 
// 除非这样  
struct button { 
    void on_click() {} 
 
    LRESULT button_proc(HWND, UINT msg, WPARAM, LPARAM) { 
        if(msg == WM_CLICK) 
            this->on_click(); // 这样就ok  
    }; 
}; 

LRESULT button_proc(HWND, UINT msg, WPARAM, LPARAM) {
 if(msg == WM_CLICK)
  btn->on_click(); //无法调用,因为无法获得btn是哪个实例
};
struct button {
 void on_click() {}
};

// 除非这样
struct button {
 void on_click() {}

 LRESULT button_proc(HWND, UINT msg, WPARAM, LPARAM) {
  if(msg == WM_CLICK)
   this->on_click(); // 这样就ok
 };
};
thunk可以把上面的全局函数变成成员函数,先来看一下全局函数和成员函数的区别,调试时从反汇编可以看到

[cpp]
call global_func  //调用全局函数  
 
push ecx //对象指针,也就是 this  
call member_func  //调用成员函数 

call global_func  //调用全局函数

push ecx //对象指针,也就是 this
call member_func  //调用成员函数区别就是成员函数需要一个额外的this指针, 所以如果把 WNDCLASS.wnd_proc 指向一段内存,在这段内存里做两件事

1.  push ecx

2.  call member_func

就ok了, 这段内存就是thunk,用一个结构体来表示:


[cpp]
struct thunk_code { 
#pragma pack(push, 1) //取消默认的4字节对齐,pack后char,short只占1,2字节  
    unsigned short stub1;      // lea ecx, p_this  
    unsigned long  p_this;    
    unsigned char  stub2;      // mov eax,member_func  
    unsigned long  member_func;   
    unsigned short stub3;      // jmp eax  
#pragma pack(pop)  
    void init() { 
        stub1       = 0x0D8D; // lea ecx 的机器码  
        p_this      = 0; 
        stub2       = 0xB8; // mov eax 的机器码  
        member_func = 0; 
        stub3       = 0xE0FF; // jmp eax  
    } 
}; 
这段内存相当于执行了 
mov dword ptr [esp+4], p_this 
mov eax, member_func 
jmp eax 

struct thunk_code {
#pragma pack(push, 1) //取消默认的4字节对齐,pack后char,short只占1,2字节
 unsigned short stub1;      // lea ecx, p_this
 unsigned long  p_this;  
 unsigned char  stub2;      // mov eax,member_func
 unsigned long  member_func; 
 unsigned short stub3;      // jmp eax
#pragma pack(pop)
 void init() {
  stub1  = 0x0D8D; // lea ecx 的机器码
  p_this  = 0;
  stub2  = 0xB8; // mov eax 的机器码
  member_func = 0;
  stub3  = 0xE0FF; // jmp eax
 }
};
这段内存相当于执行了
mov dword ptr [esp+4], p_this
mov eax, member_func
jmp eax
(因为这段内存需要被执行,而如果直接 thunk_code code;  这个code是不可执行的,所以这里用 HeapAlloc 分配 sizeof(thunk_code) 大小的内存,然后调用init()来填充,参考 thunk.h 和 heap.h)

剩下要做的事就创建控件实例时给 p_this 和 member_func 赋值了,以button为例


[cpp]
struct button { 
    thunk<button, LRESULT(HWND,DWORD,WPARAM,LPARAM)> wnd_thunk; 
 
    button() { 
        wnd_thunk.init(this, &button::wnd_proc); // 给 thunk 的 p_this 和 member_func 赋值  
    } 
 
    LRESULT wnd_proc(HWND hwnd, DWORD msg, WPARAM wp, LPARAM lp) { 
        if(msg == WM_CLICK) { 
            this->on_click(); 
        } 
    } 
    void on_click() {} 
    void create() { 
        CreateWindow("BUTTON", ...); 
 
        // 创建完后替换原 wnd_proc 为 thunk  
        ::SetWindowLong(hwnd, GWL_WNDPROC, wnd_thunk.addr()); 
    } 
}; 

struct b

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,