目录
1. 开发环境
2. SDK编程
1. 开发环境
1.1 VS和VScode的区别
VS是一款IDE,而VScode是一个轻量级的代码编辑器;如果你需要处理复杂的企业级应用程序或特定的平台开发,Visual Studio可能更适合你。而如果你更喜欢轻量级、可定制的编辑器,并且需要支持各种编程语言和框架,那么VSCode可能是更好的选择。
1.2 VS的操作
打开VS后是开启引导窗口,左侧红框是上一次打开的项目。对于一个新项目,需要在右侧新建项目。
点击“创建新项目”后,在右侧可选择项目的模板,对于windows程序而言,需要选择Windows桌面应用程序。对于普通的C代码脚本,可选择控制台应用。
选择后可以编辑项目名称和位置等信息。创建完成后可在左侧的“资源管理器”中找到源文件目录,其内部为项目具体代码,右击“源文件”,点击“添加”-“新建项” 后可以新建一个后缀为.c的C文件。新建后可打开,该文件即该程序的源代码文件。
当完成开发后,可以选择菜单栏的“调试”-“开始执行”来启动编写的windows程序。
1.2.1 断点和逐过程
右击代码,可在改行插入断点,程序运行到该行代码时会暂停;菜单栏有逐过程按钮,按下后程序执行下一行代码。
1.3 VSCODE的操作
由于VSCODE本身不具有编译功能,故需要配置添加编译功能:
vscode安装配置c/c++教程vscode安装使用教程vscode配置c/c++vscode配置Visual Studio Code使用方法Visual S_哔哩哔哩_bilibili
*注意:对于c++文件,其编译需要选择g++
配置完成后,每次运行编译和生成exe程序都直接按图下调用就行
运行后会生成一个exe文件
特别的:对于vscode场景下,调用messagebox时会出现中文乱码的情况(复现环境是按照前面配置编译环境的网址内的流程配置的环境),解决办法是换MessageboxW来调用,同时不使用TEXT,而是用L替代:用VS Code,运行MessageBoxA,弹窗乱码 - 吾爱破解 - 52pojie.cn
如果需要使用Messagebox,则需要对编译json文件进行修改:VSCODE下messagebox的中文乱码问题(minGW编译)-CSDN博客
*Message和MessageA以及MessageW之间的关系:MessageBox是在库里声明了一个宏 当你使用宽字符的时候,也就是unicode的时候,自动帮你转换使用 MessageBoxW 而当你使用窄字符的时候,会自动帮你转换到 MEssageBoxA
2. SDK编程
SDK编程是指使用软件开发工具包(Software Development Kit,简称SDK)来进行编程和开发应用程序的过程。SDK是一组开发工具、库、API(Application Programming Interface)和文档的集合,旨在帮助开发者更轻松地创建特定平台或技术的应用程序。
2.1 API
2.1.1 MessageBox
2.1.1.1 定义
用于显示一个模态对话框,其中包含一个系统图标、 一组按钮和一个简短的特定于应用程序消息,如状态或错误的信息。
API函数原型:(hWnd, lpText, lpCaption, uType),具有返回值,返回为用户按键。
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
TCHAR *szContent = TEXT("我的第一个程序!"); //字符串指针
static TCHAR szCaption[] = TEXT("你好"); //字符串数组
MessageBox(NULL, TEXT("我的第一个程序!"), TEXT("你好"), 0);
MessageBox(NULL, szContent, szCaption, 0);
return 0;
}
对于第二个参数,其实际等效于 L"我的第一个程序!" ,见2.2.1节描述。
当定义UNICODE时,TCHAR是等效于WCHAR等效于wchar_t。
其中对于uType,当选择两个及以上时,采用或操作符“|”来进行多个选择。
2.1.1.2 实例
点击“否”才关闭窗口:通过while实现窗口的反复出现,注意返回值是IDYES还是IDNO。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
BOOLEAN flag = 1;
while (flag)
{
if (MessageBox(NULL, TEXT("你真的忍心把我关闭吗?"), TEXT("程序1"), MB_YESNO) == IDYES)
{
flag = 0;
}
}
return 0;
}
2.1.2 WNDCLASS
Windows 的窗口总是基于窗口类来创建的,窗口类同时确定了处理窗口消息的窗口过程(回调函数)。在创建应用程序窗口之前,必须调用 RegisterClass 函数来注册窗口类。该函数只需要一个参数,即指向 WNDCLASS 窗口类的指针。因为 WNDCLASS 类包含了窗口所拥有的基本属性。
其结构原型如下:
typedef struct tagWNDCLASSW {
UINT style; //指定窗口类型,各种“类风格”,可以使用按位或操作符组合起来
WNDPROC lpfnWndProc; //指定窗口过程(必须是回调函数)
int cbClsExtra; //预留的额外空间,一般为 0
int cbWndExtra; //预留的额外空间,一般为 0
HINSTANCE hInstance; //应用程序的实例句柄
HICON hIcon; //为所有基于该窗口类的窗口设定一个图标
HCURSOR hCursor; //为所有基于该窗口类的窗口设定一个鼠标指针
HBRUSH hbrBackground; //指定窗口背景色
LPCWSTR lpszMenuName; //指定窗口菜单
LPCWSTR lpszClassName; //指定窗口类名
} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;
2.1.3 CreateWindow
CreateWindow 函数创建一个重叠式窗口、弹出式窗口或子窗口。它指定窗口类,窗口标题,窗口风格,以及窗口的初始位置及大小(可选的)。函数也指该窗口的父窗口或所属窗口(如果存在的话),及窗口的菜单。
函数原型:
HWND WINAPI CreateWindow(
_In_opt_ LPCTSTR lpClassName, // 窗口类名称
_In_opt_ LPCTSTR lpWindowName, // 窗口标题
_In_ DWORD dwStyle, // 窗口风格,或称窗口格式
_In_ int x, // 初始 x 坐标
_In_ int y, // 初始 y 坐标
_In_ int nWidth, // 初始 x 方向尺寸
_In_ int nHeight, // 初始 y 方向尺寸
_In_opt_ HWND hWndParent, // 父窗口句柄
_In_opt_ HMENU hMenu, // 窗口菜单句柄
_In_opt_ HINSTANCE hInstance, // 程序实例句柄
_In_opt_ LPVOID lpParam // 创建参数
);
2.1.4 MSG及相关函数
在 Windows 程序中,消息是由 MSG 结构体来表示的。
结构原型:
typedef struct tagMSG {
HWND hwnd; //指定接收消息的窗口句柄,如果是线程消息,该值是 NUL
UINT message; //消息的标识符,由于数值不便于记忆,所以 Windows 将消息对应的数值定义为 WM_XXX 宏的形式
WPARAM wParam; //指定消息的附加消息,确切的含义取决于消息成员的值
LPARAM lParam; //指定消息的附加消息,确切的含义取决于消息成员的值
DWORD time; //该消息被投放到消息队列的时间
POINT pt; //当消息被投放到消息队列的时,鼠标位于屏幕中的位置
} MSG, *PMSG, *LPMSG;
2.1.4.1 GetMessage
GetMessage 函数的作用是从当前线程的消息队列里获取一个消息并填入 MSG 结构 中。成功获取消息后,线程将从消息队列中删除该消息。
函数原型:
BOOL WINAPI GetMessage(
_Out_ LPMSG lpMsg, //指向 MSG 结构的指针,用于存放获取到的消息
_In_opt_ HWND hWnd, //1. 需要获取消息的窗口的句柄,该窗口必须属于当前线程
//2. 当其值是 NULL 时,将获取所有的当前线程的窗口消息和线程消息
//3. 当其值是 -1 时,只获取当前线程消息
_In_ UINT wMsgFilterMin, //指定被可以被获取的消息值的最小整数(消息其实就是一个被定义的整数)
_In_ UINT wMsgFilterMax ); //指定被可以被获取的消息值的最小整数
返回值:
1. 如果函数取得 WM_QUIT 之外的其他消息,返回值是非 0
2. 如果函数取得 WM_QUIT 消息,返回值是 0
3. 如果出现了错误,返回值是 -1
2.1.4.2 TranslateMessage
TranslateMessage 函数将虚拟键消息转换为字符消息,字符消息被寄送到当前线程的消息队列里。
函数原型:
BOOL WINAPI TranslateMessage(
_In_ const MSG *lpMsg //指向含有消息的 MSG 结构的指针
);
2.1.4.3 DispatchMessage
DispatchMessage 函数分派一个消息给窗口过程(回调函数),通常该消息从 GetMessage 函数获得。Windows 的控制权在该函数交给了应用程序。
函数原型:
LRESULT WINAPI DispatchMessage(
_In_ const MSG *lpmsg
);
2.1.5 获取设备环境句柄(GetDC, ReleaseDC)
GetDC获得指定窗口的客户区或整个屏幕的设备环境句柄,随后,你可以在 GDI 函数中使用该句柄在设备环境中绘图。ReleaseDC用于释放设备环境,以供其他应用程序使用.
注意使用完句柄后需要使用ReleaseDC来进行释放。
函数原型:
HDC GetDC(
_In_ HWND hWnd
);
int ReleaseDC(
_In_ HWND hWnd,
_In_ HDC hDC
);
2.1.6 文本输出(TextOut, GetTextAilgn, SetTextAlign)
TextOut使用当前选择的字体、背景颜色和文本颜色,将一个字符串绘制于窗口的指定位置。
函数原型:
BOOL TextOut(
_In_ HDC hdc, //设备环境句柄
_In_ int nXStart, //对其x坐标
_In_ int nYStart, //对其y坐标
_In_ LPCTSTR lpString, //指向被绘制字符串的指针。不需要结束符
_In_ int cchString //字符串长度
);
GetTextAlign用于获取指定的设备环境下的文本对齐方式的设置;SetTextAlign为指定设备环境设置文本的对齐标志。
函数原型:
UINT GetTextAlign(
_In_ HDC hdc //指定设备环境句柄
);
UINT SetTextAlign(
_In_ HDC hdc,
_In_ UINT fMode //设定的文本对齐方式
);
2.1.7 字符串处理函数(wsprintf, lstrlen, lstrcpy, lstrcat)
wsprintf类似printf的功能,只是不是直接打印出来,而是将内容赋值给对应的变量中。
lstrlen用于获取字符串的位数。
函数原型:
int __cdecl wsprintf(
_Out_ LPTSTR lpOut,
_In_ LPCTSTR lpFmt,
_In_ ...
);
wsprintf(string, TEXT("%0d + %0d"),a,b);
TextOut(hdc, 50, 30, szBuffer, lstrlen(szBuffer));
2.1.8 安全的字符串处理(StringCchPrintf, StringCchLength, StringCchCopy, StringCchCat)
包含在strsafe.h,相比于普通字符串处理,需要额外提供缓存区大小以防止溢出
函数原型:
#include <strsafe.h>
size_t iTarget1;
size_t iTarget2;
static TCHAR szBuffer1[];
static TCHAR szBuffer2[] = TEXT("HELLO");
StringCchPrintf(szBuffer1, 128, TEXT("行号为%0d"),i);
StringCchLength(szBuffer1, 128, &iTarget1);
TextOut(hdc, 50, 30+30*i, szBuffer1, iTarget1);
StringCchLength(szBuffer2, 128, &iTarget2);
StringCchCat(szBuffer1, iTarget1 + iTarget2 + 1, szBuffer2);
2.1.9 字符尺寸获取函数 GetTextMetrics
该函数用于获取字符默认设置的大小参数,用于确定行距和列宽。
函数原型:
BOOL GetTextMetrics(
_In_ HDC hdc,
_Out_ LPTEXTMETRIC lptm
);
TEXTMETRIC tm;
static int cxChar, cyChar;
...
case WM_CREATE:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd);
case WM_PAINT:
hdc = GetDC(hwnd);
...
TextOut(hdc, cxChar, i*cyChar, szbuffer, iTarget);
ReleaseDC(hwnd);
...
2.2 通用
2.2.1 Unicode
(1)统一所有的字符使用两个字节来存放。
(2)将所有国家的语言都放到一个字符集里。
#include<stdio.h>
#include<wchar.h>
#include<locale.h>
int main()
{
wchar_t c = L'人'; //注意此处是字符,需要用单引号;wchar为宽字符
setlocate(LC_ALL, "Chs"); //设定环境为简体中文
wprintf(L"%lc\n", c); //字符前都要加L
wchar_t str[] = L"人们"; //字符串用双引号
wprintf(L"%ls %lc\n",str,str[1]); //可以打印某一个字
return 0;
}
2.2.2 窗口
Windows 的应用程序是由各种窗口组成,因此 Windows 编程,说到底就是窗口编程。
窗口的产生过程:定义窗口类结构(WNDCLASS) -> 注册窗口类(RegisterClass) -> 创建窗口(CreateWindow) -> 显示窗口(ShowWindow) -> 更新窗口(UpdateWindow) -> 消息循环(GetMessage -> TranslateMessage -> DispatchMessage)
2.2.2.1 定义窗口类结构
static TCHAR szAppName[] = TEXT("MyWindows"); //定义窗口类名字
WNDCLASS wndclass; //窗口类结构
wndclass.style = CS_HREDRAW | CS_HREDRAW; //指定窗口类型
wndclass.lpfnWndProc = WndProc; //指定窗口过程,必须是回调函数
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance; //应用程序的实例句柄,为WinMain函数的第一个传入参数
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //为基于该窗口的类设定一个图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //为基于该窗口的类设定一个指针
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //设定窗口背景颜色
wndclass.lpszMenuName = NULL; //菜单名
wndclass.lpszClassName = szAppName; //窗口类名字
2.2.2.2 注册窗口类
传入的参数为窗口类的句柄
RegisterClass(&wndclass)
2.2.2.3 创建窗口
在调用完 CreateWindow 函数之后,应用程序实例已经在内存中创建成功,并返回窗口句柄。句柄不是指针,指针会指向一个地址,但句柄事实上只是一个数,一个索引值,一个让操作系统可以找到这个实例的索引值。事实上系统内部有建立一个索引表,维护着所有实例的索引号对应的内存地址,但这个索引表操作系统是不可能告诉我们的。
static TCHAR szAppName[] = TEXT("MyWindows"); //定义窗口类名字
HWND hwnd; //H开头为句柄
hwnd = CreateWindow //在默认的基础上进行窗口定制,返回一个窗口句柄
(szAppName, //窗口类名称,基于哪个类就填哪个类名
TEXT("鱼C工作室"), //窗口标题
WS_POPUP | WS_SIZEBOX, //窗口风格,或称窗口格式,是基础窗口类型的扩展
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序示例句柄,来自WinMain第一个参数
NULL); //创建参数
注意:窗口风格由两部分决定:WNDCLASS的style和CreateWindow的第三个变量。前者作用于整个窗口类,后者作用于具体的窗口实例。具体选项查看API。
HWND hwnd; //H开头为句柄
WNDCLASS wndclass; //窗口类结构
wndclass.style = CS_HREDRAW | CS_HREDRAW; //指定窗口类型
hwnd = CreateWindow //在默认的基础上进行窗口定制,返回一个窗口句柄
(...
WS_CAPTION, //窗口风格,或称窗口格式,是基础窗口类型的扩展
...);
窗口类结构定义了基本的属性,然后再由我们这个 CreateWindow 来丰富更多个性化的特征,这种做法是比较灵活的。因为这样做的话,同样一个窗口类,就可以生成很多拥有不同特征的窗口实例,而不是一个实例需要一个窗口类定义。
2.2.2.4 显示窗口
窗口注册后相当于窗口程序已经在内存中了,但还没有显示出来,需要调用该函数进行显示。第一个参数是创建的窗口的句柄,第二个参数详细解释在API。
ShowWindow(hwnd, iCmdShow); //把创建好的窗口展示出来
2.2.2.5 更新窗口
类比于刷新
UpdateWindow(hwnd); //更新窗口
2.2.2.6 消息循环
Windows强调的是事件触发(比如按下键盘或鼠标),并且事件之间是无序的。
当消息产生后,会把消息传递给对应的程序的消息队列中,通过get语句得到消息队列中的一个消息并放置在一个MSG结构体中,具体见API。主程序这边则通过while语句持续反复的接收消息并进行处理。首先是对消息进行翻译,然后对消息进行分配。后者带着消息去找windows系统,然后系统再执行窗口过程。(消息实际上就是个整数,不同的数对应为不同的消息,类似为ID)
对于消息队列中的消息属于先进先出(FIFO)(除开WM_PAINT(绘制窗口),WM_TIMER ,WM_QUIT,这三个消息时刻放在消息的最后);消息会细分为队列化消息和非队列化消息,后者会被立即送往窗口过程WndProc:CreateWindow执行完后会直接跳到窗口过程,具体的当窗口过程没有对对应消息有特殊处理时(WndProc中的case语句),交给DefWindowProc进行默认处理。
2.2.2.7 窗口过程
在窗口类的创建过程中提供了窗口过程的名字(lpfnWndProc),注册完成后操作系统就知道窗口过程的函数的位置了。窗口过程的参数对应为消息结构的前四个参数。
常用的消息如下:
2.2.2.8 窗口的关闭
当用户点击右上角关闭按钮的时候,windows的响应顺序为WM_CLOSE -> WM_DESTROY -> WM_NCDESTROY -> WM_QUIT