纯C+纯手写+手动编译 一个windows 窗体应用(过程记录)

(纯C + 纯手写 + 手动编译) 一个Windows 窗体应用

本篇文章仅仅是作者的一个类似笔记一样的东西,作为记录。所以请勿出现如下不友善评论

啊,这不是某某IDE直接就可以生成的吗。
搞这个东西有什么意义啊,浪费时间。
正经人谁用Win32 API写窗体程序啊,Qt跨平台他不香嘛。

所以本文章只是记录一下,我遇到的问题和结果问题的过程思路等,或者有对Win32 API感兴趣的同学,可以参考,学习提出问题一起解决等。

因为只是个笔记,所以教程中可能有些不符合项目代码规范的地方,请谅解。


正文

0x00: 最终效果

在这里插入图片描述
本文章采用 MinGW-w64 下的 gccwindres 进行编译链接。
是从SourceForge上下载的MinGW-W64 GCC-8.1.0 其中 gccwindres 均为内置。
我选择的是x86_64-win32-sjlj (posix 线程模型的应该也可以。)

0x01: 创建一个资源脚本文件 – winGUI.rc

采用 GBK编码

#include "resource.h"

// 这条是manifest可以解决控件不跟随当前系统样式的问题,具体可以查看我之前发的文章
// ID_MANIFEST RT_MANIFEST "winGUI.exe.manifest"

ID_ICON ICON ".\\icon.ico"

MY_MENU MENU
BEGIN
	POPUP "文件(&F)"
	BEGIN
		MENUITEM "退出(&X)", ID_EXIT
	END
	POPUP "帮助(&H)"
	BEGIN
		MENUITEM "关于(&A)", ID_ABOUT
	END
END

为了降低文章难度,省去了非必要的manifest环节,可能导致按钮等控件没法跟随系统样式。详细内容可以查看我往期文章的详细内容,自行开启注释掉的代码

0x02: 创建一个资源文件的头文件 – resource.h

采用 UTF-8编码

// #define ID_MANIFEST 1000
#define ID_ICON 1001
#define MY_MENU 1010
#define ID_EXIT 1011
#define ID_ABOUT 1012

这里同上,去掉了manifest相关的宏定义,降低文章理解难度。阅读完成我往期这篇文章,或者高手可自行启用被注释掉的宏定义

0x03: 创建一个资源C语言源文件 – winGUI.c

采用 UTF-8编码

#include <windows.h>
#include "resource.h"

/* 函数原型 */

// 消息处理函数
LRESULT WINAPI wndProc(HWND, UINT, WPARAM, LPARAM);

// 注册窗口类
void registWindowClass(HINSTANCE);


// 入口函数
int APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR cmdLine, int cmdShow)
{
	// 注册窗口类
	registWindowClass(hIns);
	// 创建窗口句柄
	HWND hWnd = CreateWindow("MainWindow", "Title", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hIns, NULL);
	// 显示窗口并更新
	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	// 进入消息循环
	MSG nMsg = {0};
	while (GetMessage(&nMsg, NULL, 0, 0))
	{
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);
	}
	return 0;
}

// 消息处理函数
LRESULT CALLBACK wndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)
{
	switch (msgID)
	{
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		
		case WM_COMMAND:
			switch (LOWORD(wParam))
			{
				case ID_EXIT:
					PostQuitMessage(0);
					break;
				
				case ID_ABOUT:
					MessageBoxW(hWnd, L"作者: Mycelium", L"关于", MB_OK);
					break;
			}
	}
	return DefWindowProc(hWnd, msgID, wParam, lParam);
}

// 注册窗口类函数
void registWindowClass(HINSTANCE hIns)
{
	WNDCLASS wc = {0};
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.hCursor = NULL;
	wc.hIcon = LoadIconA(hIns, MAKEINTRESOURCEA(ID_ICON));
	wc.hInstance = hIns;
	wc.lpfnWndProc = wndProc;
	wc.lpszClassName = "MainWindow";
	wc.lpszMenuName = MAKEINTRESOURCEA(MY_MENU);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	RegisterClass(&wc);
}

0x04: 添加图标文件并确保这些源文件在同一个目录

找到一个自己喜欢的图标文件(扩展名是 .ico)的
我这里找的是 Minecraft材质包里的 png, 用的在线转换网站准换成了 64x64像素的ico文件
在这里插入图片描述
确保四个文件在同一文件夹,再新建两个文件夹用来 存储之后编译生成的中间文件(obj)和最终的可执行文件(bin)
在这里插入图片描述

0x05: 开始编译源码

REM 编译资源文件
windres .\winGUI.rc -o .\obj\winGUI.res

REM 编译C语言源文件
gcc -c .\winGUI.c -o .\obj\winGUI.o

REM 链接目标文件
gcc .\obj\winGUI.o .\obj\winGUI.res -o .\bin\winGUI.exe

