Win32汇编系列七、窗口程序原理及实现

本文详细介绍如何使用汇编语言创建窗口程序,包括注册窗口类、处理消息循环、响应鼠标点击及创建按钮控件的过程。文章深入探讨了窗口过程函数、消息机制以及使用CreateWindowEx函数创建窗口和按钮的方法。

前言

学习c语言的人是不是有段这样的经历,天天面对黑框框敲来敲去,有木有?不知道是否想过用c写一个窗口程序?我想应该也很希望吧,目前我见过的书中很少有介绍窗口程序设计,如果想写的话,还需要去网上、b站搜索,或者看Window程序设计之类的书,使用c写窗口程序其实挺麻烦的,需要了解Windows消息机制、GDI或者GDI+、还有各种控件,如Button,另外自带的控件都太难看,需要使用GDI或GDI+自己绘制,这样工作量又大了很多,所以现在很少有软件用这种最"底层"的方式实现了,都是各种框架,或者使用C#、WPF,我不曾在c中大量写过,但是在VB中大量写过。

这里你可能有觉得矛盾,VB中需要你自己创建窗口、创建控件?当然不用,全是为了学习Windows机制,在VB中,可以不用新建窗口,通过sub main方式启动,在方法中调用大量系统API,完成创建窗口,窗口控件,以及控件的事件响应,这样下来也上百行了,工作量还是很大的。

另外也可以使用别的语言实现,如C#,通过调用系统API,创建窗口等,Java也可以调用Windows API,但是使用相关的API创建窗口,我并没试过(不是说JFrame)。有时间试试。

(其实不太喜欢做Java,我喜欢研究Windows,奈何当初选错了路,导致现在不能回头,如果在回头后会浪费大量时间重新学习,小伙伴做选择要慎重哦!!!也希望有共同爱好的小伙伴一起交流)

实现过程

今天的主角是汇编实现,代码量也不少,原理是invoke调用大量API完成,创建窗口不管用什么语言实现,原理都一样,先注册一个窗口类、窗口调用CreateWindow创建窗口,然后在窗口过程函数中不断处理消息即可(前提这个语言能调用Win Api)。

下面介绍三个必要的函数。

RegisterClassEx

如果要创建一个创建,首先必须调用RegisterClassEx注册一个窗口类,他的参数只有一个,WNDCLASSEX结构体,但是这个结构体有点复杂,成员非常多。

typedef struct WNDCLASSEXA {
  UINT      cbSize;
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCSTR    lpszMenuName;
  LPCSTR    lpszClassName;
  HICON     hIconSm;
} WNDCLASSEXA, *PWNDCLASSEXA, *NPWNDCLASSEXA, *LPWNDCLASSEXA;

这里不全部介绍了,但是有一个很重要的需要说一下,就是lpfnWndProc成员,指向一个函数过程,这就牵扯出Windows的消息机制。

窗口过程函数

这个不太好理解,但是写过一次Windows程序的人就明白了,当我们操作一个窗口时,不论是单机、双击、右击、鼠标移动、还是任何你发出的事件,它就会接收到系统发过来的消息,消息中就会标识这个事件是什么,然后我们需要根据消息标识做出不同响应,而就需要我们写一个窗口过程函数,告诉系统,发生消息时,你给我回调到这个地方,如果在这个函数中不处理消息,就可以调用DefWindowProc函数来处理,这是系统的默认消息处理函数。

这个函数过程的参数也是固定的,在官方文档中是这样定义的,这里面又会牵扯出句柄的概念,简单说一下,句柄就是标识一个窗口的一个整数,就像身份证号一样。参数uMsg就是消息标识,也是个整数,系统把他们都定义好了,常量名都是WM_开头得,如WM_MOVE,在窗口移动窗口时系统会通知我们。

