对于广大的玩家而言,Windows是一个不错的平台选择。特别是DirectX12展现的卓越性能使得大多大型游戏由其开发。但DX12只能在Win上运行,所以学习Win32API是必要的。
Create a new Project
> Windows Desktop Application
myWin32App:
g++ win32_window.cpp -o myWin32App -g -Wall -lgdi32 #-mwindow
一定要链接gdi32
如果以main为入口点,不想要黑框框就加上-mwindow选项
Typedef | 定义 |
---|---|
CHAR | char |
PSTR 或 LPSTR | char* |
PCSTR 或 LPCSTR | const char* |
PWSTR 或 LPWSTR | wchar_t* |
PCWSTR 或 LPCWSTR | const wchar_t* |
当 Microsoft 为Windows引入 Unicode 支持时,它通过提供两组并行 API(一个用于 ANSI 字符串,另 一个用于 Unicode 字符串)来缓解转换。 例如,有两个函数用于设置窗口标题栏的文本:
SetWindowTextW(); // Unicode function with wide-character string. SetWindowTextA(); // ANSI function.
在内部,ANSI 版本将字符串转换为 Unicode。 Windows标头还定义了一个宏,该宏在定义预处理器符号
UNICODE
或 ANSI 版本时解析为 Unicode 版本。
----Microsoft Docs
为了适应国际化编程,我们将使用Unicode编码。
#ifdef UNICODE
#define SetWindowText SetWindowTextW
#else
#define SetWindowText SetWindowTextA
#endif
定义UNICODE以后,我们就不必为用W还是A担心了。但要注意,入口点仍是wWinMain
。
在c++中L“xxx”是定义宽字符,其对应类型是wchar_t
。
在
中对其做了定义:
/****/
typedef wchar_t WCHAR;
typedef WCHAR TCHAR, *PTCHAR;
#define TEXT(quote) __TEXT(quote)
#define __TEXT(quote) L##quote
宏 | Unicode | ANSI |
---|---|---|
TCHAR | wchar_t |
char |
TEXT (“x”) | L"x" |
"x" |
每个完整的C++程序都有自己的入口点函数,例如最常用的C++ Console入口点main。
Win32api 有自己的入口点函数WinMain
或wWinMain
,他和C++ Console看起来如下:
int main(int argc,char* argv[]);//Console entry-point function.
int WINAPI wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PWSTR pCmdLine,
int nCmdShow);//Win32 Application entry-point function.
我们下面来解释下参数:
argv
一样.如果你不想用WinMain作为入口点而用main其实也可以,其实很多库,比如GLFW就是可以让你在console下创建win32窗口的。
我们可以通过获取Console的窗口句柄然后通过GetWindowLong
获得实例句柄创建Win32窗口,具体代码看起来如下:
HWND hConWnd = GetConsoleWindow();
HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hConWnd, GWLP_HINSTANCE);
窗口类是一个属性集,是Windows编程中用于创建窗口的模板。每一个窗口类都有一个WndProc,负责处理发送该类窗口的所有消息。
下面让我们来看一个例子:
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
hInstance
参数获取此值。类名是当前进程的本地名称,因此该名称仅在进程内唯一。 但是,标准Windows控件也有类。 如果使用这些控件中的任何一个,则必须选取与控件类名不冲突的类名。 例如,按钮控件的窗口类名为“Button”。
以上3个成员是必须要有的,还有一些其他的成员:
typedef struct tagWNDCLASSW {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;
#ifdef UNICODE
typedef WNDCLASSW WNDCLASS;
//...
#endif
这些我们以后再讲,有兴趣可以参见
WNDCLASSW (winuser.h) - Win32 apps | Microsoft Docs
将WNDCLASS
结构的地址传递给RegisterClass
函数。 此函数向操作系统注册窗口类。
RegisterClass(&wc);
创建窗口我们可以调用CreateWindow
或CreateWindowEx
第二个会多一个扩展参数。
我们一般用第二个:
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"My Application", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
CreateWindowEx
也是多于此处。CLASS_NAME
是窗口类的名称。我们已经定义过了。WS_OVERLAPPEDWINDOW
实际上是几个标志与按位 OR 组合。 这些标志一起为窗口提供标题栏、边框、系统菜单,以及 最小化 和 最大化 按钮。 此标志集是顶级应用程序窗口最常见的样式。CW_USEDEFAULT
表示使用默认值。最后我们要让窗口可见:
ShowWindow(hwnd, nCmdShow);
这样窗口就完成创建了。
一个程序有交互,我们需要获取这些交互:
MSG msg;
GetMessage(&msg, NULL, 0, 0);
GetMessage
可以从消息队列中拉取这些消息,我们并不用担心窗口会无响应。消息队列是另一线程中的。
GetMessage
的第一个参数是 MSG结构的地址。 如果函数成功,它将用有关消息的信息填充 MSG
结构。 这包括目标窗口和消息代码。 其他三个参数允许筛选从队列获取的消息。 几乎所有情况下,都将这些参数设置为零。
尽管MSG
结构包含有关消息的信息,但几乎永远不会直接检查此结构。 而是将它直接传递给另外两个函数。
TranslateMessage(&msg); DispatchMessage(&msg);
TranslateMessage
函数与键盘输入相关。
DispatchMessage
函数告知操作系统调用窗口过程,该窗口是消息的目标。 换句话说,操作系统在其窗口表中查找窗口句柄,查找与窗口关联的函数指针,并调用该函数。
例如,假设用户按下鼠标左键。 这会导致事件链:
WM_LBUTTONDOWN
消息。GetMessage
函数。GetMessage
从队列中拉取 WM_LBUTTONDOWN
消息,并填充 MSG
结构。TranslateMessage
和DispatchMessage
函数。DispatchMessage
中,操作系统调用窗口过程。同时GetMessage
又接收着退出消息WM_QUIT
,GetMessage
就会返回0。代码如下:
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)//Main Loop.
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
如果要中途要退出,我们可以用:
PostQuitMessage(0);
DispatchMessage
函数会调用消息目标窗口的窗口过程,窗口过程的签名如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
然后我们用switch语句去处理WM消息:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
switch (uMsg)
{
//...
}
}
如果不在窗口过程中处理特定消息,请将消息参数直接传递给DefWindowProc函数。 此函数对消息执行默认操作,该操作因消息类型而异。
return DefWindowProc(hwnd, uMsg, wParam, lParam);
这是一个窗口,它由客户区(工作区) 和 非客户区(非工作区)
客户区(Client Area):中间白色的部分
非客户区(Non-client Area):
标题栏
边框
我们现在只需绘制客户区,下面是将客户区填充白色的一个例子:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint,CreateSolidBrush(RGB(255, 255, 255)));
EndPaint(hwnd, &ps);
}
return 0;
如果要深入研究绘图可以学习 图形设备接口 (GDI) 、GDI+ 或 Direct2D 的内容,其中以上的绘制是 GDI 实现的。
此示例,我们不需要过多了解。
当用户关闭窗口时,该操作将触发窗口消息序列。窗口会接收到WM_CLOSE。
case WM_CLOSE:
if (MessageBox(hwnd, L"Really quit?", L"My Application", MB_OKCANCEL) == IDOK)
{
DestroyWindow(hwnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
上面的代码我们用MessageBox创建了个包含“确定”和“取消”按钮的对话框。 如果用户单击 “确定”,程序将调用DestroyWindow。 否则,如果用户单击 “取消”,则会跳过对 DestroyWindow的调用,并且窗口保持打开状态。
当窗口即将被销毁时,它将收到 WM_DESTROY消息。 此消息是在从屏幕中删除窗口后发送的。在主应用程序窗口中,通常通过调用PostQuitMessage来响应WM_DESTROY以退出主循环。
win32-window.cpp:wWinMain入口点
win32-window-console.cpp:main入口点
更多源码请克隆 https://github.com/OrbitGW/blog-sources.git
CSDN
bilibili
博客园
知乎