简介
Windows API
本教程是纯Windows API教程,不包含MFC,使用的是C99标准。
Windows API是创建Windows应用程序的接口,要使用Windows API就要先下载Windows SDk(Software Development Kit,软件开发环境),SDK包括头文件,函数库,样例,文档和工具等。
Windows API是由C和C++写的,被分为以下几个部分:
- 基础服务
- 安全
- 绘图
- 用户接口
- 多媒体
- Windows shell
- 网络
基础服务提供了文件系统,设备,进程,线程,注册表,以及错误处理等功能。
安全提供了函数,接口,对象和其他的认证,版权等安全相关的元素。
绘图提供了图形输出到屏幕,打印机及其他输出设备的功能。
用户接口提供了创建窗口和部件的功能。
多媒体提供了视频,音频等输出设备。
Windows Shell提供了由操作系统提供的shell功能。
网络提供了网络服务。
Windows API的官方实现是在动态库里(DLLS),例如kenel32.dll,user32.dll,gdi32.dll,shell32.dll是在Windows system目录下。
也有第三方的Windows API:最常见的就是Wine工程和ReactOS工程。
Windows API是一个动态实体,函数的数量每年都在增长。
Pelles C
教程里使用的是Palles C,我用的是VS2017.
MSDN
两个好的参考资料:Desktop App Development Documentation 和 Windows API list
主函数
main
主函数是C语言编程的一个进入点,然而他不是第一个运行的程序。
在C运行库中,程序开始的第一个函数叫做mainCRTStartup()
,他负责初始化内存管理,文件I/O,以及参数调用等,之后mainCRTStartuo()
将会调用main()
,以下是main函数的函数原型:
int main (void);
int main(int argc,char **argv);
int main(int argc,char *argv[]);
样例:
#include<stdio.h>
int main(void)
{
puts("This is a classic C Program.");
return 0;
}
wmain
前面的main函数原型只能接收 ASCII 字符,如果我们想让程序从命令行接收宽字符,就需要使用wmain:
int wmain(void);
int wmain(int argc,wchar_t **argv);
int wmain(int argc, wchar_t *argv[]);
样例;
这个程序在命令行使用宽字符,并输出命令行的第一个参数,众所周知,第0个参数是程序的运行目录。
#include <windows.h>
#include <wchar.h>
#include<clocale>
int wmain(int argc, wchar_t **argv) { //wchar_t类型即宽字符类型
setlocale(LC_ALL,"zh_CN.UTF-8"); //设置语言环境
PDWORD cChars = NULL;
HANDLE std = GetStdHandle(STD_OUTPUT_HANDLE); //获取标准输出的句柄
if (std == INVALID_HANDLE_VALUE) { //如果我们收到的返回代码是错误值INVALID_HANDLE_VALUE,
wprintf(L"Cannot retrieve standard output handle\n (%d)",
GetLastError()); //就输出一条错误信息,并输出错误代码
}
if (argv[1]) {
WriteConsoleW(std, argv[1], wcslen(argv[1]), cChars, NULL); //向控制台输出宽字符串
}
CloseHandle(std); //关闭标准输出的句柄
return 0;
}
如果想要输入命令行参数,可以在VS2017中右侧,解决方案资源量管理器->右键->属性-> 调试->命令参数,输入汉字即可。
_tmain 函数原型
_tmain()函数是微软的扩展函数,通过定义一个UNICODE宏来判断创建main()函数还是wmain()函数。
在过去,要创建这两个函数都需要创建,现在只需要一个宏就解决了,但是目前程序最通用的方式还是UNICODE程序。
以下是_tmain()的函数原型,其中,TCHAR宏用于转换char或者wchar_t,通过UNICODE宏控制:
int _tmain(void);
int _tmain(int argc,TCHAR **argv);
int _tmain(int argc,TCHAR *argv[]);
以下是一个程序样例:
#define _UNICODE //该宏用于C运行库转换
#define UNICODE ////该宏用于Windows头文件转换
#include <windows.h> //这个头文件定义了TCHAR宏,TCHAR宏受UNICODE宏影响
#include <tchar.h> //为_tmain和_tcslen宏引入头文件,他们的转换受_UNICODE影响
int _tmain(int argc, TCHAR *argv[]) {
PDWORD cChars = NULL;
HANDLE std = GetStdHandle(STD_OUTPUT_HANDLE);
if (std == INVALID_HANDLE_VALUE) {
_tprintf(L"Cannot retrieve standard output handle\n (%d)",
GetLastError());
}
if (argv[1]) {
WriteConsole(std, argv[1], _tcslen(argv[1]), cChars, NULL);
}
CloseHandle(std);
return 0;
}
WinMain
以上是控制台主函数,现在我们介绍图形用户界面的主函数:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow);
其中,pCmdLine是命令行参数。
如果进入点是WinMain()函数时,那么程序开始于WinMainCRTStartup();
如果进入点是wWinMain()函数,那么程序开始于wWinMainCRTStartup().
下面是一个样例,在屏幕上显示一个弹窗:
#include <windows.h>
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR szCmdLine, int CmdShow) {
MessageBoxW(NULL, szCmdLine, L"Title", MB_OK);
return 0;
}
系统函数
系统函数用于接收系统信息并和系统通信。
屏幕尺寸
GetSystemMetrics() 用于查看系统指标和系统设定。
下面是一个例子,用于输出屏幕尺寸:
#include <windows.h>
#include <wchar.h>
#pragma comment(lib, "user32.lib") //编译需要user32.lib库
int wmain(void) {
int x = GetSystemMetrics(SM_CXSCREEN);
int y = GetSystemMetrics(SM_CYSCREEN);
wprintf(L"The screen size is: %dx%d\n", x, y);
return 0;
}
锁屏
#include <windows.h>
#include <wchar.h>
#pragma comment(lib, "user32.lib")
int wmain(void) {
int r = LockWorkStation();
if( r == 0 ) {
wprintf(L"LockWorkStation() failed %d\n", GetLastError());
return 1;
}
return 0;
}
电脑名
GetComputerNameEx()用于检测NETBIOS和DNS名。
#include <windows.h>
#include <wchar.h>
int wmain(void) {
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1]; //MAX_COMPUTERNAME_LENGTH定义了电脑名的最大长度
DWORD size = sizeof(computerName) / sizeof(computerName[0]);
int r = GetComputerNameW(computerName, &size);
if (r == 0) {
wprintf(L"Failed to get computer name %ld", GetLastError());
return 1;
}
wprintf(L"Computer name: %ls\n", computerName);
return 0;
}
用户名
#include <windows.h>
#include <Lmcons.h> //ULEN定义
#include <wchar.h>
int wmain(void) {
wchar_t username[UNLEN+1];
DWORD len = sizeof(username) / sizeof(wchar_t);
int r = GetUserNameW(username, &len);
if (r == 0) {
wprintf(L"Failed to get username %ld", GetLastError());
return 1;
}
wprintf(L"User name: %ls\n", username);
return 0;
}
当前目录
SetCurrentDirectoryW() 用于设置当前目录;GetCurrentDirectoryW()用于获取当前目录。
在下面的样例中,我们输出了当前目录,并通过命令行参数改变了当前目录。
#include <windows.h>
#include <wchar.h>
#define BUFSIZE MAX_PATH
int wmain(int argc, wchar_t **argv) {
wchar_t buf[BUFSIZE];
if(argc != 2) {
wprintf(L"Usage: %ls <dir>\n", argv[0]);
return 1;
}
DWORD r = SetCurrentDirectoryW(argv[1]);
if (r == 0) {
wprintf(L"SetCurrentDirectoryW() failed (%ld)\n", GetLastError());
return 1;
}
r = GetCurrentDirectoryW(BUFSIZE, buf);
if (r == 0) {
wprintf(L"GetCurrentDirectoryW() failed (%ld)\n", GetLastError());
return 1;
}
if (r > BUFSIZE) {
wprintf(L"Buffer too small; needs %d characters\n", r);
return 1;
}
wprintf(L"Current directory is: %ls\n", buf);
return 0;
}
Windows版本
通过版本辅助函数可以检测当前的系统版本。
#include <windows.h>
#include <wchar.h>
#include <VersionHelpers.h>
int wmain(void) {
//if (IsWindows10OrGreater()) {
// wprintf(L"This is Windows 10+");
// }
if (IsWindows8Point1OrGreater()) {
wprintf(L"This is Windows 8.1+\n");
} else if (IsWindows8OrGreater()) {
wprintf(L"This is Windows 8\n");
} else if (IsWindows7OrGreater ()) {
wprintf(L"This is Windows 7\n");
} else if (IsWindowsVistaOrGreater ()) {
wprintf(L"This is Windows Vista\n");
} else if (IsWindowsXPOrGreater()) {
wprintf(L"This is Windows XP\n");
}
return 0;
}
内存
GlobalMemoryStatusEx() 函数用于检测当前系统的物理内存和虚拟内存。
#include <windows.h>
#include <wchar.h>
int wmain(void) {
MEMORYSTATUSEX mem = {0};
mem.dwLength = sizeof(mem);
int r = GlobalMemoryStatusEx(&mem);
if (r == 0) {
wprintf(L"Failed to memory status %ld", GetLastError());
return 1;
}
wprintf(L"Memory in use: %ld percent\n", mem.dwMemoryLoad);
wprintf(L"Total physical memory: %lld\n", mem.ullTotalPhys);
wprintf(L"Free physical memory: %lld\n", mem.ullAvailPhys);
wprintf(L"Total virtual memory: %lld\n", mem.ullTotalVirtual);
wprintf(L"Free virtual memory: %lld\n", mem.ullAvailVirtual);
return 0;
}
Known Folders
从Windows Vista开始,使用了新的系统来标识重要的目录,它叫做Known Folders。Known Folders使用一组GUID(Globally unique identifier) 来索引重要的文件夹。
SHGetKnownFolderPath()函数通过GUID返回全路径。
#include <windows.h>
#include <initguid.h> //由于一些内部API的问题,不包含这个编译时会报错
#include <KnownFolders.h>
#include <ShlObj.h>
#include <wchar.h>
int wmain(void) {
PWSTR path = NULL;
HRESULT hr = SHGetKnownFolderPath(&FOLDERID_Documents, 0, NULL, &path);
if (SUCCEEDED(hr)) {
wprintf(L"%ls\n", path);
}
CoTaskMemFree(path); //释放内存
return 0;
}
驱动名
使用GetLogicalDriveStringsW()函数来获取磁盘驱动名,他返回一个驱动路径的buffer:
#include <windows.h>
#include <wchar.h>
int wmain(void) {
wchar_t LogicalDrives[MAX_PATH] = {0};
DWORD r = GetLogicalDriveStringsW(MAX_PATH, LogicalDrives);
if (r == 0) {
wprintf(L"Failed to get drive names %ld", GetLastError());
return 1;
}
if (r > 0 && r <= MAX_PATH) {
wchar_t *SingleDrive = LogicalDrives;
while(*SingleDrive) {
wprintf(L"%ls\n", SingleDrive);
SingleDrive += wcslen(SingleDrive) + 1;
}
}
return 0;
}
剩余空间
函数GetDiskFreeSpaceExW() 用于检测磁盘的用量信息。他返回三条信息:空间总量,剩余空间总量,剩余可用空间总量,下面的样例检测c盘的空间情况:
#include <windows.h>
#include <wchar.h>
int wmain(void) {
unsigned __int64 freeCall,
total,
free;
int r = GetDiskFreeSpaceExW(L"C:\\", (PULARGE_INTEGER) &freeCall,
(PULARGE_INTEGER) &total, (PULARGE_INTEGER) &free);
if (r == 0) {
wprintf(L"Failed to get free disk space %ld", GetLastError());
return 1;
}
wprintf(L"Available space to caller: %I64u MB\n", freeCall / (1024*1024));
wprintf(L"Total space: %I64u MB\n", total / (1024*1024));
wprintf(L"Free space on drive: %I64u MB\n", free / (1024*1024));
return 0;
}
CPU速度
CPU速度通过检测注册表的值来检测,需要查询HARDWARE\DESCRIPTION\System\CentralProcessor\0 key
#include <windows.h>
#include <wchar.h>
int wmain(void) {
DWORD BufSize = MAX_PATH;
DWORD mhz = MAX_PATH;
HKEY key;
long r = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key);
if (r != ERROR_SUCCESS) {
wprintf(L"RegOpenKeyExW() failed %ld", GetLastError());
return 1;
}
r = RegQueryValueExW(key, L"~MHz", NULL, NULL, (LPBYTE) &mhz, &BufSize);
if (r != ERROR_SUCCESS) {
wprintf(L"RegQueryValueExW() failed %ld", GetLastError());
return 1;
}
wprintf(L"CPU speed: %lu MHz\n", mhz);
r = RegCloseKey(key);
if (r != ERROR_SUCCESS) {
wprintf(L"Failed to close registry handle %ld", GetLastError());
return 1;
}
return 0;
}
字符串
C语言中是没有字符串数据类型的。字符串本质是一个字符型数组,无论什么时候我们说到字符串,都是指字符串数组。
我们有五个处理字符串的API,包括CRT和Windows API的:
- ANSI C standard functions
- Security enhanced CRT functions
- Windows API kernel and user functions
- Windows API Shell Lightweight Utility functions
- Windows API StrSafe functions
ANSI C 字符串函数
CRT函数调用了Windwos API函数,因此会有一点小开销,这些函数虽然实现了字符串函数但是有一些限制,当不被正确使用时,会造成安全问题,当执行失败时不会返回错误值。
#include <windows.h>
#include <wchar.h>
#define STR_EQUAL 0
int wmain(void) {
wchar_t str1[] = L"There are 15 pines";
wprintf(L"The length of the string is %lld characters\n", wcslen(str1)); //输出宽字符串的数量
wchar_t buf[20];
wcscpy(buf, L"Wuthering"); //将字符串拷贝到buffer
wcscat(buf, L" heights\n"); //向buffer中添加字符串
wprintf(buf);
if (wcscmp(L"rain", L"rainy")== STR_EQUAL) { //比较字符串
wprintf(L"rain and rainy are equal strings\n");
} else {
wprintf(L"rain and rainy are not equal strings\n");
}
return 0;
}
安全提高的CRT函数
安全提高的CRT函数不是标准函数而是微软的扩展函数。
这些函数验证了参数,buffer大小,字符串空检测以及提供了强化的错误报告。
安全提高的CRT函数都有一个_s后缀:
#include <windows.h>
#include <wchar.h>
#define BUF_LEN 25
int wmain(void) {
wchar_t str1[] = L"There are 15 pines";
const int MAX_CHARS = 50;
size_t count = wcsnlen_s(str1, MAX_CHARS);
wprintf(L"The length of the string is %ld characters\n", count);
wchar_t buf[BUF_LEN] = {0};
int r = wcscpy_s(buf, BUF_LEN, L"Wuthering");
if (r != 0) {
wprintf(L"wcscpy_s() failed %ld", r);
}
r = wcscat_s(buf, BUF_LEN, L" heights\n");
if (r != 0) {
wcscat_s(L"wcscat_s() failed %ld", r);
}
wprintf_s(buf);
return 0;
}
内核和用户字符串函数
这些函数只用于Windows系统,他们比CRT更轻量。
内核级字符串函数根植于Windows内核开发,他们以字母l为前缀。
字符串长度
TODO.
日期和时间
TODO.
窗口
在Windows中,一切都是窗口,主窗口、按钮,静态文本甚至是图标,都是一个窗口,静态文本只不过是一个特殊窗口。
样例
simplewindow.c
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow) {
MSG msg;
HWND hwnd;
WNDCLASSW wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.lpszClassName = L"Window";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpszMenuName = NULL;
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 350, 250, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
参考链接:
https://zetcode.com/gui/winapi/