LRESULT CALLBACK WindowProc(
  _In_ HWND   hwnd,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

CreateWindowEx

创建一个窗口,返回窗口句柄,参数都比较简单,这里最有意思的是窗口的样式参数,有非常多的样式可选择,平常我们是见不到这些样式的。

HWND CreateWindowExA(
  DWORD     dwExStyle,
  LPCSTR    lpClassName,
  LPCSTR    lpWindowName,
  DWORD     dwStyle,
  int       X,
  int       Y,
  int       nWidth,
  int       nHeight,
  HWND      hWndParent,
  HMENU     hMenu,
  HINSTANCE hInstance,
  LPVOID    lpParam
);

CreateWindow完成之后,就需要不断循环从消息队列获取消息,进行分发。

好,上代码!!!

.386 
.model flat,stdcall 
option casemap:none 
include c:\masm32\include\windows.inc 
include c:\masm32\include\user32.inc 
includelib c:\masm32\lib\user32.lib           
include c:\masm32\include\kernel32.inc 
includelib c:\masm32\lib\kernel32.lib 

WinMain proto :DWORD

.DATA                   
ClassName db "MyWindowClass",0       
AppName db "汇编实现窗口",0       
.DATA?              
hInstance HINSTANCE ?
.CODE             
start: 
invoke GetModuleHandle, NULL   
                        
mov hInstance,eax 
invoke WinMain, hInstance
invoke ExitProcess, eax       
WinMain proc hInst:HINSTANCE
    LOCAL wc:WNDCLASSEX              
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 

    mov   wc.cbSize,SIZEOF WNDCLASSEX   
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInstance 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_WINDOW+1 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, addr wc   
    invoke CreateWindowEx,NULL,\ 
                ADDR ClassName,\ 
                ADDR AppName,\ 
                WS_OVERLAPPEDWINDOW,\ 
                0,\ 
                0,\ 
                300,\ 
                300,\ 
                NULL,\ 
                NULL,\ 
                hInst,\ 
                NULL 
    mov   hwnd,eax 
    invoke ShowWindow, hwnd,SW_SHOWDEFAULT   
    invoke UpdateWindow, hwnd               

    .WHILE TRUE                                             
		invoke GetMessage, ADDR msg,NULL,0,0 
        .BREAK .IF (!eax) 
        invoke TranslateMessage, ADDR msg 
        invoke DispatchMessage, ADDR msg 
   .ENDW 
    mov     eax,msg.wParam                      
    ret 
WinMain endp 

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
       invoke DefWindowProc,hWnd,uMsg,wParam,lParam  
    ret 
WndProc endp 

end start 

运行后,得出个窗口,使用了80多行。
在这里插入图片描述

判断鼠标单击

接下来,我们做个简单的修改,当鼠标单击窗口的时候,在窗口打印"Hello GUI",但是这里面又涉及到HDC的知识,小伙伴自行了解哦。

.386 
.model flat,stdcall 
option casemap:none 
include c:\masm32\include\windows.inc 
include c:\masm32\include\user32.inc 
includelib c:\masm32\lib\user32.lib           
include c:\masm32\include\kernel32.inc 
includelib c:\masm32\lib\kernel32.lib 

WinMain proto :DWORD

.DATA                   
ClassName db "MyWindowClass",0       
AppName db "汇编实现窗口",0       
HelloGUI db "HelloGUI",0     
.DATA?              
hInstance HINSTANCE ?
hWindowHdc HDC ?
.CODE             
start: 

invoke GetModuleHandle, NULL   
mov hInstance,eax 
invoke WinMain, hInstance
invoke ExitProcess, eax       
WinMain proc hInst:HINSTANCE
    LOCAL wc:WNDCLASSEX              
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 

    mov   wc.cbSize,SIZEOF WNDCLASSEX   
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInstance 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_WINDOW+1 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, ADDR wc   
    invoke CreateWindowEx,NULL,\ 
                ADDR ClassName,\ 
                ADDR AppName,\ 
                WS_OVERLAPPEDWINDOW,\ 
                0,\ 
                0,\ 
                300,\ 
                300,\ 
                NULL,\ 
                NULL,\ 
                hInst,\ 
                NULL 
    mov   hwnd,eax 
    invoke GetDC,eax
    mov  hWindowHdc,eax
    invoke ShowWindow, hwnd,SW_SHOWDEFAULT   
    invoke UpdateWindow, hwnd               

    .WHILE TRUE                                             
		invoke GetMessage, ADDR msg,NULL,0,0 
        .BREAK .IF (!eax) 
        invoke TranslateMessage, ADDR msg 
        invoke DispatchMessage, ADDR msg 
   .ENDW 
    mov     eax,msg.wParam                      
    ret 
WinMain endp 

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
		LOCAL strRect:RECT
		.IF uMsg == WM_LBUTTONDOWN
			mov strRect.right,100
			mov strRect.bottom,100
			mov strRect.left,0
			mov strRect.top,0
			invoke DrawText,hWindowHdc, ADDR HelloGUI,8,ADDR  strRect,DT_VCENTER
		.ELSE
			invoke DefWindowProc,hWnd,uMsg,wParam,lParam  
			ret
		.ENDIF
    ret 
WndProc endp 

end start 

效果:

在这里插入图片描述

创建按钮

创建控件其实也是通过CreateWindowEx来完成,并不需要我们注册类名,使用系统定义好的类名即可,这部分可以查看文档:https://docs.microsoft.com/en-us/windows/win32/winmsg/about-window-classes。

最终还要给按钮设置WS_TABSTOP OR WS_VISIBLE OR WS_CHILD OR BS_DEFPUSHBUTTON这几个样式,其中WS_CHILD样式代表作为一个子窗口,否则会出现意料之外的。

当按钮单击后,系统会回调WM_COMMAND消息, wParam 低两字节是命令ID,这就需要在CreateWindowEx时候指明。

.386 
.model flat,stdcall 
option casemap:none 
include c:\masm32\include\windows.inc 
include c:\masm32\include\user32.inc 
includelib c:\masm32\lib\user32.lib           
include c:\masm32\include\kernel32.inc 
includelib c:\masm32\lib\kernel32.lib 

WinMain proto :DWORD

.DATA                   
ClassName db "MyWindowClass",0       
AppName db "汇编实现窗口",0    
ButtonClassName db "Button",0   
ButtonTitle db "按钮",0
HelloGUI db "HelloGUI",0

Message db "单机了按钮",0
MessageTitle db "提示",0
.DATA?              
hInstance HINSTANCE ?
hWindowHdc HDC ?
hButton HWND ?
hWindow HWND ?
.CONST 
ButtonID equ 1 
.CODE             


start: 

invoke GetModuleHandle, NULL   
mov hInstance,eax 
invoke WinMain, hInstance
invoke ExitProcess, eax       
WinMain proc hInst:HINSTANCE
    LOCAL wc:WNDCLASSEX              
    LOCAL msg:MSG 

    mov   wc.cbSize,SIZEOF WNDCLASSEX   
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInstance 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_WINDOW+1 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, ADDR wc   
    invoke CreateWindowEx,NULL,\ 
                ADDR ClassName,\ 
                ADDR AppName,\ 
                WS_OVERLAPPEDWINDOW,\ 
                0,\ 
                0,\ 
                300,\ 
                300,\ 
                NULL,\ 
                NULL,\ 
                hInst,\ 
                NULL 
    mov   hWindow,eax

    invoke CreateWindowEx,NULL,ADDR ButtonClassName,ADDR ButtonTitle, WS_TABSTOP OR  WS_VISIBLE OR WS_CHILD OR BS_DEFPUSHBUTTON  ,\
    0,0,100,100,hWindow,ButtonID,NULL,NULL
    mov hButton,eax

    invoke GetDC,eax
    mov  hWindowHdc,eax
    invoke ShowWindow, hWindow,SW_SHOWDEFAULT   
    invoke UpdateWindow, hWindow               

    .WHILE TRUE                                             
		invoke GetMessage, ADDR msg,NULL,0,0 
        .BREAK .IF (!eax) 
        invoke TranslateMessage, ADDR msg 
        invoke DispatchMessage, ADDR msg 
   .ENDW 
    mov     eax,msg.wParam                      
    ret 
WinMain endp 

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
		LOCAL strRect:RECT
		.IF uMsg == WM_LBUTTONDOWN
			mov strRect.right,100
			mov strRect.bottom,100
			mov strRect.left,0
			mov strRect.top,0
			invoke DrawText,hWindowHdc, ADDR HelloGUI,8,ADDR  strRect,DT_VCENTER
		.ELSEIF uMsg ==WM_COMMAND
				mov eax,wParam 
				.IF ax == ButtonID 
					invoke MessageBox,hWindow,ADDR Message,ADDR MessageTitle,MB_OK
				.ENDIF
				
		.ELSE
			invoke DefWindowProc,hWnd,uMsg,wParam,lParam  
			ret
		.ENDIF
    ret 
WndProc endp 

end start 


在这里插入图片描述
在这里插入图片描述

怎么样,学废了吗?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值