如果是最后发布程序了,或者不再需要命令行的窗口调试了,可以再添加一个-mwindows 选项来编译出没有 命令行窗口的程序。

如果你启用了manifest相关代码,且了解你在做什么,使用下边的命令代替上边那条来进行链接

gcc .\obj\winGUI.o .\obj\winGUI.res -l ComCtl32 -o .\bin\winGUI.exe

最后双击,或者用命令行启动程序查看成果

.\bin\winGUI.exe


部分问题详解

菜单部分详解

如何自己写一个菜单
遵循下边的语法

${菜单的ID} MENU
BEGIN
	POPUP "${顶菜单项}"
	BEGIN
		MENUITEM "菜单子项", ${菜单项的ID}
	END
END

例如

#include "res.h"

MYCELIUM_MENU MENU
BEGIN
	POPUP "文件(&F)"
	BEGIN
		MENUITEM "打开(&O)", ID_OPEN
		MENUITEM "退出(&X)", ID_EXIT
	END
	POPUP "编辑(&E)",
	BEGIN
		MENUITEM "全选(&A)", ID_SELECT_ALL
	END
END

讲解一下这里&字母的作用,是按下alt+后边的字母可以快速定位/点击菜单项的快捷热键。应该挺常见的吧。
比如我想退出可以

alt+falt+x

另外还记得新建一个头文件
来用宏定义给这些 ID 一个整数的定义
比如 新建一个res.h

#define MYCELIUM_MENU 	1000
#define ID_OPEN			1010
#define ID_EXIT			1011
#define ID_SELECT_ALL	1012

代码层面怎么导入这个菜单的资源呢

在你注册窗口类的时候有个成员叫做 .lpszMenuName 给这个成员一个字符串的数字id就可以了

比如以下的两种方法都可以
但是可能就第一种比较正规。
但是本质上也是一种把数字的id转换成字符串的效果(MAKEINTRESOURCEA这个宏定义函数)(比如123 => “123”)

wc.lpszMenuName = MAKEINTRESOURCEA(MYCELIUM_MENU);
// 或者
wc.lpszMenuName = (char*)MYCELIUM_MENU;
// 当然微软的windows.h头文件里定义的也是可以的
wc.lpszMenuName = (LPSTR)MYCELIUM_MENU;
// 再或者 可以,但是不推荐,不然你新建头文件干啥
//不就是避免给程序员看这些数字id嘛
wc.lpszMenuName = (char*)1000;

当然方法不只在注册时候,挂载这个菜单资源,还有其他的方法和时候,都可以,但是这里只介绍这一种方法,再多的就是Win32课程需要学的了,不是本文章的重点了,所以略过。

还有就是怎么知道用户点击了我的自定义菜单项呢?这就是这个菜单项后边的ID的作用了。本文章通过 消息处理函数来解决的,过于简单直接放源码。

// 消息处理函数
LRESULT CALLBACK wndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)
{
	switch (msgID)
	{
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		
		case WM_COMMAND: // <= 重点*
			switch (LOWORD(wParam)) // <= 取wParam 的低2字节
			{
				case ID_EXIT: // <= 你设置的菜单项的ID
					PostQuitMessage(0);
					break;
				
				case ID_ABOUT:
					MessageBoxW(hWnd, L"作者: Mycelium", L"关于", MB_OK);
					break;
			}
	}
	return DefWindowProc(hWnd, msgID, wParam, lParam);
}

配合上边的 注释应该能理解,我就不过多解释了,也是win32 api相关课程能学到的知识。为什么取低2字节,也是因为windows的消息内容就是这样的。并不是用到了什么技巧。


应用程序的图标

遵循下边的语法

${图标ID} ICON "${图标路径.ico}"

例如:

ID_ICON ICON ".\\icon.ico"

表示 把当前目录下的 icon.ico文件当作是图标资源 赋予ID ID_ICON。

跟上边的菜单一样,新建一个头文件"res.h"

#define ID_ICON 1005

不修改代码应该就 可以在资源管理器里正常显示了,但是你打开应用,展示给你的窗口左上角的图标应该还没有修改。

同样也只是介绍在注册菜单类时,如何设置窗口的图标
找到.hIcon 给他设置成图标的句柄,就可以完成了,但是仍然推荐使用第一种。感觉比较正规。
例如以下方式:

wc.hIcon = LoadIconA(hIns, MAKEINTRESOURCEA(ID_ICON));
// 或者
wc.hIcon = LoadIconA(hIns, (char*)ID_ICON);
// 或者
wc.hIcon = LoadIconA(hIns, (LPSTR)ID_ICON);
// 使用数字ID的方式这里就不赘述了 可以,但不推荐。

注意LoadIconA的第一个参数为 当前程序实例句柄

有什么其他的问题可以在评论区留言,我也不一定会,但是没准可以思考讨论一下啥的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值