0
点赞
收藏
分享

微信扫一扫

Win32

左小米z 2022-07-18 阅读 93

title: win32 toc: true categories: language tags: Learn date: 2022.06.21 description: keywords:

win32的一些东西

............................................................................................................................

#include "framework.h"
#include "Day1.h"
#include<stdio.h>
#define junk NULL

HANDLE g_handle = NULL;

void OnCreate(HWND hWnd, LPARAM lParam)
{
CREATESTRUCT* ptr1 = (CREATESTRUCT*)lParam;
char* ptr2 = (char*)(ptr1->lpCreateParams);
HWND father_window = hWnd;
MessageBox(hWnd, ptr2, "info", MB_OK);
CreateWindowEx(NULL, \
"Edit", \
"here", \
WS_CHILD | WS_VISIBLE |WS_BORDER, \
0, 0, \
200, 200, \
father_window, NULL, \
NULL, NULL);

}

void OnSize(HWND hWnd, LPARAM lParam)//如果你修改一些东西,就会触发这个消息
{
short nWidth = LOWORD(lParam);
short nHight = HIWORD(lParam);
char szText[256] = { 0 };
sprintf(szText, "宽:%d,高:%d\n", nWidth, nHight);
WriteConsole(g_handle, szText, strlen(szText), NULL, NULL);
}


//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_SYSCOMMAND:
if (wParam == SC_CLOSE)
{
int ret=MessageBox(hWnd, "close or not ?", "info", MB_YESNO);
if (ret == IDYES)
;//因为点击了关闭按钮,默认函数会销毁窗口,然后发出WM_DESTROY,然后PostQuitMessage(0)
else
return 0;
}
//点击其它的按钮由默认出函数处理,因为是其它的按钮,所以就不是关闭的按钮
break;
case WM_CREATE:
OnCreate(hWnd,lParam);
break;
case WM_SIZE:
OnSize(hWnd, lParam);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int APIENTRY WinMain
(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow
)
{
HWND hWnd_Father = NULL;
HWND hWnd_son1 = NULL;
HWND hWnd_son2 = NULL;
DWORD dwStyle = 0;
HWND father_window = NULL;
HMENU hMenu = NULL;
WNDCLASS wc = { 0 };
AllocConsole();//产生一个DOS的窗口
g_handle = GetStdHandle(STD_OUTPUT_HANDLE);//在DOS窗口可以输出

char str[] = "I am dqx_xpb";

//----------------------------------------------------------------窗口信息的注册
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3);
wc.hCursor = NULL;//默认光标
wc.hIcon = NULL;//默认图标
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc_deal_DIY;
wc.lpszClassName = "My_Father";
wc.lpszMenuName = NULL;//没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);



//--------------------------------------------------------------------------窗口的创建
dwStyle = WS_OVERLAPPEDWINDOW;
father_window = NULL;
hMenu = NULL;

hWnd_Father = CreateWindowEx(NULL, \
"My_Father", \
"dqx_xpb", \
dwStyle, \
100, 100, \
500, 500, \
father_window, hMenu, \
hInstance, str);

ShowWindow(hWnd_Father, SW_SHOW);
UpdateWindow(hWnd_Father);



MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;
}

编译链接

int WINAPI Wmain(...)

WIHNAPI是一个宏,代表了stdcall

动态库与静态库

动态库,我的理解可能是,他是已经写好的exe,可执行文件,而你是直接调用那个可执行的exe

静态库,是你写好的源码,然后如果你需要,那些源码就会出现在那里

linux下的编译程序是gcc

而visual stdio或者说是windwos用到是

cl.exe去编译

link.exe去链接

● 编译器用CL.EXE 将c/c+​​的源代码编译成.obj,类似于​​.o文件

● 链接器用LINK.EXE将.obj目标代码、库链接生成最终文件

● 资源编译器用RC.EXE (.rc)将资源编译 ,最终通过链接器存入最终文件

路径: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin

编译链接的过程

Win32_Win32

先准备一个​​.c​​文件

#include <windows.h>
int WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR IpCmdLine,int nCmdShow )
{
MessageBox( NULL, "D0g3", "Dqx-0x9d",MB_YESNOCANCEL|MB_ICONERROR);
return 0;
}

然后准备一个ICO的图标,名字叫​​q.ico​

写了ICO的脚本,文件后缀名是​​.rc​

100 ICON q.ico

请事先准备环境,用到了​​vcvars32.bat​​,可以用everything搜到这个文件,然后用命令行处理运行一下,环境就配置好了

D:\2-Reverse\C0de\Work\test>cl.exe 1.c -c

用于 80x86 Microsoft (R) 32 C/C++ 优化编译器 16.00.30319.01
版权所有(C) Microsoft Corporation。保留所有权利。

1.c
----------------------------------------------------------------------于是生成一个1.obj的文件
D:\2-Reverse\C0de\Work\test>rc.exe q.rc

Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385
Copyright (C) Microsoft Corporation. All rights reserved.
----------------------------------------------------------------------于是生成了一个res的文件

D:\2-Reverse\C0de\Work\test>link.exe 1.obj q.res user32.lib

Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation. All rights reserved.

------------------------------------------------------------------------于是生成了最后的exe

Win32_Win32_02

然后再点击那个exe

Win32_Win32_03

窗口死掉后,进程不会退出...好家伙,我以为他就真真的死掉了,其实是悄悄的藏了起来,这可能有点隐蔽

导入的代码

#include<windows.h>
#include<stdio.h>
#define junk NULL

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
)

{
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3);
wc.hCursor = NULL;//默认光标
wc.hIcon = NULL;//默认图标
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc_deal_DIY;
wc.lpszClassName = "Dqx-x9d";
wc.lpszMenuName = NULL;//没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);



DWORD dwStyle = WS_OVERLAPPEDWINDOW;
int x = 100, y = 100, nWidth = 500, nHeight = 500;
HWND father_window = NULL;
HMENU hMenu = NULL;

HWND hWnd =
CreateWindow( "Dqx-x9d", "name-of-title", dwStyle, x, y, nWidth, nHeight, father_window, hMenu, hInstance, junk);


ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);

MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;
}

一些专业的术语

Application type:
Windows application 窗口应用程序
Console application 控制台应用程序
DLL 动态链接库
Static ibrary 静态链接库

字符编码

中文编码用的GB2312或者GB2312-80是基于ASCII的,一个字符占用了2个ASCII

GB2312编码的每个字节的最高位都是1

Linux下的Unicode码一般是utf-8,也就是与我们创建的ASCII类似

Windows下的Unicode码一般是utf-16,每个符号都占用2个字节,其数据类型的本质时​​unsigned short​

没有2个字节就用0来补全高位[^所以用print输出时会提前的截断,遇到0就终止]

在使用utf-16的编码时,前提是你强制使用utf-16

你不能直接认为编译器会把你的字符串处理为utf-16,你应该告诉编译器你用的是utf-16,所以

wchat_t str[]=L"dqx"//那个L就是告诉编译器,需要把我的字符串按照utf-16的形式处理

在后面会遇到tchar,然后你就不用老去加上一些东西

正是因为每个字符占用2个字节,于是就产生了一些wchar_t的函数

wprintf: ​​wprintf(L"%s",my_str)​

wsclen: ​​用于计算双字节字符的长度​

_UNICODE宏,如果定义了该宏

如何定义?

在程序的开头​​#define UNICODE​

那么文本的字符串就都是utf-16,否者就可能是utf-8

#ifdef  UNICODE                     
#define __TEXT(x) L##x //意思是__TEXT("x")就是L"X"
#else
#define __TEXT(x) x //意思是__TEXT("x")就是"X"
#endif
#define TEXT(x) __TEXT(x) //意思是TEXT("x")就是__TEXT("x")

所以根据这个宏定义,把字符串定义为TEXT()最保险了

以下是相关代码的解释,还有一些问题,我还没有解决

#include <windows.h>
#include <stdio.h>
#define junk NULL
typedef TCHAR tchar;
typedef wchar_t wchar;
void A_char()
{
char str[] = "hello A_char";
int len = strlen(str);
printf("utf-08 %s %d\n", str,len );
}
void W_char()
{

wchar str[] = L"hello W_char";
int len = wcslen(str);
wprintf(L"utf-16 %s %d \n", str,len);
}
void T_char() {

tchar str[] = "hello char";
#ifdef UNICODE
wprintf(L"utf-16 %s %d\n", str);
#else
printf("utf-08 %s\n", str);
#endif
}
void Chi() {

//我无法用wchat_t成功地输出中文
HANDLE hStdout;
tchar buf[] ="我是Dqx_x9d\n";
int len = lstrlen(buf);
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(hStdout, buf, len, NULL, NULL);


}
int main()
{
A_char();
W_char();
T_char();
Chi();
system("pause nul");
return 0;
}

类型转化

char*->CString
char* p="dqx"
SCtring q=SCtring(p)

CString->char*
CStringA tmp;
tmp = str;
char * pp= tmp.GetBuffer() ;

注册窗口

一些简单的函数介绍,虽然是废话

int WINAPI WinMain
(
HINSTANCE hInstance,//当前程序的实例句柄
HINSTANCE hPrevInstance, //这个参数一般是没用的
LPSTR IpCmdLine,//命令行参数字符串
int nCmdShow //窗口的显示方式 有最大化显示,最小化显示,还有一种显示方式,我也不知道
)

窗口类就是一个结构体

//----------------------------------------------------------------窗口信息的初始化
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3);//背景色
wc.hCursor = NULL;//默认光标,
wc.hIcon = NULL;//默认图标
wc.hInstance = hInstance;//主线程句柄???
wc.lpfnWndProc = WndProc_DIY;//窗口处理函数,请引起注意,
wc.lpszClassName = "Dqx-x9d";//窗口类的名字,也就是模板窗口的名字
wc.lpszMenuName = NULL;//没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW;//窗口的属性风格

RegisterClass(&wc);//直接把窗口结构体的地址给他,这个函数就把窗口类的信息写入内核

每个窗口都具有窗口(类)结构体,窗口与窗口类是多对1的关系

基于窗口类创建的窗口,它的窗口处理函数是由窗口类决定的,而不是在Createwidnwos时候决定的

窗口(类)包含了窗口的各种参数信息,是但是Createwindow也有一些窗口的信息,

个人认为

窗口类包含的信息是是每个窗口的基本信息

创建窗口包含的信息是每个窗口DIY的信息

以下几个参数较为重要

wc.lpszClassName = "Dqx-x9d";

wc.hInstance = hInstance;

wc.lpfnWndProc = WndProc_DIY;

创建窗口

函数介绍

HWND CreateWindowEx
(
DWORD dwExStyle, //窗口的扩展风格
LPCTSTR lpClassName, //已经注册的窗口类名称
LPCTSTR lpWindowName, //窗口标题栏的名字
DWORD dwStyle, //窗口的基本风格
int x, //窗口左上角水平坐标位置
int y, //窗口左上角垂直坐标位置
int nWidth, //窗口的宽度
int nHeight, //窗口的高度
HWND hWndParent, //窗口的父窗口句柄,针对与子窗口,父窗口的话,这里就是NULL
HMENU hMenu, //窗口菜单句柄
HINSTANCE hInstance, //应用程序实例句柄
LPVOID lpParam //窗口创建时附加参数
); //创建成功返回窗口句柄

窗口创建的原理

Win32_Win32_04

之前注册一个窗口的时候,我们就放入了窗口结构体的name和实例句柄,当然也有消息处理函数的指针

在内核中,局部窗口有很多个,全局窗口也有很多个,系统窗口也有很多个,这些窗口类都有name成员和实例句柄成员

现在createWindw的参数成员也有窗口结构体的的name和实例句柄

该函数的原理就像是拿着令牌去比对信息一样

createWindw根据name参数,遍历内核的窗口类信息

感觉用语言无法描述,就用了伪代码

xxx CreateWindwos()
{
bool ok=0;
for(i=0;i<3;i++)//3是局部窗口,全局窗口,系统窗口,一共3个
{
for(j=0;j<局部窗口个数;j++)
{
ok=strcmp(name,局部窗口[j].name);
if(ok==0)
if(实例句柄==局部窗口[j].实例句柄)
{
申请一块内存,把之前register注册的一些信息和createwindow传入的信息写入内存
return 内存的句柄;//于是他就是窗口句柄
}
else
return continue;
else
continue;//继续遍历
}
for(j=0;j<全局窗口个数;j++)
{
ok=strcmp(name,局部窗口[j].name);
if(ok==0)
{
申请一块内存,把之前register注册的一些信息和Createwindow传入的信息写入内存
return 内存的句柄;
}
else
continue;//继续遍历
}
for(j=0;j<系统窗口个数;j++)
{
ok=strcmp(name,局部窗口[j].name);
if(ok==0)
{
申请一块内存,把之前register注册的一些信息和Createwindow传入的信息写入内存
return 内存的句柄;
}
else
continue;//继续遍历
}
}
}

参数DWORD dwStyle

包括:系统窗口

BUTTON(按钮),EDIT(文本框),LISTBOX(列表),MDICLIENT(子窗口),SCROLLBAR(滚动条),RICHEDIT(富文本),STATIC(静态控件);

dwStyle:指定窗口样式。其值为以下值中的一个或多个:

WS_BORDER

创建一个有边框的窗口。

WS_CAPTION

创建一个具有标题栏 (意味着WS_BORDER样式) 的窗口。不能用的WS_DLGFRAME样式。

★ WS_CHILD

创建一个子窗口。不能用的WS_POPUP样式。★

WS_CHILDWINDOW

与WS_CHILD样式相同。

WS_CLIPCHILDREN

不包括在父窗口内绘制时子窗口所占用的区域。当您创建父窗口时使用。

WS_CLIPSIBLINGS

剪辑子窗口彼此;也就是说,当一个特定的子窗口接收绘制消息, WS_CLIPSIBLINGS风格剪辑子窗口要更新的区域外的所有其他重叠的子窗口。 (如果没有给WS_CLIPSIBLINGS和子窗口重叠内子窗口的客户区, 绘制时,它是可能内相邻的子窗口的客户区的绘制。与WS_CHILD样式只一起使用。

WS_DISABLED

创建一个窗口是最初被禁用。

WS_DLGFRAME

创建一个窗口,但是没有头衔的双边框。

WS_GROUP

指定一组控件,用户可以从一个控件移向下的箭头键的第一个控件。用虚假的WS_GROUP样式后的第一个控件定义的所有控件都属于同一个组。 WS_GROUP样式的下一控件启动下一个组 (就是下一步的开始位置的一组结束)。

WS_HSCROLL

创建一个具有水平滚动条的窗口。

WS_ICONIC

创建一个最初最小化的窗口。WS_MINIMIZE样式相同。

WS_MAXIMIZE

创建一个窗口的最大大小。

WS_MAXIMIZEBOX

创建一个窗口有最大化按钮。

WS_MINIMIZE

创建一个最初最小化的窗口。与仅WS_OVERLAPPED样式一起使用。

WS_MINIMIZEBOX

创建一个具有最小化按钮的窗口。

WS_OVERLAPPED

创建重叠的窗口。重叠的窗口通常具有标题和边框。

WS_OVERLAPPEDWINDOW

与WS_OVERLAPPED、 WS_CAPTION、 WS_SYSMENU、 WS_THICKFRAME、 WS_MINIMIZEBOX和WS_MAXIMIZEBOX样式创建重叠的窗口。

WS_POPUP

创建一个弹出窗口。不能使用具有WS_CHILD样式。

WS_POPUPWINDOW

用WS_BORDER, WS_POPUP和WS_SYSMENU的样式创建一个弹出窗口。WS_CAPTION样式必须结合WS_POPUPWINDOW样式,以使控制菜单上可见。

WS_SIZEBOX

创建一个具有大小调整边框的窗口。WS_THICKFRAME样式相同。

WS_SYSMENU

创建一个窗口,都有一个控制菜单框在其标题栏中。仅用于带有标题栏的窗口。

WS_TABSTOP

指定任意数量的控件,通过它用户可以通过使用 TAB 键移动之一。TAB 键移动用户到指定WS_TABSTOP风格的下一个控件。

WS_THICKFRAME

创建一个窗口,用厚厚的框架,可以用来调整窗口的大小。

WS_TILED

创建重叠的窗口。重叠的窗口有标题栏和边框。WS_OVERLAPPED样式相同。

WS_TILEDWINDOW

用 WS_OVERLAPPED、 WS_CAPTION、 WS_SYSMENU、 WS_THICKFRAME、 WS_MINIMIZEBOX 和 WS_MAXIMIZEBOX 的样式创建重叠的窗口。 与WS_OVERLAPPEDWINDOW 样式相同。

WS_VISIBLE

创建的窗体可见

WS_VSCROLL

创建一个具有垂直滚动条的窗口.

窗口类之分

系统窗口类

系统已经定义好的窗口类,无需注册

所有应用程序都可以直接使用。

类似的系统窗口有很多

包括:BUTTON(按钮),EDIT(文本框),LISTBOX(列表),MDICLIENT(子窗口),SCROLLBAR(滚动条),RICHEDIT(富文本),STATIC(静态控件);

#include<windows.h>
#include<stdio.h>
#define junk NULL

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int CALLBACK WinMain
(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
)

{
//--------------------------------------------------------------------------窗口的创建的信息
DWORD dwStyle = WS_OVERLAPPEDWINDOW;
int x = 100, y = 100, nWidth = 500, nHeight = 500;
HWND father_window = NULL;
HMENU hMenu = NULL;


HWND hWnd = CreateWindow(\
"Edit", \
"Big_Button", \
dwStyle, \
x, y, \
nWidth, nHeight, \
father_window, hMenu, \
hInstance, junk);

ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);//--------------------------------------------------翻译消息
DispatchMessage(&msg);//---------------------------------------------------把消息给处理函数
}

return 0;
}

基于Botton

Win32_Win32_05

基于Edit

Win32_Win32_06

自定义的窗口

窗口信息的注册,因为是自定义的,所以需要注册

看一下窗口类的结构体是什么样子

typedef struct tagWNDCLASSW {
UINT style ; // 类风格
WNDPROC lpfnWndProc; // 窗口的处理过程
int cbClsExtra; // 指定紧随在 WNDCLASS 数据结构后分配的字节数
int cbWndExtra; // 指定紧随在窗口实例之后分配的字节数
HINSTANCE hInstance; // 窗口类所在模块的实例句柄
HICON hIcon; // 窗口类的图标
HCURSOR hCursor; // 窗口类的光标
HBRUSH hbrBackground; // 窗口类的背景画刷
LPCWSTR lpszMenuName; // 窗口类的菜单资源名
LPCWSTR lpszClassName; // 窗口类的名称
} WNDCLASSW

然后一个窗口的初始化,

WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3);
wc.hCursor = NULL;//默认光标
wc.hIcon = NULL;//默认图标
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc_deal_DIY;
wc.lpszClassName = "Dqx-x9d";//窗口类的名字
wc.lpszMenuName = NULL;//没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW;

RegisterClass(&wc);//把所有的信息写入操作系统

全局窗口类

由用户自己定义,当前应用程序所有模块都可以使用。类似于全局变量

如果要定义,就在​​wc.style = CS_GLOBALCLASS​

于是就注册了全局额窗口

局部窗口类

由用户自己定义 , 当前应用程序中本模块可以使用。类似于局部变量

他是比较常用的窗口类

子窗口的创建

1.创建时要设置父窗口句柄,因为是基于父窗口创建的

HWND hWnd_Father = NULL;
HWND hWnd_son1 = NULL;
HWND hWnd_son2 = NULL;

2.创建风格要增加为​​dwStyle = WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW;//这里发生了变化​

#include<windows.h>
#include<stdio.h>
#define junk NULL

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY://获取的消息,窗口即将死去
PostQuitMessage(0);//遗言是抛出一个WM_QUIT
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
)


{
HWND hWnd_Father = NULL;
HWND hWnd_son1 = NULL;
HWND hWnd_son2 = NULL;
DWORD dwStyle = 0;
HWND father_window = NULL;
HMENU hMenu = NULL;
WNDCLASS wc = { 0 };

//----------------------------------------------------------------窗口信息的注册
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3);
wc.hCursor = NULL;//默认光标
wc.hIcon = NULL;//默认图标
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc_DIY;
wc.lpszClassName = "My_Father";
wc.lpszMenuName = NULL;//没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);




dwStyle = WS_OVERLAPPEDWINDOW;
father_window = NULL;
hMenu = NULL;

hWnd_Father =
CreateWindowEx(NULL, "My_Father", "dqx_xpb", dwStyle, 100, 100, 500, 500, father_window, hMenu, hInstance, junk);


wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wc.hCursor = NULL;//默认光标
wc.hIcon = NULL;//默认图标
wc.hInstance = hInstance;//这里没有发生变化
wc.lpfnWndProc = DefWindowProc;
wc.lpszClassName = "my_kid";
wc.lpszMenuName = NULL;//没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);



dwStyle = WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW;//这里发生了变化
father_window = hWnd_Father;//这里发生了变化,参数来源于是一个createWindow的返回值
hMenu = NULL;

hWnd_son1 =
CreateWindowEx(NULL, "my_kid", "Child1", dwStyle, 0, 0, 200, 200, father_window, hMenu, hInstance, junk);

hWnd_son2 =
CreateWindowEx(NULL, "my_kid", "Child2", dwStyle, 200, 0, 200, 200, father_window, hMenu, hInstance, junk);

ShowWindow(hWnd_Father, SW_SHOW);
UpdateWindow(hWnd_Father);
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}

Win32_Win32_07

ShowWindow(hWnd_Father, SW_SHOW);

hWnd是创建窗口​​hWnd_son1 = CreateWindowEx...​​时开辟的一个内存区,是一个窗口句柄

这个内存区具有窗口的信息,之前也说过

ShowWindow根据窗口的信息去整理,然后根据SW_SHOW显示

出现的问题是,编辑窗口的无法点动

消息

MSG 结构体

消息组成( windows平台下)

typedef struct tagMSG 
{
HWND hwnd;//窗口的句柄,属于哪个窗口
UINT message;//消息的ID
WPARAM wParam; //附加参数x2
LPARAM lParam;
DWORD time;/消息产生的时间
POINT pt;//消息产生时,鼠标的位置
} MSG;

1.窗口句柄,也就是hWnd,这个参数由CreateWindow函数返回,说明了这个消息是属于哪个窗口的

2.消息ID,每个消息都有他的ID编号,这个编号决定了消息的一些属性

3/4.

消息的两个参数(两个附带信息),消息产生时,不仅仅是产生单独的消息,还会夹杂一些附带的信息,通过这个2个参数返回

最后2个消息成员.基本上用不到

5.消息产生的时间,这个时间是抽象的时间,不是几分几秒

6.消息产生时的鼠标位置,这个位置也是抽象的位置,比如详鼠标的位置是点击了close

PS: GetMessage(&msg, NULL, 0, 0)会初始化Msg

MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

消息队列

● 消息队列是用于存放消息的队列。

● 消息在队列中先入先出。坐车,先坐前排,然后做后排,前排先下车,后排最后离开

● 所有窗口exe程序都具有消息队列。

● GetMessage可以从程序的消息队列中获取消息。

系统消息队列- 由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等。​​会不定时的​​把一些程序的消息派发到程序的消息队列

程序消息队列- 属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护。程序消息队列不会直接的抓取消息,而是由系统消息队列转发到程序消息队列

所有的消息产生都是依靠

SendMessage()
PostMessage()

消息产生后首先会进入系统消息队列,而不是本进程的的队列

系统消息队列根据Msg变量,寻找Hwnd成员,每个窗口的Hwnd成员是独一无二

根据Hwnd,把产生的消息转发给该进程

SendMessage既不会把消息给系统队列也不会给本进程的消息队列

消息分为队列消息和非队列消息

但是队列消息和非队列消息不是消息的本身属性

你把消息放入队列,他就是队列消息,然后Gestmessage抓取,然后处理

你不把消息方放入队列,他就是非队列消息,然后就会直接调用对应的消息处理函数,而不会直接接触GetMeesage

大多数的消息都会被放入消息队列

● 队列消息-消息发送后,首先放入队列,然后通过消息循环的GetMessage,从队列当中获取。

GetMessage -从消息队列中获取消息一

PostMessage -将消息投递到消息队列

常见队列消息: WM_PAINT、 键盘、鼠标、定时器。

● 非队列消息-消息发送时,首先查找消息接收窗口的窗口处理函数, 直接调用处理函数,完成消息。

SendMessage -直接将消息发送给窗口的处理函数,并等候处理结果。

常见消息: WM_CREATE、WM_SIZE等。

WM_QUIT一定是队列消息,如果GetMessage抓不到它,消息循环就不会退出

WM_CREATE一定不是队列消息,因为WM_CREAT是在Register之后,Show_Windwo之前,消息循环之前产生的

所以GetMessage是抓不到他的,因为WM_CREATE产生时,GetMessage还没有出生

消息循环的函数

GetMessage 消息爬取

BOOL GetMessage(
//阻塞函数
LPMSG IpMsg, //存放获取到的消息的结构体
HWND hWnd, //窗口句柄 ,表示抓哪个窗口
UINT min_ID,//获取消息的最小ID编号
UINT Max_ID//获取消息的最大ID编号
);

他会去程序的消息队列,抓取本进程的消息,也不是不可以抓其他的进程(那是黑客编程的事情)

从程序的消息队列获取消息,将消息从消息队列中移除,

比如抓取鼠标的消息,抓取键盘的消息,成功地抓取消息后他就会返回,不再阻塞

当系统无消息时,会一直等候下一条消息,等待的过程中就是一个消息也没有抓取到,相当于一直在睡觉,一直阻塞

PeekMessage -非阻塞,以查看的方式从系统获取消息,可以不将消息从系统移除,

当系统有消息时,返回true,继续执行后续代码。

当系统无消息时,返回FALSE,不会阻塞继续执行后续代码。

IpMsg -当获取到消息后,将消息的参数存放到MSG结构中。

hWnd -获取到hWnd所指定窗口的消息。从消息句柄的角度去限制抓取消息的范围

最后2个参数,从消息ID的角度去限制抓取消息的范围, 抓取消息ID在[min_ID,Max_ID]之间的消息

如果都为0 ,表示没有范围,什么都抓取

所以

GetMessage(&msg, NULL, 0, 0)

像这样的消息抓取,表示没有限制

GerMessge原理

GerMessge首先去本进程的程序消息队列抓取消息

if(抓取到了)
{
就执行消息处理函数
return ;
}

else
{
GetMessage就循询问系统消息队列,那里有没有它的消息
if()
{
系统消息队列由之前的不定时派发消息到各个进程
变为了马上就派发各个消息到对应的程序消息队列
然后执行消息处理函数
return
}
else
{
那么getMessage就去查看有没有要重新绘制的窗口
if()
{
就发出WM_PAINT消息,然后处理
return;
}
else
{
自己去寻找有没有要炸的定时器
if()
{
就发出消息ID为WM_TIMER,然后处理消息
return;
}
else
{
GetMessag就去整理一下程序的资源和内存
}
}
}
}
开始阻塞,实在无事可做

TranslateMessage

它只会抓取键盘的可见字符消息

检查消息是否是按键的消息,如果不是按键消息,不做任何处理。

TranslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR的消息,对于不可见字符无此消息。

附带信息:

WPARAM -输入的字符的ASCII字符编码值

LPARAM -按键的相关参数

当你按下一个键帽,发出一个KEY_DOWN的消息

void TranslateMessage(msg)
{
if(msg.mseID!=KEYDOWN)
return;
else if(msg.wParam!=可见字符的虚拟键值)
reurn;
else if(大写锁打开了)
PostMessage(msg.hWnd,WM_CHAR,虚拟按键的可见大写ASCII大写,0)
else
PostMessage(msg.hWnd,WM_CHAR,虚拟按键的可见小写ASCII大写,0)
}

当TranslateMessage发送了WM_CAHR到系队消息队列,早晚会被getMessage抓取,然后TranslateMessage接收到消息不是

WM_KEYDOWN,而是WM_CHAR

于是就直接返回

DispatchMessage() 消息处理函数调用

根据消息,调用消息处理函数

DispatchMessage(&msg)
{
...
}

他的具体流程是

msg.Hwnd->WndProc_deal,根据Msg结构体遍历他的成员,最后寻找消息处理函数

拿到消息处理函数WidnProc,把Msg的前4个成员作为参数传递给消息处理函数,此刻的4个参数都已经被初始化了

调用WndProc_deal_DIY(hWnd,MsgID,wParam,lParam)

PeekMessag 侦察兵

这个函数相比较与GetMessage

它有眼睛去侦察消息,

有嘴巴去返回消息

可以用手去移除消息,但是一般没有手,于是主要的功能就是侦察

BOOL PeekMessage
(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin, //first message
UINT wMsgFilterMax, //last message
UINT wRemoveMsg //PM_REMOVE 加上手 PM_NOREMOVE 移除
);

主要是去侦察消息,然后返回

#include<windows.h>
#include<stdio.h>
#define junk NULL

HANDLE g_handle = NULL;
//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int CALLBACK WinMain
(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
)
{
HWND hWnd_Father = NULL;
HWND hWnd_son1 = NULL;
HWND hWnd_son2 = NULL;
DWORD dwStyle = 0;
HWND father_window = NULL;
HMENU hMenu = NULL;
WNDCLASS wc = { 0 };
AllocConsole();//产生一个DOS的窗口,用于显示我们的窗口变化的消息
g_handle = GetStdHandle(STD_OUTPUT_HANDLE);//在DOS窗口可以输出


wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3);
wc.hCursor = NULL;//默认光标
wc.hIcon = NULL;//默认图标
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc_DIY;
wc.lpszClassName = "My_Father";
wc.lpszMenuName = NULL;//没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);



dwStyle = WS_OVERLAPPEDWINDOW;
father_window = NULL;
hMenu = NULL;

hWnd_Father =
CreateWindowEx(NULL, "My_Father", "dqx_xpb", dwStyle, 100, 100, 500, 500, father_window, hMenu, hInstance, NULL);

ShowWindow(hWnd_Father, SW_SHOW);
UpdateWindow(hWnd_Father);

MSG msg = { 0 };
while (1)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))//有消息返回非0,无消息就不处理
{
if (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
break;
}
else
{
Sleep(100);
WriteConsole(g_handle, "...", strlen("..."), NULL, NULL);
}
}
return 0;
}

消息派发

无论什么消息的发送,都是直接或者间接的调用这2个函数

SendMessage -发送消息,会等候消息处理的结果。这是一个阻塞函数,发送的消息直接调消息处理函数,而不会进入消息循环

PostMessage -投递消息,消息发出后立刻返回,不等候消息执行结果。不是一个阻塞函数,发送消息到系统消息队列

说一下这2个函数的区别

SendMessage就像是打电话,你拨打一个电话,于是就相当于发出一个消息,然后你就会等待对方接通

接通完毕后,消息处理,你才会挂断电话,然后返回,不在阻塞

PostMessage.可以看到Post这个单词,也就是邮递的意思

这个函数就像是你去邮局寄一封信,然后你把信件扔进邮箱,于是你就马上离开了

你也不会说等到对方把信收到手了,然后才离开邮局

LRESULT SendMessage( HWND hWnd, //这个消息是哪个窗口的,我要送到那里去 UINT Msg, //这是一个什么样的消息 WPARAM wParam, //下面是附带的2个参数 LPARAM lParam ); BOOL PostMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );

话说消息有6个,这里发送消息只用打了4个消息参数,于是呢,剩余的2个参数就是系统给你补齐的

这句话,后面再理解

我们之前遇到的第一个消息产生的函数

PostQuitMessage(0),他的内部也是调用的SendMessage或者PostMessage

LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
//PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
//SendMessage(hWnd,WM_QUIT,0,0);关闭失败
PostMessage(hWnd,WM_QUIT,0,0);//可以成功地退出
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

消息ID

MsgID浏览

WM_CREATE 窗口创建

WM_DESTROY 窗口摧毁消息

WM_ QUIT 窗口进程结束消息

WM_SIZE / WM_PAINT 窗口绘制消息

WM_KEYDOWN -按键被按下时产生消息

WM_KEYUP -按键被放开时产生消息

WM_SYSKEYDOWN -系统键按下时产生的消息,比如ALT、F10,他们

WM_SYSKEYUP -系统键放开时产生的消息

●基本鼠标消息

WM_LBUTTONDOWN -鼠标左键按下

WM_LBUTTONUP -鼠标左键抬起

WM_RBUTTONDOWN -鼠标右键按下

WM_RBUTTONUP -鼠标右键抬起

WM_MOUSEMOVE -鼠标移动消息

●双击消息

WM_ LBUTTONDBLCLK -鼠标左键双击按下

WM_ RBUTTONDBLCLK -鼠标右键双击按下

●滚轮消息

WM_ MOUSEWHEEL - 鼠标滚轮消息

●鼠标点击菜单发生的消息

WM_SYSCOMMAND 鼠标点击系统菜单栏发生的消息,放大缩小,关闭

WM_COMMADN 鼠标点击菜单发生的消息

WM_TIMER 定时器消息

WM_INITDIALOG 对话框创建之后显示之前发出的消息,注意这里的特殊之处 ​​不是产生WM_CREATE​​,而是WM_INITDIALOG

系统消息ID / 自定义消息ID

系统消息- ID范围(0 - 0x03FF),一共有0x400个

由系统定义好的消息,可以在程序中直接使用。

系统消息只是系统或者人为的发送,消息的处理也是系统来做

用户自定义消息D范围0x0400 -0x7FFF ()

由用户自己定义,满足用户自己的需求。需要由用户自己发出消息,并处理消息

自定义消息宏: WM_USER+n

自定义一个消息​​#define dqx ( 0x400+10 )​

然后选择一个时间点,发送消息

LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_SIZE:
PostMessage(hWnd, dqx, 13, 14);//选择了一个消息发送的时间
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case dqx:
Ondqx(hWnd, wParam, lParam);//写了一个消息处理的函数
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

自己写的一个消息处理函数

void Ondqx(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
char str[256] = { 0 };
sprintf(str, "爱你%d%d", wParam, lParam);
MessageBox(hWnd, str, "info", MB_OK);
}

#include<windows.h>
#include<stdio.h>
#define junk NULL
#define dqx ( 0x400+10 )
HANDLE g_handle = NULL;


void Ondqx(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
char str[256] = { 0 };
sprintf(str, "爱你%d%d", wParam, lParam);
MessageBox(hWnd, str, "info", MB_OK);
}
//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_SIZE:
PostMessage(hWnd, dqx, 13, 14);//发送一个消息
break;
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case dqx:
Ondqx(hWnd, wParam, lParam);//消息的处理
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

●每个窗口都必须具有 窗口处理函数。

●当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。

在窗口处理函数中,不处理的消息,使用缺省窗口处理函数。例如:DefWindowProc.

窗口消息

窗口创建

WM_CREATE 窗口创建

● 产生时间: 在窗口创建成功但还未显示时。​​CreateWindow已经创建好了,但是还没有ShowWindwo​

● 附带信息: IParam : 为CREATESTRUCT类型的结构体指针。通过这个指针可以获取CreatWindowEx中的全部12个参数的信息。

● 般用法: 常用于初始化窗口的参数、资源等等,包括创建子窗口等。

#include<windows.h>
#include<stdio.h>
#define junk NULL
#define dqx ( 0x400+10 )
HANDLE g_handle = NULL;

void just_for_fun(HWND hWnd, LPARAM lParam)
{
CREATESTRUCT* ptr1 = (CREATESTRUCT*)lParam;
char* ptr2 = (char*)(ptr1->lpCreateParams);
HWND father_window = hWnd;
MessageBox(hWnd, ptr2, "info", MB_OK);//我们先弹窗,然后出现一个子窗口,编辑框
CreateWindowEx(NULL, \
"Edit", \
"here", \
WS_CHILD | WS_VISIBLE | WS_BORDER, \
0, 0, \
200, 200, \
father_window, NULL, \
NULL, NULL);
}

LRESULT CALLBACK WndProc__DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_CREATE:
just_for_fun(hWnd, lParam);
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
)
...
//此刻产生消息
ShowWindow(hWnd_Father, SW_SHOW);//
UpdateWindow(hWnd_Father);
...

窗口摧毁消息

WM_DESTROY 窗口摧毁消息

产生时间: 点击关闭按钮,窗口被销毁时

●附带信息: 不用它

●一般用法: 常用于在窗口被销毁之前,做相应的善后处理,例如资源、内存...的处理,也就是要死了,留下的遗言

LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY://这个case代表了消息要死了,而要执行的语句就是i留下的遗言
PostQuitMessage(0);//这里就是遗言的具体内容
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

窗口进程结束消息

WM_ QUIT 窗口进程结束消息

● 产生时间: 程序员有我们发送。也就会程序员自己调用PostQuitMessage()发出WM_QUIT的消息

● 附带信息: wParam: PostQuitMessage函数传递的参数。 IParam : 0。

●一般用法: 用于结束消息循环,

程序死亡前,发送WM_DESTORY

然后消息处理函数根据WM_DESTORY派遣PostMessage发送WM_QUIT

最后GetMessage抓取后,返回0,退出消息循环

键盘消息

WM_KEYDOWN -按键被按下时产生消息

WM_KEYUP -按键被放开时产生消息

WM_SYSKEYDOWN -系统键按下时产生的消息,比如ALT、F10,他们

WM_SYSKEYUP -系统键放开时产生的消息

其它键帽消息

WM_CHAR 键盘消息

TranslateMessage接受到可见字符按下时,派遣消息加工厂发送了WM_CAHR

附带信息: WPARAM -按键的Virtual Key键值

LPARAM -按键的参数,例如按下次数

可以同时按下ctrl+可见字符键,他们也有对应ID

void up(HWND hWnd, WPARAM wParam )
{
char str[256] = { 0 };
sprintf(str, "down:%#x\n", wParam);
WriteConsole(g_handle, str, strlen(str), NULL, NULL);
}
void down(HWND hWnd, WPARAM wParam)
{
char str[256] = { 0 };
sprintf(str, "up:%#x\n", wParam);
WriteConsole(g_handle, str, strlen(str), NULL, NULL);
}

LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_KEYDOWN:
down(hWnd, wParam);
break;
case WM_KEYUP:
up(hWnd, wParam);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

WM_CHAR

void OnChar(HWND hWnd, WPARAM wParam )
{
char str[256] = { 0 };
sprintf(str, "ASCII:%c\n", wParam);
WriteConsole(g_handle, str, strlen(str), NULL, NULL);
}
//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_CHAR:
OnChar(hWnd,wParam);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

鼠标消息

WM_SETCURSOR 这个消息的产生就是在你移动鼠标的时候

WM_SETCURSOR消息参数

wPARAM -当前使用的光标句柄

​ IPARAM -​​LOWORD​​当前区域的代码( Hit-Test code )

HTCLIENT / HTCAPTION...客户区域,标题栏区域

HIWORD -当前鼠标消息ID.比如是左键还是右键按下等等,基本上不用

WM_COMMAND,鼠标点击菜单发生的消息

●附带信息:

wPARAM :

HIWORD -对于菜单为0 //无用

LOWORD -菜单项的ID

IPARAM -对于菜单为0 //无用,鼠标点击菜单发生的消息

基本鼠标消息

●基本鼠标消息

WM_LBUTTONDOWN -鼠标左键按下

WM_LBUTTONUP -鼠标左键抬起

WM_RBUTTONDOWN -鼠标右键按下

WM_RBUTTONUP -鼠标右键抬起

WM_MOUSEMOVE -鼠标移动消息

●基本鼠标消息的附带信息: wPARAM :其他按键的状态,例如Ctrl/Shift, 好比你ctrl c的时候,鼠标在选中一些东西复制

IPARAM :鼠标的位置,窗口客户区坐标系。

LOWORD X坐标位置

HIWORD Y坐标位置

●一般情况鼠标按下/抬起成对出现。在鼠标移动过程中, 会根据移动速度产生一系列的WM_ MOUSEMOVE消息。

void down(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
char str[256] = { 0 };
sprintf(str, "down: %d other :[%03d,%03d]\n", wParam, LOWORD(lParam), HIWORD(lParam));
WriteConsole(g_handle, str, strlen(str), NULL, NULL);
}

void up(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
char str[256] = { 0 };
sprintf(str, "up : %d other :[%03d,%03d]\n", wParam,LOWORD(lParam),HIWORD(lParam));
WriteConsole(g_handle, str, strlen(str), NULL, NULL);
}
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_LBUTTONDOWN:
down(hWnd, wParam,lParam);
break;
case WM_LBUTTONUP:
up(hWnd, wParam, lParam);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

一般来说按下与按上,之间相差一个值

你可以在某个地方按下,在另外一个地方按上去,,哈哈,笑死

void func()
{
WriteConsole(g_handle, "hello world\n", strlen("hello\n"), NULL, NULL);
}

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_MOUSEMOVE:
func();
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

你的鼠标移动的越快,它发送消息的时间就越少,

你的鼠标移动的越慢,它发送消息的时间就越多,

双击消息

●双击消息 WM_ LBUTTONDBLCLK -鼠标左键双击按下 WM_ RBUTTONDBLCLK -鼠标右键双击按下

● 附带信息: wPARAM -其他按键的状态,例如Ctrl/Shift等 IPARAM -鼠标的位置,窗口客户区坐标系。 LOWORD(IParam) //X坐标位置 HIWORD(IParam) //Y坐标位置 ●消息产生顺序 以左键双击为例:

​使用时需要在register注册窗口类的时候添加CS_DBLCLKS 风格。而不是CreateWindow创建窗口的时候​

WM_ LBUTTONDOWN 左键按下 WM_ LBUTTONUP 左键弹起 WM_ LBUTTONDBLCLK 左键双击 WM_ LBUT TONUP 左键弹起

void Ldd()
{
WriteConsole(g_handle, "double clink\n", strlen("double clink\n"), NULL, NULL);
}
void Lup()
{
WriteConsole(g_handle, "up\n", strlen("up\n"), NULL, NULL);
}
void Ldown()
{
WriteConsole(g_handle, "down\n", strlen("down\n"), NULL, NULL);
}

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_LBUTTONDOWN:
Ldown();
break;
case WM_LBUTTONUP:
Lup();
break;
case WM_LBUTTONDBLCLK:
Ldd();
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}
//窗口注册
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
RegisterClass(&wc);

滚轮消息

●滚轮消息 WM_ MOUSEWHEEL - 鼠标滚轮消息

●附带信息: wPARAM : LOWORD -其他按键的状态 HIWORD -滚轮的偏移量,通过正负值表示表示滚动方向。 正:向前滚动 负:向后滚动 IPARAM :鼠标当前的位置,屏幕坐标系/ LOWORD- X坐标 HIWORD - Y坐标 ●使用: 通过偏移量,获取滚动的方向和距离。

void mouse_whell(WPARAM wParam)
{
char str[256] = { 0 };
sprintf(str, "%d\n", (signed short)HIWORD(wParam));
WriteConsole(g_handle, str, strlen(str), NULL, NULL);
}

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_MOUSEWHEEL:
mouse_whell(wParam);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

鼠标点击系统菜单栏发生的消息

WM_SYSCOMMAND 鼠标点击系统菜单栏发生的消息

●产生时间: 当点击窗口的最大化、最小化、关闭...

●附带信息:

wParam: 具体点击的位置,例如关闭SC_CLOSE等.

IParam : 鼠标光标的位置。是一个4字节数据 LOWORD(IParam) ; //水平位置 HIWORD(IParam) ; //垂直位置

●-般用法: 常用在窗口关闭时,提示用户处理。

LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_SYSCOMMAND:
if (wParam == SC_CLOSE)//如果点击的是关闭的按钮
{
int ret=MessageBox(hWnd, "Close it ?", "info", MB_YESNO);
if (ret == IDYES)
;//因为点击了关闭按钮,默认函数会销毁窗口,然后发出WM_DESTROY,然后PostQuitMessage(0)
else
return 0;//表示不关闭,直接返回
}
//点击其它的按钮由默认出函数处理,因为是其它的按钮,所以就不是关闭的按钮
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

当你点击关闭的按钮后

最先获取的消息是WM_SYSCOMMAND

然后跳出MessagebOx

在return的时候,DefWindowProc默认的给你销毁窗口,除非你先前就return了

否则不会有一个WM_DESTROY的消息,

Win32_Win32_08

定时器消息

●产生时间: 在程序中创建定时器,当到达时间间隔时,定时器会向程序发送一个WM_TIMER消息。定时器的精度是毫秒,但是准确度很低。

例如设置时间间隔为1000ms,但是会在非1000毫秒到达消息。

●附带信息: wPARAM :定时器ID IPARAM :定时器处理函数的指针

定时器消息主要是给GetMessage函数使用的

埋了很多的炸弹,如果其中一个炸弹炸了,我得根据不同的炸弹炸了,干不同的事情,于是就需要知道定时器ID

创建定时器

UINT_PTR SetTimer(          
HWND hWnd,//定时器的窗口句柄,也就是炸弹炸了,哪个窗口的处理函数来处理消息WM_TIME
UINT_PTR nIDEvent,//定时器的ID
UINT uElapse, //时间间隔,毫秒级别
TIMERPROC lpTimerFunc //定时器函数处理指针,一般为NULL,
);//创建成功返回非0

销毁定时器

BOOL KillTimer(          
HWND hWnd, //定时器窗口句柄
UINT_PTR uIDEvent //定时器ID
);

void func(HWND hWnd,WPARAM wParam)
{
char str[256] = { 0 };
sprintf(str, "Iam %d\n", wParam);
WriteConsole(g_handle, str, strlen(str), NULL, NULL);
//KillTimer(hWnd, 1);//如果要杀死其中一个炸弹
}

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);//埋下炸弹
SetTimer(hWnd, 2, 3000, NULL);//埋下炸弹
break;
case WM_TIMER:
func(hWnd,wParam);//炸弹爆炸时要做的事情
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

窗口绘制消息

WM_SIZE/WM_PAINT 窗口绘制消息

WM_SIZE

产生时间: 在窗口的大小发生变化需要重新绘制,

附带信息: wParam : 窗口大小变化的原因。

IParam : 窗口变化后的大小。

LOWORD(IParam) //变化后的宽度

HIWORD(IParam) //变化后的高度

一般用法: 常用于窗口大小变化后,调整窗口内各个部分的布局。

WM_PAINT

产生时间: GetMessage找不到事情干并且窗口需要重新绘制的时候,基本上都是GetMessage派遣SendMessage去MW_PAINT

Show_window的时候,只会发送一次

附带信息: 他的附带信息都没用

void Onpaint(HWND hWnd)//如果你修改一些东西,就会触发这个消息
{
WriteConsole(g_handle, "wind need paint\n", strlen("wind need paint\n"), NULL, NULL);
}

void OnSize(HWND hWnd, LPARAM lParam)//如果你修改一些东西,就会触发这个消息
{
short nWidth = LOWORD(lParam);
short nHight = HIWORD(lParam);
char szText[256] = { 0 };
sprintf(szText, "宽:%d,高:%d\n", nWidth, nHight);
WriteConsole(g_handle, szText, strlen(szText), NULL, NULL);
}


//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_SIZE:
OnSize(hWnd, lParam);
break;
case WM_PAINT:
Onpaint(hWnd);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

windwos资源

菜单

顶层菜单

文件 | 共享 | 查看 | 视频 | 工具

系统菜单

  还原(R)
移动(M)
大小S) A
- 最小化(N)
□ 最大化(X)
x 关闭(Q) Alt+F4

弹出式菜单

例子1,右键出现的菜单

例子2,点击一个按钮出现的菜单

专业术语

HMENU类型表示菜单的句柄, ID表示菜单项。

菜单资源的使用

●添加菜单资源

●加载菜单资源的3个方法

1> 注册窗口类时设置菜单

2> 创建窗口传参设置菜单

3> 在主窗口WM_CREATE消息中利用SetMenu函数设置菜单

//加载菜单资源.获取菜单的句柄,于是就可以用在CreateWindow的时候传递进去
HMENU LoadMenu(
HINSTANCE hInstance, // 去哪个进程寻找资源
LPCTSTR IpMenuName // 寻找哪个菜单
);
//用于方法2和3

图形化界面添菜单的样子

添加菜单资源到本进程

方法一,注册时添加

wc.lpszMenuName = (char*)IDC_WIN32;//注册一下 这里的数据是菜单资源的ID

方法二,createwindow时添加

hMenu = LoadMenu(hInstance, (char*)IDC_WIN32); //获取窗口句柄的时候添加
hWnd_Father = CreateWindowEx(NULL, \
"My_Father", \
"dqx_xpb", \
dwStyle, \
100, 100, \
500, 500, \
father_window, hMenu, \
hInstance, str);

方法三

HINSTANCE hIns = NULL;
void oncreate(HWND hWnd)
{
HMENU hMenu = LoadMenu(hIns, (char*)IDC_WIN32);//参数1是窗进程的句柄
SetMenu(hWnd, hMenu);
}

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_CREATE:
oncreate(hWnd);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

WM_COMMAND,鼠标点击菜单发生的消息 wPARAM :

HIWORD 为1表示加速键,为0表示菜单。

LOWORD(为命令ID。也就是,加速键ID或者菜单键ID就是命令ID)

void onCOMMAND(HWND hWnd, WPARAM wParam)
{
switch (LOWORD(wParam))
{
case IDM_new_file:
MessageBox(hWnd,"新建","info",MB_OK);
break;
case IDM_ABOUT:
MessageBox(hWnd, "ABOUT", "info", MB_OK);
break;
}
}

//-----------------------------------------------------------------------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_COMMAND:
onCOMMAND(hWnd, wParam);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

ICO资源

图像化添加图标

HICON LoadIcon(          
HINSTANCE hInstance, //哪个进程
LPCTSTR lpIconName //哪个ICO的ID
);

wc.hIcon = LoadIcon(hInstance, (char*)IDI_ICON1);//默认图标

鼠标资源

●添加光标的资源 光标的大小默认是32X32像素,每个光标有HotSpot ,也就是当前鼠标的有效点

●加载资源

HCURSOR LoadCursor(
HINSTANCE hInstance, // handle to application instance
LPCTSTR IpCursorName // name or resource identifier
);
//hInstance -可以为NULL ,获取系统默认的Cursor

●设置资源的2种方法

在注册窗口时,设置光标

wc.hIcon = LoadIcon(hInstance, (char*)IDI_ICON1);//默认图标

使用SetCursor,设置光标

HCURSOR SetCursor(
HCURSOR hCursor // handle to cursor,现在要修改的光标句柄
);//返回的是原来得分光标句柄

这个函数的调用只能是在WM_SETCURSOR消息产生的时候,由消息处理函数去解决

这个消息的产生就是在你移动鼠标的时候

WM_SETCURSOR消息参数 wPARAM -当前使用的光标句柄 IPARAM -​​LOWORD​​当前区域的代码( Hit-Test code ) HTCLIENT / HTCAPTION...客户区域,标题栏区域 HIWORD -当前鼠标消息ID.比如是左键还是右键按下等等,基本上不i用

LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_SETCURSOR:
{
if(LOWORD(lParam)== HTCLIENT)//客户区域
SetCursor(LoadCursor(hIns, (char*)IDC_CURSOR2));
else//标题栏区域
SetCursor(LoadCursor(hIns, (char*)IDC_CURSOR1));
return 0;;
}
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

字符串资源

●添加字符串资源 添加字符串表 ,在表中增加字符串,可视化添加

字符串资源的使用,在创建窗口的时候

int LoadString(
HINSTANCE hInstance, // handle to resource module
UINT uID, //字符串ID
LPTSTR IpBuffer, //存放字符串BUFF
int nBufferMax //字符串BUFF长度
);
//成功返回字符串长度,失败0

char title[256] = { 0 };//字符串资源缓冲区
LoadString(hInstance, IDS_Title, title, 256);//在这加载资源
hWnd_Father = CreateWindowEx(NULL, \
"My_Father", \
title, \
dwStyle, \
100, 100, \
500, 500, \
father_window, hMenu, \
hInstance, str);

加速键资源

●添加 资源添加​​加速键表​​,增加命令ID对应的加速键。 ● 使用

加载加速键表,可视化的方式添加

HACCEL LoadAccelerators(
HINSTANCE hInstance, // 那个进程去寻找加速建表资源
LPCTSTR IpTableName //加速键表的ID
);返回加速键表句柄

翻译加速键,根据它的原理实现可知,它主要用于翻译已经绑定的加速键??

int TranslateAccelerator(
HWND hWnd,//处理消息的窗口句柄
HACCEL hAccTable, //加速键句柄
LPMSG lpMsg //消息
);如果是加速键,返回非零。

一般加速键和菜单项是绑定在一起用的,就像你可以通过标题栏去保存文件或者用ctrl s来保存文件,

但是你可以加速键去实现一些菜单项没有的功能,也就是加速键可以不和菜单项绑定

所谓的绑定,就是使用同一个ID

TranslateAccelerator的原理实现伪代码

TranslateAccelerator(hWnd,hAccTable,lpMsg)
{
if(lpMesg.messageID!=WM_KEYDOWN)
return 0;
然后拿着lpMsg.wParam去获取键码值,知道你按下了什么键
然后拿着键码值去加速表hAccTable去对比中间2列的成员
if(没找到)
return 0;
else
SenfMessage(hWnd,WM_COMMADN),对应加速键的ID值 | 1,NULL);
}

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

HANDLE g_handle = NULL;
HINSTANCE hIns = NULL;


void func(HWND hWnd, WPARAM wParam)
{
switch (LOWORD(wParam))
{
case ID_NEW:
MessageBox(hWnd, "ID_NEW", "info", MB_OK);
break;
case ID_NULL:
MessageBox(hWnd, "ID_NULL", "info", MB_OK);
break;
}
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_COMMAND:
func(hWnd, wParam);
break;
}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
)
{

hIns = hInstance;
HWND hWnd = NULL;
HWND hWnd_son1 = NULL;
HWND hWnd_son2 = NULL;
DWORD dwStyle = 0;
HWND father_window = NULL;
HMENU hMenu = NULL;
WNDCLASS wc = { 0 };
g_handle = GetStdHandle(STD_OUTPUT_HANDLE);//在DOS窗口可以输出




wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc_deal_DIY;
wc.lpszClassName = "My_Father";
wc.lpszMenuName = (char*)IDR_MENU1;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
RegisterClass(&wc);




dwStyle = WS_OVERLAPPEDWINDOW;
father_window = NULL;
hMenu = NULL;

char title[256] = { 0 };
LoadString(hInstance, IDS_Title, title, 256);
HACCEL hAccel = LoadAccelerators(hInstance, (char*)IDR_ACCELERATOR1);

hWnd = CreateWindowEx(NULL, \
"My_Father", \
title, \
dwStyle, \
100, 100, \
500, 500, \
father_window, hMenu, \
hInstance, NULL);

ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);

MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(hWnd, hAccel, &msg))//没按下或者按下没找到,就执行原来的循环
{//如果找到了加速表,就不会进入消息循环,直接调用SendMessage消息,调用处理函数
TranslateMessage(&msg);
DispatchMessage(&msg);
}

}
return 0;
}

●在WM_COMMAND中相应消息,消息参数 wPARAM : HIWORD 为1表示加速键,为0表示菜单。 LOWORD(为命令ID。也就是,加速键ID或者菜单键ID就是命令ID) lParam :为0

如果要区分的话...如果不区分的话

switch(LOWORD(wParam)){
case ID_NEW:
if(HIWORD(wParam)==0)
MessageBox( hWnd, "新建菜单项被点击", "Infor", MB_OK );
else if(HIWORD(wParam)==1)
MessageBox( hWnd, "CTRL+ M被点击","Infor" , MB_OK );
break;
}

位图资源

基本绘图

● 绘图设备DC ( Device Context ),绘图上下文/绘图描述表 ● HDC 绘图设备句柄 ● GDI - Windows graphics device interface ( Win32提供的绘图API ) ●颜色 计算机使用红、绿、蓝, R- 0~255 G- 0~255 B- 0~255 每一个点颜色是3个字节24位保存0-2^24-1

颜色的使用 COLORREF -实际DWORD 例如: COLORREF nColor= 0; ●赋值使用RGB宏 例如: nColor= RGB( 0, 0, 255); ●获取RGB值, GetRValue/GetGValue/GetBValue 例如: BYTE nRed = GetRValue( nColor );

SetPixel设置指定点的颜色
COLORREF SetPixel(
HDC hdc,//DC句柄
int X,//X坐标
int Y,//Y坐标
COLORREF crColor //设置的颜色
);返回点原来的颜色

画渐变色

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

HANDLE g_handle = NULL;
HINSTANCE hIns = NULL;

void DrawPit(HDC hdc)
{
for (int i = 0; i < 256; i++)
for (int j = 0; j < 256; j++)
SetPixel(hdc, i, j, RGB(i, i, j));
}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hWnd, &ps);
DrawPit(hdc);//绘制点
EndPaint(hWnd, &ps);
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT :
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

画直线

●线的使用(直线、弧线)

MoveToEx -指名画画的起点

LineTo -从窗口起点到指定点绘制一条直线

void DrawPit(HDC hdc)
{
MoveToEx(hdc, 100, 100, NULL);//从(100,100)开始,用hdc画笔句柄画图,NULL=不接受原来的当前点
//上面这个函数只是确定画图的起点,不调用的话,就默认0,0的起点
LineTo(hdc, 300, 300);//从当前点,用hdc画图..画到300,300,
//然后画图的当前点就变为了最后的重点

}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hWnd, &ps);
DrawPit(hdc);//绘制点
EndPaint(hWnd, &ps);
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT :
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

矩形/内切圆绘制

void DrawPit(HDC hdc)
{
Rectangle(hdc, 100, 100, 300, 300); //矩形
Ellipse(hdc, 100, 100, 300, 300);//内切圆
}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hWnd, &ps);
DrawPit(hdc);//绘制点
EndPaint(hWnd, &ps);
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT :
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

GDI绘图

画笔

● 画笔的作用 线的颜色、(线型、线粗。

HPEN -画笔句柄

● 画笔的使用

创建一支新的画笔

HPEN CreatePen(
int fnPenStyle, //画笔的样式
int nWidth, //画笔的粗细
COLORREF crColor //画笔的颜色
);创建成功返回句柄

第一个参数

​PS_SOILD​​ -实心线,支持线条粗细的变化

​PS_DASH​​ -虚线画笔.画笔宽度只能是1,如果改为其他的宽度,那么的话第一个参数自动转为PS_SOILD,变为实心画笔

将画笔应用到DC中,交换画笔,把原来的画笔交换为我之前新创建的画笔

HGDIOBJ SelectObject(
HDC hdc,//绘图设备句柄
HGDIOBJ hgdiobj //GDI绘图对象句柄,画笔句柄,获取新的画笔
);返回原来的GDI绘图对象句柄.返回旧的画笔

保存原来DC当中画笔。等一下要把画笔给还回去

绘图

取出DC中的画笔,将原来的画笔,使用SelectObject函数,放入到设备DC中,等他返回我们之前旧的画笔

释放画笔,[^句柄指向来新开辟的内存,这里指的是新开辟的画笔,现在我们在上一步已经拿到了新画笔的句柄,现在要释放了]

BOOL DeleteObject(
HGDIOBJ hObject //GDI绘图对象句柄 ,画笔句柄
);

只能删除不被DC使用的画笔,所以在释放前,必须将画笔从DC中取出。取出再释放

void DrawPit(HDC hdc)
{
Rectangle(hdc, 100, 100, 300, 300); //矩形
Ellipse(hdc, 100, 100, 300, 300);//内切圆
}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC Boy = BeginPaint(hWnd, &ps);//创建画家
HPEN new_Pen = CreatePen(PS_SOLID, 5, RGB(255, 0, 0));//创建一支新的画笔
HGDIOBJ OldPen = SelectObject(Boy, new_Pen);//交换画笔,把画笔给画家,旧的画笔备份一下


DrawPit(Boy);//开始画画
SelectObject(Boy, OldPen);//交换画笔,然后归还画笔
DeleteObject(new_Pen);//释放新建画笔的内存
EndPaint(hWnd, &ps);//画家回家
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT:
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

画刷
DIY的画刷

与画笔的套路差不多一致

●画刷相关

画刷-封闭图形的填充的颜色、图案,即使是用直线围起来的图形也不是封闭图形

HBRUSH -画刷句柄

●画刷的使用

创建画刷

CreateSolidBrush -创建实心画刷,创建单一的颜色

CreateHatchBrush -创建纹理画刷

BOOL CreateSolidBrush(
COLORREF crColor
);

将画刷应用到DC中

SelectObject

绘图

将画刷从DC中取出

SelectObject

删除画刷

DeleteObject

void DrawPit(HDC hdc)
{
Ellipse(hdc, 100, 100, 300, 300);//内切圆
}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC Boy = BeginPaint(hWnd, &ps);//创建画家
HPEN new_Pen = CreatePen(PS_DASH, 1, RGB(255, 0, 0));//创建一支新的画笔
HGDIOBJ OldPen = SelectObject(Boy, new_Pen);//交换画笔,把画笔给画家,旧的画笔备份一下

//HBRUSH New_Brush = CreateSolidBrush(RGB(0, 255, 0));//创建一支新的画刷
HBRUSH New_Brush = CreateHatchBrush(HS_CROSS, RGB(0, 255, 0));//纹理线

HGDIOBJ OldBrush = SelectObject(Boy, New_Brush);//交换画刷,把画刷给画家,旧的画刷备份一下



DrawPit(Boy);//开始画画

SelectObject(Boy, OldPen);//交换画笔,然后归还画笔
DeleteObject(new_Pen);//释放新建画笔的内存

SelectObject(Boy, OldBrush);//交换画刷,然后归还画刷
DeleteObject(New_Brush);//释放新建画刷的内存

EndPaint(hWnd, &ps);
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT:
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

透明画刷

可以使用GetStockObject函数获取系统维护的画刷、画笔等。

如果不使用画刷填充,

NULL_BRUSH透明画刷

GetStockObject返回的画刷不需要

DeleteObject。因为该画刷不是你创建的,你干嘛要去销毁?

void DrawPit(HDC hdc)
{
Ellipse(hdc, 100, 100, 300, 300);//内切圆
}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC Boy = BeginPaint(hWnd, &ps);//创建画家
HPEN new_Pen = CreatePen(PS_DASH, 1, RGB(255, 0, 0));//创建一支新的画笔
HGDIOBJ OldPen = SelectObject(Boy, new_Pen);//交换画笔,把画笔给画家,旧的画笔备份一下


HGDIOBJ New_Brush = GetStockObject(NULL_BRUSH);

HGDIOBJ OldBrush = SelectObject(Boy, New_Brush);//交换画刷,把画刷给画家,旧的画刷备份一下



DrawPit(Boy);//开始画画

SelectObject(Boy, OldPen);//交换画笔,然后归还画笔
SelectObject(Boy, OldBrush);//拿回原来的画刷
DeleteObject(new_Pen);//释放新建画笔的内存


EndPaint(hWnd, &ps);
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT:
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

位图

●位图相关 光栅图形-记录图像中每一-点的颜色等信息。常见

矢量图形-记录图像算法、绘图指令等。

HBITMAP -位图句柄

●位图的使用

1在资源中添加位图资源

2从资源中加载位图LoadBitmap

3创建一个与当前DC相匹配的DC (内存DC )

HDC CreateCompatibleDC(
HDC hdc //当前DC句柄 ,可以为NULL (使用屏幕DC )
);返回创建好的DC句柄

将位图放入匹配的DC中SelectObject 成像(1:1),不可放大与缩小

BOOL BitBIt(
HDC hdcDest, //照片的句柄

int nXDest, //把照片洗到什么位置
int nYDest,

int nWidth, // 照片的长宽
int nHeight,

HDC hdcSrc, //底片的句柄

int nXSrc, //从底片的什么位置开始洗照片
int nYSrc,
//源左上Y坐标
DWORD dwRop //成像方法SRCCOPY,原样子抽象
);

取出位图 SelectObject

释放位图 ReleteQbject

释放匹配的DC DeleteDC

Win32_Win32_09

缩放成像

BOOL StretchBlt( HDC hdcDest, // handle to destination DC int nXOriginDest, // x-coord of destination upper-left corner int nYOriginDest, // y-coord of destination upper-left corner int nWidthDest, // width of destination rectangle int nHeightDest, // height of destination rectangle HDC hdcSrc, // handle to source DC int nXOriginSrc, // x-coord of source upper-left corner int nYOriginSrc, // y-coord of source upper-left corner int nWidthSrc,// 源DC宽 int nHeightSrc, // 源DC高. DWORD dwRop // raster operation code );

void DrawPit(HDC boy)
{
//添加bmp图像 无须代码

//加载bmp资源
HBITMAP hBmp = LoadBitmap(hIns, (CHAR*)IDB_BITMAP1);//从本进程hIns去寻找资源ID为IDB_BITMAP1的bmp

HDC DiPian = CreateCompatibleDC(boy);//创建一个内存底片

//把现有bmp的资源写入内存底片
HGDIOBJ nOldBmp = SelectObject(DiPian, hBmp);


//画家根据底片DiPian句柄,从第底片的(0,0)位置洗照片,
//照片的起点在窗口的(0,0)位置,照片的大小是50*50
BitBlt(boy, 0, 0, 50, 50, DiPian, 0, 0, SRCCOPY);


// 画家根据底片DiPian句柄, 从第底片的(0, 0)位置洗照片,底片选取的大小是50x50
//照片的起点在窗口的(100,100)位置,照片的大小是300*300
StretchBlt(boy, 100, 100, 300,300, DiPian, 0, 0, 50, 50, SRCCOPY);


//把内存底片给还回去
SelectObject(DiPian, nOldBmp);
//释放bmp资源
DeleteObject(hBmp);
//释放底片
DeleteDC(DiPian);

}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC Boy = BeginPaint(hWnd, &ps);//创建画家
DrawPit(Boy);//开始画画
EndPaint(hWnd, &ps);
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT:
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

字符串绘制

文字的绘制. TextOut -将文字绘制在指定坐标位置。

DrawText 文本绘画

int DrawText(
HDC hDC,
//DC句柄
LPCTSTR IpString, //字符串
int nCount,
//字符数量
LPRECT IpRect,//绘制文字的矩形框
UINT uFormat
}
//绘制的方式

字体 设置

Window常用的字体为TrueType格式的字体文件,在windwos/fonts下的目录.点击一个字体.他的名字就会出现,而不是看文件名字

HFONT CreateFont(
int nHeight, //字体高度
int nWidth, //字体宽度
int nEscapement, //字符串倾斜角度 字符串基线的相对水平的角度
int nOrientation,//字符旋转角度 无用
int fnWeight, //字体的粗细
DWORD fdwItalic, //斜体 0/1
DWORD fdwUnderline, //字符下划线 0/1
DWORD fdwStrikeOut, //删除线 0/1
DWORD fdwCharSet, //字符集 GB2312_CHARSET
DWORD fdwOutputPrecision,//输出精度 没用 0
DWORD fdwClipPrecision,//剪切精度 没用 0
DWORD fdwQuality,//输出质量 没用 0
DWORD fdwPitchAndFamily,//匹配字体 没用 0
LPCTSTR lpszFace //字体名称
);

字体名-标识字体类型

HFONT -字体句柄

应用字体到DC

SelectObject

绘制文字

DrawText/TextOut

取出字体

SelectObject

删除字体

DeleteObject

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

HANDLE g_handle = NULL;
HINSTANCE hIns = NULL;


void DrawPit(HDC boy)
{
char szText[] = "Hello world I am 0x9d";


/*
HFONT CreateFont(
int nHeight, //字体高度
int nWidth, //字体宽度
int nEscapement, //字符串倾斜角度 字符串基线的相对水平的角度
int nOrientation,//字符旋转角度 无用
int fnWeight, //字体的粗细
DWORD fdwItalic, //斜体 0/1
DWORD fdwUnderline, //字符下划线 0/1
DWORD fdwStrikeOut, //删除线 0/1
DWORD fdwCharSet, //字符集 GB2312_CHARSET
DWORD fdwOutputPrecision,//输出精度 没用 0
DWORD fdwClipPrecision,//剪切精度 没用 0
DWORD fdwQuality,//输出质量 没用 0
DWORD fdwPitchAndFamily,//匹配字体 没用 0
LPCTSTR lpszFace //字体名称
);

*/

//字体创建和属性配置
HFONT hFont = CreateFontA(10, 10, \
0, 0, \
900, 0, 0, 0, \
GB2312_CHARSET, \
0, 0, 0, 0, \
"Cascadia Code ExtraLight");

//把字体字体内存句柄给画家
HGDIOBJ nOldFont = SelectObject(boy, hFont);

//字体颜色
SetTextColor(boy, RGB(0, 0, 0));

//SetBkColor(boy, RGB(0, 255, 0));//在不透明的情形下使用,否则失效

//字体透明显示
SetBkMode(boy, TRANSPARENT);

RECT rc;
rc.left=150;
rc.top =150;
rc.right = 500;
rc.bottom = 500;
DrawText(boy, szText, strlen(szText), &rc, DT_LEFT | DT_TOP | DT_WORDBREAK|DT_NOCLIP);
//DT_WORDBREAK多行画,一行显示不玩换另外一行
//DT_NOCLIP多行装不下,那就打破限制,不剪切

//归还自己句柄
SelectObject(boy, nOldFont);
//释放字体句柄
DeleteObject(hFont);


}
void OnPaint(HWND hWnd)
{
PAINTSTRUCT ps = { 0 };
HDC Boy = BeginPaint(hWnd, &ps);//创建画家
DrawPit(Boy);//开始画画
EndPaint(hWnd, &ps);
}
//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_PAINT:
OnPaint(hWnd);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
)
{

hIns = hInstance;
HWND hWnd = NULL;
HWND hWnd_son1 = NULL;
HWND hWnd_son2 = NULL;
DWORD dwStyle = 0;
HWND father_window = NULL;
HMENU hMenu = NULL;
WNDCLASS wc = { 0 };
g_handle = GetStdHandle(STD_OUTPUT_HANDLE);//在DOS窗口可以输出




wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc_deal_DIY;
wc.lpszClassName = "My_Father";
wc.lpszMenuName = (char*)IDR_MENU1;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
RegisterClass(&wc);




dwStyle = WS_OVERLAPPEDWINDOW;
father_window = NULL;
hMenu = NULL;

char title[256] = { 0 };
LoadString(hInstance, IDS_Title, title, 256);

hWnd = CreateWindowEx(NULL, \
"My_Father", \
title, \
dwStyle, \
100, 100, \
500, 500, \
father_window, hMenu, \
hInstance, NULL);

ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);

MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}

内核编程 初入

对话框

普通窗口:自定义函数调用缺省函数

对话框窗口:缺省函数调用自定义函数

个人认为,无论最先是什么函数,都只是一个形式罢了,反正都要先询问我们,是不是要调用我们的函数

●对话框的分类

模式对话框- 当子对话框显示时,会禁止其他窗口和用户交互操作,除了子窗口以外的其它窗口都会像死去了一样[^其原因是有其中有阻塞函数]

无模式对话框-在对话框显示后,其他窗口仍然可以和用户交互操作,就像是多个窗口同时进行一样

对话框窗口处理函数(并非真正的对话框窗口处理函数)

INT CALLBACK DialogProc(
HWND hwndDlg, //窗口句柄
UINT uMsg,//消息ID
WPARAM wParam, //消息参数
LPARAM IParam //消息参数
);

返回TRUE -缺省处理函数不需要处理。

返回FALSE-交给缺省处理函数处理。

不需要调用缺省对话框窗口处理函数。

Win32_Win32_10

模式对话框

INT DialogBox(
HINSTANCE hInstance, //应用程序实例句柄
LPCTSTR IpTemplate, //对话框资源ID
HWND hWndParent, //对话框父窗口
DLGPROC IpDialogFunc //自定义函数
);

DialogBox是一个阻塞函数,只有当对话框关闭后,才会返回,继续执行后续代码。返回值是通过EndDialog设置。

EndDialog可以结束模式对话框,然后消除阻塞,消除阻塞这一步非常的重要

对话框的关闭

BOOL EndDialog(
HWND hDIg,//关闭的对话框窗口句柄,你要关闭哪个对话框?
INT_ PTR nResult //关闭的返回值 ,关闭后,你要返回什么值
);

关闭模式对话框,只能使用EndDialog ,不能使用DestroyWindow等函数。

nResult是DialogBox函数退出时的返回值。

●对话框的消息

WM_INITDIALOG 对话框创建之后显示之前发出的消息

注意这里的特殊之处 ​​不是产生WM_CREAT​​E,而是WM_INITDIALOG

可以完成自己的初始化相关的操作。

对话框也是一种资源,你也要开辟和释放

int CALLBACK DlgProc_DIY(HWND hWnd_Dlg, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_SYSCOMMAND://我发现对话窗口的系统菜单只有x,难以区分是谁点的x
{
if(wParam==SC_CLOSE)
EndDialog(hWnd_Dlg, 12);
//DestroyWindow( hwndlg );//它只是摧毁窗口,但是窗口的其它东西,不能干净的clan,比如不能清除DialogBox的阻塞
}
break;
case WM_INITDIALOG :
MessageBox(hWnd_Dlg, "WM_ NITDIALOG", "Infor", MB_OK);
break;
case WM_CREATE :
MessageBox(hWnd_Dlg, "WM_CREATE", "Infor", MB_OK);
break;
}
return 0;
}

void OnMad(HWND hWnd, WPARAM wParam)
{
switch (LOWORD(wParam))
{
case ID_MOD1:
{
int ret=DialogBox(hIns, (char*)IDD_DIALOG1, hWnd, DlgProc_DIY);
if (ret == 12)
MessageBox(hWnd, "模式化 消除", "Success", MB_OK);
break;
}

case ID_MOD2:
MessageBox(hWnd, "非模式 开始", "info", MB_OK);
break;
}
}

//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_COMMAND:
OnMad(hWnd,wParam);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

无模式对话框

●创建对话框

HWND CreateDialog(
HINSTANCE hInstance, //应用程序实例句柄
LPCTSTR IpTemplate,//模板资源ID
HWND hWndParent,//父窗口
DLGPROC IpDialogFunc //自定义函数
);

非阻塞函数,创建成功返回窗口句柄, ​​需要使用ShowWindow函数显示对话框。​

对话框的关闭

关闭时使用DestroyWindow销毁窗口, 不能使用EndDialog关闭对话框。

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

HANDLE g_handle = NULL;
HINSTANCE hIns = NULL;
HWND no_mod = NULL;
int CALLBACK DlgProc_DIY(HWND hWnd_Dlg, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_SYSCOMMAND:
{
if (wParam == SC_CLOSE)//人为的用模式对话框去点击
{
MessageBox(hWnd_Dlg, "模式化Dlg 消除", "Success", MB_OK);
EndDialog(hWnd_Dlg, 12);
//DestroyWindow( hwndlg );//它只是摧毁窗口,但是窗口的其它东西,不能干净的clan,比如不能清除DialogBox的阻塞
}
if (hWnd_Dlg == no_mod)//用无模式对话框点击,并且句柄指针还符合情况
{
MessageBox(hWnd_Dlg, "非模式化Dlg 消除", "Success", MB_OK);
DestroyWindow(hWnd_Dlg);
//EndDialog(hWnd_Dlg, 12);不能用他,因为无模式对话框根本不需要消除阻塞
}

}
break;
case WM_INITDIALOG :
MessageBox(hWnd_Dlg, "WM_ NITDIALOG", "Infor", MB_OK);
break;
case WM_CREATE ://不会触发
MessageBox(hWnd_Dlg, "WM_CREATE", "Infor", MB_OK);
break;
}
return 0;
}

void OnMad(HWND hWnd, WPARAM wParam)
{
switch (LOWORD(wParam))
{
case ID_MOD1://模式化窗口
{
MessageBox(hWnd, "模式 开始", "info", MB_OK);
int ret=DialogBox(hIns, (char*)IDD_DIALOG1, hWnd, DlgProc_DIY);
//if (ret == 12)
//MessageBox(hWnd, "模式化 消除", "Success", MB_OK);
break;
}

case ID_MOD2://非模式窗口
MessageBox(hWnd, "非模式 开始", "info", MB_OK);
no_mod=CreateDialog(hIns, (char*)IDD_DIALOG1, hWnd, DlgProc_DIY);
ShowWindow(no_mod, SW_SHOW);//这里很容易忘记
break;
}
}

//----------------------消息处理函数
LRESULT CALLBACK WndProc_deal_DIY(HWND hWnd, UINT MsgID, WPARAM wParam, LPARAM lParam)
{
switch (MsgID)
{
case WM_DESTROY:
PostQuitMessage(0);//它可以让你的窗口退出后,进程也退出
break;
case WM_COMMAND:
OnMad(hWnd,wParam);
break;

}
return DefWindowProc(hWnd, MsgID, wParam, lParam);//默认处理方式
}

静态库

●运行不存在。

●静态库源码被链接到调用程序中。

里面的地址一般写相对路径

c项目调用c静态库

涉及的文件都是c文件

项目1的c文件,

../是退出当前项目

one/Debug/one.lib是找到lib文件

#include<stdio.h>
#pragma comment( lib, "../one/Debug/clib.lib" ) //退出本项目地址,然后进入那个项目,然后找到lib
int cadd(int, int);
int csub(int, int);
int main()
{
int sum, sub;
sum = cadd(5,3);
sub = csub(5,3);
printf("sum=%d,sub=%d\n", sum, sub);
return 0;
}

lib项目

int cadd(int x, int y)
{
return (x + y);
}
int csub(int x, int y)
{
return (x - y);
}

当然lib项目也有很多的其它自动生成的文件

cpp项目调用cpp静态库

C + +静态库的创建

一样的操作,文件全是cpp

首先是启动项目的文件

#include<iostream>
using namespace std;
#pragma comment( lib, "../one/Debug/cpplib.lib")

int cppsub(int, int );
int cppadd(int, int);


int main()
{
int sum = cppadd(5, 4);//?CPPlib_ add@ @YAHHH@Z
int sub = cppsub(5, 4);//?CPPlib_ _sub@ @YAHHH@Z
cout << "add=" << sum << ",sub=" << sub << endl;
return 0;
}

然后lib项目的文件

int cppadd(int x, int y)
{
return (x + y);
}
int cppsub(int x, int y)
{
return (x - y);
}

cpp项目调用c静态库

启动项项目

#include<iostream>
using namespace std;
#pragma comment( lib, "../one/Debug/cpplib.lib")
#pragma comment( lib, "../one/Debug/clib.lib")
int cppsub(int, int );
int cppadd(int, int);
extern "C" int cadd(int, int);
extern "C" int csub(int, int);

int main()
{
int sum1 = cppadd(5, 4);
int sub1 = cppsub(5, 4);
int sum2 = cadd(5, 4);
int sub2 = csub(5, 4);
cout << "cppadd=" << sum1 << ",cppsub=" << sub1 << endl;
printf("cadd: %d, csub: %d\n", sum2, sub2);
return 0;
}

如果不加上​​extern "c"​

1>2.obj : error LNK2019: 无法解析的外部符号 "int __cdecl cadd(int,int)" (?cadd@@YAHHH@Z),函数 _main 中引用了该符号
1>2.obj : error LNK2019: 无法解析的外部符号 "int __cdecl csub(int,int)" (?csub@@YAHHH@Z),函数 _main 中引用了该符号

就报错

原因..c++文件的换名机制

你在c++源代码中写的是​​cppadd​​​函数名,那么在编译的时候就会更换为一个类似于​​cppadd@@YAHHH@Z​

对于c文件的编译就不会出现换名的机制

所以的话,如果这样写代码

#include<iostream>
using namespace std;
#pragma comment( lib, "../one/Debug/cpplib.lib")
#pragma comment( lib, "../one/Debug/clib.lib")

int cppsub(int, int );
int cppadd(int, int);

int cadd(int, int);
int csub(int, int);

int main()
{
int sum1 = cppadd(5, 4);
int sub1 = cppsub(5, 4);
int sum2 = cadd(5, 4);
int sub2 = csub(5, 4);
cout << "cppadd=" << sum1 << ",cppsub=" << sub1 << endl;
printf("cadd: %d, csub: %d\n", sum2, sub2);
return 0;
}

C++项目hi拿着编译好的c函数​​cadd@@YAHHH@Z​​​去寻找clib的函数​​cadd​​,这样就会报错

解决办法就是让c++在编译的时候不去修改名字

c项目调用cpp静态库

问题先放在这里

动态库

● 动态库特点

1 )运行时独立存在,他有独立的运行空间

2 )源码不会链接到执行程序,而只是提供一API

3 )使用时加载(使用动态库必须使动态库执行)

● 与静态库的比较:

1 )由于静态库是将代码嵌入到使用程序中,[^源码拷贝] 多个程序使用时,会有多份代码,所以代码体积会增大。

动态库的代码只需要存在一份 ,其他程序通过函数地址使用,所以代码体积小。

2 )静态库发生变化后,新的代码需要重新链接嵌入到执行程序中。动态库发生变化后,

如果库中函数的定义(或地址)未变化,其他使用DLL的程序不需重新链接。

● 创建动态库项目

添加库程序

库程序导出-提供给使用者库中的函数等信息。

导出方式

声明导出

使用_declspec(dllexport)导出函数,只需要在函数声明​​最前面​​加上这一句就欧克[^这里我会非常任意忘记]

_declspec(dllexport) int add(int,int)
{
//不写_declspec的话,你的函数就不会导出,也就是不会出现在导出表里面
...
}

注意:动态库编译链接后, 也会有LIB文件,是作为动态库函数​​映射​​​使用,与静态库​​不完全相同​​。

这种导出方式对于cpp文件来说,会出现换名的机制,woc

模块定义文件

模块定义文件.def 例如:

LIBRARY DLLFunc //库,LIBRARY是必须写的,后面的是dll的名字,此刻不带后缀名
EXPORTS //库导出表
add_func @1 //导出的函数
sub_func @2
...

隐式链接

操作系统负责使动态库执行,系统悄悄的把数据丢到内存

可以在函数原型的声明前,增加 ​​_declspec(dllimport)​

#include <iostream>
using namespace std;
#pragma comment( lib, "../Main_NULL/Debug/CppDll.lib") //寻找lib文件

//声明下面为导入的函数
_declspec(dllimport) int add(int, int);
_declspec(dllimport) int sub(int, int);
int main()
{
std::cout << add(1, 2) << " " << sub(10, 6) << endl;//函数的调用
}

dll的源文件

//把下面的函数声明为导出函数
_declspec(dllexport)int add(int x, int y)
{
return x + y;
}
_declspec(dllexport)int sub(int x, int y)
{
return x - y;
}

隐式链接的情况,dll文件可以存放的路径:

( 1 ) 与执行文件中同一个目录下

( 2 ) 当前工作目录

( 3 ) Windows目录

( 4 ) Windows/System32目录

( 5 ) Windows/System

( 6 ) 环境变量PATH指定目录

( )显式链接(程序员自 负责使动态库执行) 1 )定义函数指针类型typedef 2 )加载动态库

HMODULE LoadLibrary(
LPCTSTR IpFileName //动态库文件名或全路径
);返回DLL的实例句柄( HINSTANCE )

3 )获取函数地址

FARPROC GetProcAddress(
HMODULE hModule,//DLL句柄
LPCSTR IpProcName //函数名称
);成功返回函数地址

4 )使用函数 5 )卸载动态库

BOOL FreeLibrary(
HMODULE hModule //DLL的实例句柄
);

显示链接

显式链接(程序员自己负责使动态库执行)

1 )定义函数指针类型、typedef

2 )加载动态库

HMODULE LoadLibrary(
LPCTSTR IpFileName //动态库文件名或全路径
);返回DLL的实例句柄( HINSTANCE )

当dll在系统路径或者与exe在同一个路径之下的时候,IpFileNam=dll的文件名,否者的话就写dll路径的全称

该函数返回的是动态链接库dll在内存中的地址,实际地址[^因为基址重定位的原因,每一次的加载的地址都会不一样]

3 )获取函数地址

FARPROC GetProcAddress(
HMODULE hModule, //DLL句柄
LPCSTR IpProcName //函数名称
);成功返回函数地址

基于dll的实际地址,通过参数IpProcName遍历导出函数表,获取一个导出函数的编号

然后去获取函数的导出地址,这些地址是相对的地址,相对于dll句柄,那个内存块的地址

找到后,dll的地址+那个相对地址=最后的导出地址

4 )使用函数

5 )卸载动态库

BOOL FreeLibrary(
HMODULE hModule //DLL的实例句柄
);

声明导出

cpp文件

// 1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <stdio.h>
#include<Windows.h>
typedef int(*ptr)(int, int);
int main()
{
HINSTANCE dll_ptr = LoadLibrary("dll.dll");
if (dll_ptr == NULL)
exit(-1);
ptr me_add = (ptr)GetProcAddress(dll_ptr, "?add@@YAHHH@Z");
if (me_add == NULL)
exit(-1);
int sum = me_add(1, 2);
printf("%d\n", sum);
}

dll文件

_declspec(dllexport)int add(int x, int y)
{
return (x + y);
}

模块化定义文件

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

int func(int x, int y)
{
return (x - y);
}
extern "C" _declspec(dllexport) int add(int x, int y)
{
return x + y;
}

LIBRARY 
EXPORTS
add @1

不用说了,vs2022找不到他的文件,根本无法使用

动态链接库封装一个类

话不多说

关于dll项目

设立头文件

#pragma once

#ifndef _DLLCLASS_H //注意这个东西,记住就可以了
#define _DLLCLASS_H


//下面这个宏定义值得学习,如果有会是怎么样,如果没有会是怎么样
#ifdef dll_class_botton
#define dll_class _declspec(dllexport)//DLL开发者
#else
#define dll_class _declspec(dllimport)//使用者
#endif
class dll_class math
{
public: //注意这个权限
int sub(int x, int y);
};
#endif //注意这个东西,记住就可以了

关于源文件

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

#define dll_class_botton //打开那个开关
#include "test.h" //包含刚才的头文件

int math::sub(int x, int y) //定义一下刚才的头文件
{
return x - y;
}

关于main项目如何使用dll的class

#include < iostream>
using namespace std;

#pragma comment( lib, "../Main_NULL/Debug/one.lib" ) //隐式的调用lib文件
#include "../one/test.h" //访问那个dll的类class


int main()
{
math class_math;//用那个声明创建一个对象
int sum = class_math.sub(10, 3);//调用成员函数
cout << "sum=" << sum << endl;
return 0;
}

线程

一些关键字的解释

进程开启意味着分配内存,而不是程序的开始执行

也就是一个车间,车间的设立不是意味着开工,而是分配了工作的各个设备与资源

每个线程都有一个ID

相当于每个车间的员工都有自己的员工编号

进程的资源可以共享给任何线程,

相当于每个车间的设备,在没有具体分配之前,我们是大家都可以使用,这会由于没有具体的分配,就会导致所有员工一拥而上

分配不平衡,分配异常

但是线程的栈空间资源不是共享的.每个线程都有自己独立的栈空间

相当于,车间的员工每个人都有自己的隐私空间,这个隐私空间只有那个员工才可以用

而且这个隐私空间是可以被栈保存的

线程的调度

CPU把执行时间分为时间片段,然后依次执行这些时间片段的任务,注之一是依次执行,按照顺序执行

关于CPU的使用权分配

这个可以比喻为,流水线在传送带上的速度是一样的,不会作一个停留

于是传送带上的产品留给每个员工的工作时间是一样的,如果其中一个线程做的事情很多,需要的时间很多,但是传送带还是一样的向前传送

相关函数

线程创建

HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全属性,一个废弃的参数,直接0
__in SIZE_T dwStackSize, //栈空间分配大小,安装1m对齐,只要小于1m,就还是1m大小`
__in LPTHREAD_START_ROUTINE lpStartAddress, //线程函数的处理地址
__in_opt LPVOID lpParameter, //线程处理函数的参数指针,这里是一个void的指针
__in DWORD dwCreationFlags, //线程的创建的方式.分为立即执行和休眠2钟方式
//立即执行是0
//挂起是CREATE_SUSPENDWD
__out_opt LPDWORD lpThreadId //返回线程的ID
);
//函数返回线程的句柄,也就是线程的内存地址

线程处理函数,函数的性质不能修改,函数的返回值类型不能修改,函数的参数类型不能修改

DWORD thread_Prc_DIY(LPVOID 参数指针)//创建线程时的参数指针
{
return 0;
}

值得注意的是,当主线程退出时,子线程就g了,就像车间的灯光关闭了,车间的工作人员还怎么工作

杀死自定的线程,相当于主管开出指定的员工

BOOL WINAPI TerminateThread(
__inout HANDLE hThread,
__in DWORD dwExitCode
);

自杀函数

VOID WINAPI ExitThread(
__in DWORD dwExitCode
);

谁调用它,谁就被自杀,相当于一份毒药,

ExitCode退出码,也就是告诉系统函数我们要怎么样退出,退出的原因是什么....这个参数随便填写,一般填写为0

获取当前线程的句柄

HANDLE WINAPI GetCurrentThread();//参数为0

获取当前线程的ID

DWORD GetCurrentThreadId();//参数为空

等待句柄

这些句柄的特点是可以发出信号

类似的句柄有: 线程句柄

什么叫发出信号:

无信号:当前线程被阻塞,或者没被执行

有信号:当前线程结束,当前线程直接调用发出信号的函数

等待单个线程句柄有信号

DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle, //等待哪个线程
__in DWORD dwMilliseconds //最大等候时间 INFINITE
);

这是一个阻塞函数,当函数接收到信号的时候才会马上返回,不再阻塞

如果一直没信号,函数就会一直阻塞

另外补充一下,这个函数是阻塞当前线程,而不是阻塞其它线程

等待多个线程句柄有信号

DWORD WINAPI WaitForMultipleObjects(
__in DWORD nCount, //等候多少个句柄有信号
__in const HANDLE* lpHandles, //句柄数组的地址
__in BOOL bWaitAll, //等候方式 如果是true,当所有的句柄都有信号的时候才会解除阻塞
// FALSE ,但凡有1个句柄有信号就会解除阻塞
__in DWORD dwMilliseconds //最大等候时间
);

一个简单线程的创建

#include<Windows.h>
#include<stdio.h>
DWORD CALLBACK Proc1(LPVOID pParam)//.传递过来的是一个空的指针
{
char* pszText = (char*)pParam;//实现一个指针类型的转化
while (1)
{
printf(" %s\n", pszText);
Sleep(1000);//0.1秒钟
}
return 0;
}
int main()
{
DWORD nID = 0;
char pszText[] = "*********";
char pszText2[] = "--------";
HANDLE hThread = CreateThread(NULL, 0, Proc1, pszText, 0, &nID);
HANDLE hThread2 = CreateThread(NULL, 0, Proc1, pszText2, 0, &nID);
system("pause nul"); //阻塞主线程的结束
return 0;
}

挂起/唤醒线程

挂起一个线程

DWORD WINAPI SuspendThread(
__in HANDLE hThread //线程句柄
);

唤醒一个线程

DWORD WINAPI ResumeThread(
__in HANDLE hThread //线程句柄
);

#include<Windows.h>
#include<stdio.h>
DWORD CALLBACK Proc1(LPVOID pParam)//.传递过来的是一个空的指针
{
char* pszText = (char*)pParam;//实现一个指针类型的转化
while (1)
{
printf(" %s\n", pszText);
Sleep(1000);
}
return 0;
}
int main()
{
DWORD nID = 0;
char pszText[] = "*********";
char pszText2[] = "--------";
HANDLE hThread = CreateThread(NULL, 0, Proc1, pszText, 0, &nID);
HANDLE hThread2 = CreateThread(NULL, 0, Proc1, pszText2, CREATE_SUSPENDED, &nID);//函数一开始就是挂起的状态

system("pause nul"); //开启设置
if (hThread == NULL || hThread2 == NULL)
exit(-1);
SuspendThread(hThread);//挂起线程1
ResumeThread(hThread2);//唤醒线程2
system("pause nul"); //阻塞主线程的结束
return 0;
}

临界资源-加速机制

当多个线程操作同一个资源的时候,这个资源叫临界资源,此刻我们要采用加速机制

这个资源可以是一个变量,一个内存,一个文件,也可以是一个窗口

原子加速

当多个线程对同一个数据进行运算符操作的时候,会导致数据丢失

比多个工人对同一个零件进行操作的话,那个零件损失的可能性就会很大,很难复原

一个人操作的话,复原的可能性会更加的大

非原子加速

#include<Windows.h>
#include<stdio.h>
long long x = 0;
DWORD CALLBACK Proc1(LPVOID pParam)//.传递过来的是一个空的指针
{
int i = 0;
for (i = 0; i < 100000000; i++)
x++;
return 0;
}
DWORD CALLBACK Proc2(LPVOID pParam)//.传递过来的是一个空的指针
{
int i = 0;
for (i = 0; i < 100000000; i++)
x++;
return 0;
}
int main()
{
DWORD nID = 0;
HANDLE hThread[2] = { 0 };
hThread[0] = CreateThread(NULL, 0, Proc1, NULL, 0, &nID);
hThread[1] = CreateThread(NULL, 0, Proc2, NULL, 0, &nID);

WaitForMultipleObjects(2, hThread, TRUE, INFINITE);

printf("%d", (int)x);
return 0;
}

数据丢失的原因是什么??

就是CPU分配的时间段原因,CPU给每个线程分配的时间太短了

导致CPU对于不同的线程会穿插的执行时报错

时间段1 执行线程1

时间段2 执行线程2

时间段3 执行线程1

时间段4 执行线程2

所以的话...在CPU的执行切换之间会导致数据的丢失

好比一个i++的操作

mov eax,i //如果在这里打断了,我是说如果
add eax,1 //如果在这里打断了,我是说如果
mov i,eax //如果在这里打断了,我是说如果

上面3条指令之间,CPU不是说把3条指令执行完了再切换,而是说可能在3条指令之间的其中某条

指令突然就切换了,为什么在其中一条指令就切换了,主要是CPU给每个线程分配的时间段的问题

这就导致了一个切换的打断问题,在切换之前,会有一个现场的保护

mov eax,i 
add eax,1 //如果在这里打断了
mov i,eax
//当转回来的时候,本来i=10,10来自另外一个线程,而这里的eax=1,现在i=eax,于是10就丢失l

解决办法就是用时间换效率与准确性

刚才不是说CPU会来回的切换吗,导致一个打断的操作

现在我们准备了一个函数​​InterlockedIncrement(&x);​

通过翻译可以知道,这是一个++的操作,他是如何实现的?

其实他是一个阻塞函数,阻塞解除的标志就是查看一个内存空间有没有被加锁

​InterlockedIncrement(&x);​​在执行这一句的时候

InterlockedIncrement(&x)
{
if(x没被当前线程加锁)
{
让x被加锁;
x++;
}
else if(x被当前线程加锁)
{
x++;
x被当前程序解锁;
}
else
{
阻塞,消耗CPU的时间段,啥事情也不干,就不会对某一个数据进行操作
}
}

这个函数实现了不同线程之间对同一个数据内存的加锁

而且不同线程之间的相互阻塞

互斥加锁

创建互斥

解决多个线程下对同一个代码资源的共享问题

HANDLE WINAPI CreateMutex(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性,垃圾,NULL
__in BOOL bInitialOwner,
//true 调用该函数的线程拥有互斥,相当于创造了一张火车票,该火车票这属于当前线程
//false 调用该函数的线程没有互斥,相当于创造了一个火车票,该火车票不属于任何人
__in_opt LPCTSTR lpName //互斥的字符串名字,随便取
);
//返回线程句柄的东西,就是创建了一个内存空间
//也就是这个空间最后是要释放的

互斥的特性:

在任意一个时间段,

只有一个线程拥有互斥,

其它线程只能等待,

拥有互斥的线程具有独占的特性

把互斥句柄的信号比作火车票

只要我有火车票,其它任何人都不可能有火车票

只要我把火车票放手了,其他人就可以有火车票,最先等待火车票的人,就可以先获取

等候互斥信号

WaitForSingleObject()

谁先等互斥,谁最先拥有互斥

PS:谁先用Wait...谁就现有互斥

买火车票:

谁先等火车票,谁最可能买到火车票

只要火车票被买到,后面的人就不可能买到火车票

除非那个人把火车票给退了,后面的人就可以买到

互斥信号的释放

如何把火车票给退掉?

BOOL WINAPI ReleaseMutex(
__in HANDLE hMutex //创建互斥的时候返回的句柄
);
//把互斥给扔掉

句柄的关闭

BOOL WINAPI CloseHandle(
__in HANDLE hObject
);

互斥前

#include<Windows.h>
#include<stdio.h>
long x = 0;
DWORD CALLBACK Proc1(LPVOID pParam)//.传递过来的是一个空的指针
{
int i = 0;
char* str = (char*)pParam;
while (1)
{
for (i = 0; i < 4; i++)
{
printf("%c", str[i]);;//它的参数类型只能是long
Sleep(100);
}
puts("");
}


return 0;
}
DWORD CALLBACK Proc2(LPVOID pParam)//.传递过来的是一个空的指针
{
int i = 0;
char* str = (char*)pParam;
while (1)
{
for (i = 0; i < 4; i++)
{
printf("%c", str[i]);;//它的参数类型只能是long
Sleep(100);
}
puts("");
}

return 0;
}
int main()
{
DWORD nID = 0;
HANDLE hThread[2] = { 0 };
char str1[] = "****";
char str2[] = "----";
hThread[0] = CreateThread(NULL, 0, Proc1, str1, 0, &nID);
hThread[1] = CreateThread(NULL, 0, Proc2, str2, 0, &nID);
system("pause nul");
return 0;
}

使用互斥后

#include<Windows.h>
#include<stdio.h>
long x = 0;
HANDLE hc = 0;
DWORD CALLBACK Proc1(LPVOID pParam)//.传递过来的是一个空的指针
{
int i = 0;
char* str = (char*)pParam;
while (1)
{
WaitForSingleObject(hc, INFINITE);
/*
他是一个阻塞函数,
如果有互斥句柄没被抓,他就抓住,然后hc句柄就属于当前线程了,然后这函数就立马返回
如果没有互斥句柄,也就阻塞,让CPU睡觉
*/
for (i = 0; i < 4; i++)
{
printf("%c", str[i]);;//它的参数类型只能是long
Sleep(100);
}
puts("");
ReleaseMutex(hc);
}
return 0;
}
DWORD CALLBACK Proc2(LPVOID pParam)//.传递过来的是一个空的指针
{
int i = 0;
char* str = (char*)pParam;
while (1)
{
WaitForSingleObject(hc, INFINITE);
for (i =0; i < 4; i++)
{
printf("%c", str[i]);;//它的参数类型只能是long
Sleep(100);
}
puts("");
ReleaseMutex(hc);
}

return 0;
}
int main()
{
DWORD nID = 0;
HANDLE hThread[2] = { 0 };
char str1[] = "****";
char str2[] = "----";
hc = CreateMutex(
__in_opt NULL,
__in FALSE,
__in_opt NULL //互斥的字符串名字,随便取
);
hThread[0] = CreateThread(NULL, 0, Proc1, str1, 0, &nID);
hThread[1] = CreateThread(NULL, 0, Proc2, str2, 0, &nID);

system("pause nul");
CloseHandle(hc);
return 0;
}

事件与信号

除了互斥可以解决资源的分配问题,还有另外的方法

事件,用时间去解决线程之间的资源分配的问题

事件的创建

HANDLE WINAPI CreateEvent(
__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性,直接设置为NULL就可
__in BOOL bManualReset,
//有信号->无信号的过程
//true 手动控制
//FALSE 自动控制,也就是WaitForSingleObject后,自动的变为无信号
__in BOOL bInitialState,//事件之处的状态
//true 一创建就有信号
//false 一开始没信号
__in_opt LPCTSTR lpName //时间的字符串名字
);

他是一个可等候句柄, 有信号与无信号的状态,而这个信号是由程序员自己控制的

所以可以使用WaitForSingleObject函数

发送的信号

BOOL WINAPI SetEvent(
__in HANDLE hEvent //创建事件时返回的句柄
);

信号归还

复位,变为无信号的过程,信号归还

BOOL WINAPI ResetEvent(
__in HANDLE hEvent //创建事件时返回的句柄
);

#include<Windows.h>
#include<stdio.h>
long x = 0;
HANDLE ev = 0;
DWORD CALLBACK show(LPVOID pParam)//.传递过来的是一个空的指针
{
int i = 0;
char* str = (char*)pParam;
while (1)
{
WaitForSingleObject(ev, INFINITE);//抓取信号
printf("*********\n");
ResetEvent(ev);//信号归还
}
return 0;
}
DWORD CALLBACK control_proc(LPVOID pParam)//.传递过来的是一个空的指针
{
while (1)
{
Sleep(1000);
SetEvent(ev);//发送信号
}

return 0;
}
int main()
{
DWORD nID = 0;
HANDLE hThread[2] = { 0 };
ev = CreateEvent(NULL, TRUE, FALSE, NULL);//安全属性无,一开始信号手动控制,,一开始创建无信号,事件名字NULL
hThread[0] = CreateThread(NULL, 0, show, NULL, 0, &nID);
hThread[1] = CreateThread(NULL, 0, control_proc, NULL, 0, &nID);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
if (ev == NULL)
exit(-1);
CloseHandle(ev);
return 0;
}

线程的死锁

什么意思?

一下情况出现于一开始没有信号

线程1

wait事件1
开启事件2

线程2

wait事件2
开启事件1

信号量通过

除了事件,还有信号量可以解决事件问题

它基于事件,限制了事件的次数

初始化信号量

HANDLE WINAPI CreateSemaphore(
__in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全属性,NULL
__in LONG lInitialCount, //通过wait的初始化次数
//当lInitialCount=5,可以无条件通过wait,相当于有信号,每通过一次,就--.少1
//当lInitialCoun=0,无法通过
__in LONG lMaximumCount, //信号量的可设置的最大个数
__in_opt LPCTSTR lpName //信号量的字符串名字
);

它也拥有可等候的信号句柄

给信号量指定数量

BOOL WINAPI ReleaseSemaphore(
__in HANDLE hSemaphore, //信号量句柄,创建信号返回的句柄
__in LONG lReleaseCount, //新的信号量数量
__out_opt LPLONG lpPreviousCount //一个指针,返回原来剩余的信号量
);

最后CloseHandle

#include <Windows.h>
#include <stdio.h>
HANDLE g_se = 0;//信号量句柄
DWORD CALLBACK TestProc(LPVOID pParam)
{
while (1)
{
WaitForSingleObject(g_se, INFINITE);
printf("*******\n");
}
}
int main()
{
g_se = CreateSemaphore(NULL, 3, 10, NULL);//安全属性,初始化3个,最大10个,信号量的名字是NULL
DWORD nID = 0;
HANDLE hThread = CreateThread(NULL, 0, TestProc, NULL, 0, &nID);
system("pause nul");
if (g_se == NULL)
exit(-1);
ReleaseSemaphore(g_se, 5, NULL);//句柄,给他几个信号,返回原来的信号
//如果信号量错过最大值的话,函数就会失败

if (hThread == NULL)
exit(-1);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(g_se);
CloseHandle(hThread);
return 0;
}

遇到的API

类型

tchar/wchar 表示的字符大于8位

#include <iostream>
#include <locale>
#include <cstdlib>
#include<windows.h>
using namespace std;

locale loc("chs");//windows下ok

//这段貌似在ubuntu下ok
//locale loc("zh_CN.UTF-8");
//而且还需要在ubuntu的终端中执行:
//sudo locale-gen

int main()
{
wchar_t wStr[] = L"这是一段中文";
wcout.imbue(loc);
wcout << wStr << endl;
system("pause nul");
return 0;
}

HWND、HANDLE、HMODULE、HINSTANCE之间的差别不是在变量类型上,而是在语义上。

HINSTANCE

HINSTANCE :​​unsigned long​​ 一个结构体指针

句柄,一般用于window窗口程序, 在win32下与HMODULE是相同的东西,

HINSTANCE 是“句柄型”数据类型。为什么我们叫它句柄型,因为他有一定的性质, 比如,不能拿来做四则运算

说回来,它相当于装入到了内存的资源的ID,可以说他是指针。

实际际上HINSTANCE并非纯粹意义上的句柄(HANDLE),因为它实际上是由物理PE文件映射到WINDOWS进程的虚拟模块首地址Imagebase,

LPVOID

LPVOID是一个没有类型的指针

其中LPVOID lParam即为空类型指针,可以将其理解为long型的指针,指向void型。

tchar

因为C++支持两种字符串,即常规的ANSI编码(使用""包裹)和Unicode编码(使用L""包裹),

这样对应的就有了两套字符串处理函数,比如:strlen和wcslen,分别用于处理两种字符串

LPCTSTR / LPTSTR

LPCTSTR == const TCHAR *

LPTSTR == TCHAR *

把字母分开解释

L表示long​​指针​​

P表示这是一个指针,pointer

C表示是一个常量,const

T表示在Win32环境中, 有一个_T宏

STR表示这个变量是一个字符串

意思就是 :指向常量字符串的32位指针,可用于Unicode和DBCS。

process : module

win32环境中,一个进程代表一个exe,或说代表一个应用程序的实例,而一个线程代表进程里代码的一条执行线路。

车间就是进程,车间只是分配了资源,不执行任何代码,

工人就是线程,

工人在利用这些资源干活,是线程在执行代码,进程不做事

一个进程会有主线程,也就是一个车间需要一些干重活的人

win32下进程与模块没有区别。


win32下

模块分为两种:进程内模块和进程外模块,

前者共享进程的内存空间,比如许多在进程中加载的dll,每个dll可以看做是一个独立的模块;

后者与进程一样,独立运行,通常供其它进程调用(由引用计数之类的管理),进程外模块当然也包括dll。!!??!!??

一个模块代表的是一个运行中的exe文件或者dll文件,用来代表这个文件中的所有代码和资源,,

所以磁盘上的文件不是模块,载入内存后运行时就叫做模块。同样的,一个应用程序调用其他dll中的api时,这些dll文件被装入内存, 就产生了不同的模块。也叫导出函数的模块???

模块句柄:进程句柄

一个进程里有N个进程内模块,为了区分地址空间中的不同模块,每个模块都有一个唯一的模块句柄来标识,模块句柄就是模块在进程中的首址

模块(exe或者dll)被加载后,其开始地址就是该模块的句柄值,通常应用程序都是通过模块句柄来访问它的每个进程中的模块(一个应用程序可能启动多个进程),事实上模块句柄的值就是该模块映射到进程中的地址。

HINTERNET -> hinternet 网络句柄

_DEBUG_EVENT

描述调试的事件

typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

​​https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-debug_event​​

Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄..任意的对象,

就像你非每个东西编的

转化

long atol( const char *str);
long _atol_l( const char *str,_locale_t locale);
long _wtol(const wchar_t *str);
long _wtol_l( const wchar_t *str,_locale_t locale);

参数

str

locale

这是一个参数宏?

value = atol(str); //把数字字符串转化为整数
..后面的同理

函数

Messagebox

可选项

MB_ABORTRETRYIGNORE:消息框含有三个按钮:Abort,Retry和Ignore。

MB_OK:消息框含有一个按钮:OK。这是缺省值。

MB_OKCANCEL:消息框含有两个按钮:OK和Cancel。

MB_RETRYCANCEL:消息框含有两个按钮:Retry和Cancel。

MB_YESNO:消息框含有两个按钮:Yes和No。

MB_YESNOCANCEL:消息框含有三个按钮:Yes,No和Cancel。

指定下列标志中的一个来显示消息框中的图标:标志的含义如下。

MB_ICONEXCLAMATION:

MB_ICONWARNING:一个惊叹号出现在消息框。

MB_ICONINFORMATION:

MB_ICONASTERISK:一个圆圈中小写字母i组成的图标出现在消息框。

MB_ICONQUESTION:一个问题标记图标出现在消息框。

MB_ICONSTOP:

MB_ICONERROR:

MB_ICONHAND:一个停止消息图标出现在消息框。

指定下列标志中的一个来显不缺省的按钮:标志的含义如下。

MB_DEFBUTTON1:第一个按钮为缺省按钮。如果MB_DEFBUTTON2,MB_DEFBUTTON3,MB_DEFBUTTON4没有被指定,则MB_DEFBUTTON1为缺省值。

MB_DEFSUTTON2;第二个按钮为缺省按钮。

MB_DEFBUTTON3:第三个按钮为缺省按钮。

MB_DEFBUTTON4:第四个按钮为缺省按钮。

指定下列标志中的一个来显示对话框的形态:标志的含义如卜。

MB_APPLMODAL:在hwnd参数标识的窗口中继续工作以前,用户一定响应消息框。但是,用户可以移动到其他线程的窗口且在这些窗口中工作。根据应用程序中窗口的层次机构,用户则以移动到线程内的其他窗口。所有母消息框的子窗口自动地失效,但是弹出窗口不是这样。如果既没有指定MB_SYSTEMMODAL也没有指定MB_TASKMOOAL,则MB_APPLMODAL为缺省的。

MB_SYSTEMMODAL:除了消息框有WB_EX_TOPMOST类型,MB_APPLMODAL和WS_EX_TOPMOST一样。用系统模态消息框来改变各种各样的用户,主要的损坏错误需要立即注意(例如,内存溢出)。如果不是那些与hwnd联系的窗口,此标志对用户对窗口的相互联系没有影响。

MB_TASKMODAL:如果参数hwnd为NULL,除了所有属于当前线程高层次的窗口足失效的,MB_TASKMODALL和MB_ApPLMODAL一样。当调用应用程序或库没有一个可以得到的窗口句柄时,使用此标志。但仍需要阻止到调用应用程序甲其他窗口的输入而不是搁置其他线程。

另外,可以指定下列标志。

MB_DEFAULT_DESKTOP_ONLy:接收输入的当前桌面一定是一个缺省桌面。否则,函数调用失败。缺省桌面是一个在用户已经纪录且以后应用程序在此上面运行的桌面。

MB_HELP:把一个Help按钮增加到消息框。选择Help按钮或按F1产生一个Help事件。

MB_RIGHT:文本为右调整。

MB_RTLREADING:用在Hebrew和Arabic系统中从右到左的顺序显示消息和大写文本。

MB_SETFOREGROUND:消息框变为前景窗口。在内部系统为消息个调用SetForegrundWindow函数。

MB_TOPMOSI:消息框用WS_EX_TOPMOST窗口类型来创建MB_SERVICE_NOTIFICATION。

Windows NT:调用程序是一个通知事件的用户的服务程序。函数在当前活动桌面上显示一个消息框,即使没有用户登记到计算机。

如果设置了此参数,则hwnd参数一定为NULL。所以消息框可以出现在一个桌面上而不是桌面响应参数hwnd。

对于Windows NT 4.0,MB_SERVICE_NOTIFICATION的值已经改变。对于旧的和新的值,请参见WINUSER。

Windows NT 4.O通过把旧值映射到 MessageBox 和 MessageBox Ex执行中的新值,为先存在的服务程序提供逆兼容。此映射只为有了版本数目的可执行程序而做。

为了建立一个用MB_SERVICE_NOTIFICATION的服务器,且可以在Windows NT 3.X和Window NT 4.0上执行,可有两种选择。在连接时间,指定一个版本数目小于4.0的版本,或在连接时间,指定一个4.0版本。在运行时间,用函数GetVersionEx来检测系统版本,然后在Windows NT 3.X上用MB_SERVICE_NOTIFICATION_NT 3.x来运行和在Windows NT 4.0上用MB_SERVICE_NOTIFICAION来运行。MB_SERVCE_NOTIFICATION_NT3.x(WindowNT)此值响应于为WindowNT3.51的MB_SERVICE_NOTIFICAION

定义的值。

返回值

如果没有足够的内存来创建消息框,则返回值为零。如果函数调用成功,则返回值为下列对话框返回的菜单项目值中的一个:

IDABORT:Abort 按钮被选中。IDCANCEL:Cancel按钮被选中。IDIGNORE:Ignore按钮被选中。

IDNO:NO按钮被选中。IDOK:OK按钮被选中。IDRETRY:RETRY按钮被选中。

IDYES:YES按钮被选中。

如果一个消息框有一个Cancel按钮,且如果Esc键被按下或Cancel键被选择,则函数返回IDCANCEL值。如果消息框没有Cancel按钮,则按Esc键没有作用。

备注:当创建一个系统模态消息框来表示系统在内存的低端中时,由lpTeXt和lpCaption参数指向的字符串不应该从一个资源文件中取出,因为试图装载此资源可能导致失败。

当一个应用程序调用 MessageBox ,且为uType参数指定MB_ICONHAND和MB_SYSTEMMODAL标志时,系统不管可用内存为多少,直接显示结果消息框。当这些标志被指定,系统把消息框文本的长度局限于三行。系统不能自动截断要填到消息框的行,但是消息字符串一定含有回车换行,以在合适的位置换行。

如果在对话框出现的的同时创建了消息框,则可使用对话框的句柄作为hwnd参数,hwnd参数不应该标识一个子窗口,例如对话框中的一个控制。

Windows 95:系统可以支持最大达16364个窗口句柄。

Windows CE:Windows CE 不支持uType参数的下列值:

MB_STSTEMMODAL;MB_TASKMODAL;MB_HELP;MB_RTLREADING;MB_DEFAULT_DESKTOP_ONLY;

MB_SERVICE_NOTIFICATION;MB_USERICON。

不支持下列返回值:IDCLOSE;IDHELP。

速查:Windows:3.1及以上版本:Windows:95及以上版本;Windows:1.0及以上版本;头文件:Winuser.h;库文件:USer32.lib;URicode:在Windows NT上实现为Unicode和ANSI两种版本。

AdjustTokenPrivileges 特权设置

用于启用或禁止,指定访问令牌的特权

​​https://developer.aliyun.com/article/366969​​

BOOL AdjustTokenPrivileges(
[in] HANDLE TokenHandle, //包含要修改de特权的句柄
[in] BOOL DisableAllPrivileges, //是否禁用所有令牌的权限
[in, optional] PTOKEN_PRIVILEGES NewState, //新特权信息的指针,指定了一组特权和他们的属性.
[in] DWORD BufferLength, //指向的缓冲区的大小
[out, optional] PTOKEN_PRIVILEGES PreviousState, //接收被改变特权当前状态的Buffer,他和都3个参数的类型一样的
[out, optional] PDWORD ReturnLength //接收PreviousState缓存区要求的大小
);

AdjustTokenPrivileges函数无法向访问令牌添加新权限。它只能启用或禁用令牌的现有权限。要确定令牌的权限,请调用 ​​GetTokenInformation​​函数。

返回值 如果函数成功,则返回值非零。要确定函数是否调整了所有指定的权限,请调用 GetLastError,它会在函数成功时返回以下值之一: 令牌不具有在NewState参数中指定的一项或多项特权。即使没有调整权限,该函数也可能会成功并出现此错误值。PreviousState参数表示已调整的权限 。

如果函数失败,则返回值为零。要获取扩展的错误信息,请调用GetLastError。

CopyFile

BOOL CopyFile(
[in] LPCTSTR lpExistingFileName, //S
[in] LPCTSTR lpNewFileName, //D
[in] BOOL bFailIfExists
);

最后一个参数

[in] bFailIfExists

如果此参数为TRUE,原文存在,则函数失败

如果此参数为 FALSE,源文件不存在,于是就覆盖

返回值

如果函数成功,则返回值非零。 如果函数失败,则返回值为零。要获取扩展的错误信息,请调用 GetLastError。

CreateThread 建立新的线程

该​​函数​​​在​​主线程​​​的基础上创建一个新线程。​​线程​​终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象

导入

一个进程可以拥有多个线程,但是一个线程必须有一个进程。线程自己不拥有系统资源,只有运行所必须的一些数据结构,

但它可以与同属于一个进程的其它线程共享进程所拥有的全部资源,同一个进程中的多个线程可以并发执行

HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,//指向SECURITY_ATTRIBUTES的指针,
//用于定义新线程的安全属性,一般设置成NULL;

SIZE_T dwStackSize, //分配以字节数表示的线程堆栈的大小,默认值是0;
LPTHREAD_START_ROUTINE lpStartAddress, //指向一个线程函数地址。每个线程都有自己的线程函数
//线程函数是线程具体的DIY执行代码;
__drv_aliasesMem LPVOID lpParameter, //传给新线程(可执行代码)的参数指针,之一是参数
DWORD dwCreationFlags, //表示创建线程的运行状态,其中CREATE_SUSPEND
//表示挂起当前创建的线程,而0表示立即执行当前创建的进程;
[out, optional] LPDWORD lpThreadId //返回新创建的线程的ID编号;
);

返回值

如果函数成功,则返回值是新线程的句柄。

也是有这样的写法

CloseHandle(CreateThread(NULL, 0, My_Thread_Proc, NULL, 0, NULL)); //创建线程,关闭句柄

如果函数失败,则返回值为​​NULL​​。

eg:

hThread = CreateThread(NULL, 0, ThreadProc_TLS, NULL, 0, NULL);

资源占用问题

线程自己不拥有系统资源,但是占用系统的资源

示例代码

#include <iostream>
#include <windows.h>
using namespace std;

DWORD WINAPI Fun(LPVOID lpParamter)
{
while(1) { cout<<"Fun Girl!"<<endl;Sleep(500);}
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
CloseHandle(hThread);
while(1) { cout<<"main Boy!"<<endl;Sleep(1000);}
return 0;
}

输出的时候会出现ManiBoy和FunGirl出现在同一行!!

这是有问题的?

多线程的程序时并发地运行的,多个线程之间如果公用了一些资源的话,我们并不能保证这些资源都能正确地被利用,因为这个时候资源并不是独占的

多个线程虽然是并发运行的,但是有一些操作是必须一气呵成的,不允许打断的

#include <iostream>
#include <windows.h>
using namespace std;
HANDLE A_Handle_Of_Resource;
DWORD WINAPI Fun(LPVOID lpParamter)
{
while (1)
{
WaitForSingleObject(A_Handle_Of_Resource, INFINITE);
cout << "Girl Dance!" << endl;
Sleep(500);
ReleaseMutex(A_Handle_Of_Resource);
}
}
int main()
{
HANDLE my_Thread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
if (my_Thread == NULL)
exit(-1);
A_Handle_Of_Resource = CreateMutex(NULL, FALSE, "screen");
//第一个参数我们没有使用,可以设为NULL,第二个参数指定该资源初始是否归属创建它的当前进程,第三个参数指定资源的名称。
CloseHandle(my_Thread);
while (1)
{
WaitForSingleObject(A_Handle_Of_Resource, INFINITE);//申请资源的函数,
//第一个参数指定所申请的资源的句柄,
//第二个参数一般指定为INFINITE,表示如果没有申请到资源就一直等待该资源,
//如果指定为0,表示一旦得不到资源就返回,也可以具体地指定等待多久才返回,单位是千分之一秒
cout << "Boy Look!" << endl;
Sleep(1000);
ReleaseMutex(A_Handle_Of_Resource);//释放一个独占资源
}
return 0;
}

#include <stdio.h>
#include <windows.h>
HANDLE hFile;
CRITICAL_SECTION cs;//定义临界区全局变量
//线程函数:在文件中写入10000个hello
DWORD WINAPI Thread(LPVOID lpParam)
{
int n = (int)lpParam;
DWORD dwWrite;
for (int i = 0;i < 10000;i++)
{
//进入临界区
EnterCriticalSection(&cs);
char data[512] = "hello\r\n";
//写文件
WriteFile(hFile, data, strlen(data), &dwWrite, NULL);
//离开临界区
LeaveCriticalSection(&cs);
}
printf("Thread #%d returned successfully\n", n);
return 0;
}
int main()
{
char *filename = "hack.txt";
WCHAR name[20] = { 0 };
MultiByteToWideChar(CP_ACP, 0, filename, strlen(filename) + 1, name, sizeof(name) / sizeof(name[0]));
//创建文件
hFile = CreateFile(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile error.\n");
return 0;
}
DWORD ThreadID;
HANDLE hThread[5];
//初始化临界区
InitializeCriticalSection(&cs);
for (int i = 0;i < 5;i++)
{
//创建线程,并调用Thread写文件
hThread[i] = CreateThread(NULL, 0, Thread, (LPVOID)(i + 1), 0, &ThreadID);
printf("Thread #%d has been created successfully.\n", i + 1);
}


//等待所有进程结束
WaitForMultipleObjects(5, hThread, TRUE, INFINITE);
//删除临界区
DeleteCriticalSection(&cs);
//关闭文件句柄
CloseHandle(hFile);
return 0;
}

CallNextHookEx

LRESULT CallNextHookEx(
HHOOK hhk, //该参数被忽略。
int nCode,
WPARAM wParam,
LPARAM lParam
);

ncode

传递给当前钩子过程的钩子代码。下一个钩子过程使用此代码来确定如何处理钩子信息。??

类型:WPARAM

传递给当前挂钩过程的*wParam值。*该参数的含义取决于与当前钩子链关联的钩子类型 ??

类型:LPARAM

传递给当前挂钩过程的*lParam值。*该参数的含义取决于与当前钩子链关联的钩子类型。??

CreateRemoteThread 创建远程线程

它能够创建一个在其它进程地址空间中运行的线程(也称:创建远程线程)

​​https://www.cnblogs.com/inva/p/4971331.html​​

​​https://www.jianshu.com/p/204692ab86f9​​

​​https://bbs.pediy.com/thread-253918.htm​​

HANDLE CreateRemoteThread(
[in] HANDLE hProcess, //线程所属进程的进程句柄.
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程的安全属性.
[in] SIZE_T dwStackSize, //线程堆栈大小,一般设置为0,表示使用默认的大小
[in] LPTHREAD_START_ROUTINE lpStartAddress, //线程函数的地址
[in] LPVOID lpParameter, //线程参数指针
[in] DWORD dwCreationFlags, //线程的创建标志
[out] LPDWORD lpThreadId //输出参数,记录创建的远程线程的ID,如果创建失败,该参数为NULL.
);

eg:

hThread = CreateRemoteThread(Target_Process, NULL, 0,pThreadProc, p_RemoteBuf_D, 0, NULL);
在Target_Process的进程创建一个线程pThreadProc,这个线程其实是函数的地址,函数的参数是p_RemoteBuf_D,是我们之前分配的空间的指针,空间已经被我们初始化为了为了参数

关于参数dwCreationFlags

[in] dwCreationFlags

控制线程创建的标志。

价值

意义

0

线程在创建后立即运行。

CREATE_SUSPENDED0x00000004

线程在挂起状态下创建,直到 ​​调用 ResumeThread​​函数才运行。

STACK_SIZE_PARAM_IS_A_RESERVATION0x00010000

dwStackSize参数指定堆栈 的初始保留大小。如果未指定此标志,则dwStackSize指定提交大小。

[out] lpThreadId

指向接收线程标识符的变量的指针。

如果此参数为NULL,则不返回线程标识符。

返回值

如果函数成功,则返回值是新线程的句柄。

如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用 ​​GetLastError​​。

请注意, 即使lpStartAddress指向数据、代码或不可访问, **CreateRemoteThread也可能成功。**如果线程运行时起始地址无效,则发生异常,线程终止。由于起始地址无效而导致的线程终止被视为线程进程的错误退出。此行为类似于 ​​CreateProcess​​的异步性质,即使进程引用无效或丢失的动态链接库 (DLL),也会创建该进程。

CreateToolhelp32Snapshot 获取进程信息

函数通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照.

说到底,可以获取系统中正在运行的进程信息,线程信息

HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags, //用来指定“快照”中需要返回的对象
[in] DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0
);

返回值

如果函数成功,它将返回指定快照的打开句柄。

如果函数失败,则返回INVALID_HANDLE_VALUE。要获取扩展的错误信息,请调用 ​​GetLastError​​。

//#include "StdAfx.h"
#include "windows.h"
#include "tlhelp32.h"
#include "stdio.h"
int main(int argc, char* argv[])
{
PROCESSENTRY32 Photo;
/*

typedef struct tagPROCESSENTRY32W
{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID; // this process
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID; // associated exe
DWORD cntThreads;
DWORD th32ParentProcessID; // this process's parent process
LONG pcPriClassBase; // Base priority of process's threads
DWORD dwFlags;
WCHAR szExeFile[MAX_PATH]; // Path
} PROCESSENTRY32W;


*/
//在使用这个结构前,先设置它的大小
Photo.dwSize = sizeof(Photo);
//给系统内所有的进程拍个快照
HANDLE Photo_Handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Photo_Handle == INVALID_HANDLE_VALUE)
{
printf("CreateToolhelp32Snapshot 调用失败.\n");
return -1;
}
//遍历进程快照,轮流显示每个进程的信息
BOOL Button = ::Process32First(Photo_Handle, &Photo);
while (Button)
{
printf("进程名称:%ls\n", Photo.szExeFile); //这里得到的应该是宽字符,用%ls,不然无法正常打印
printf("进程ID:%#x\n\n", Photo.th32ProcessID);
Button = ::Process32Next(Photo_Handle, &Photo);
}
//不要忘记清除掉snapshot对象
::CloseHandle(Photo_Handle);
return 0;
}

另外一个实例代码

BOOL EjectDll(DWORD Exe_PID, LPCTSTR Dll_Name)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE Target_Snapshot, QQ_Process, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE A_DLL_Export_Func_ThreadProc;

// Exe_Pid = notepad
// TH32CS_SNAPMODULE
Target_Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, Exe_PID);//指定查看QQ的线程??/

bMore = Module32First(Target_Snapshot, &me); //Process32First,现在是MOudle,查看一个Procsee
//的Modue
for (; bMore; bMore = Module32Next(Target_Snapshot, &me))
{
//路径或者直接软件的名字
//szModule 是dll在内存中加载的地址
if (!_tcsicmp((LPCTSTR)me.szModule, Dll_Name) ||
!_tcsicmp((LPCTSTR)me.szExePath, Dll_Name))
{
bFound = TRUE;
break;
}
}

if (!bFound)//关闭刚才打开的句柄
{
CloseHandle(Target_Snapshot);
return FALSE;
}

if (!(QQ_Process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Exe_PID)))//打开QQ
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", Exe_PID, GetLastError());
return FALSE;
}

hModule=GetModuleHandle(L"kernel32.dll");//获取的dll的ImageBase,
if (hModule == NULL)
exit(-1);
A_DLL_Export_Func_ThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");//导出函数的地址
if (A_DLL_Export_Func_ThreadProc == NULL)//这里获取的地址不是QQ的dll->freelibary,而是ejectdll.exe中dll的freelibart地址
exit(-1);//看上去有点矛盾,但是所有exe调用dll的在进程中的地址都是一样的,这个后面慢慢理解
hThread = CreateRemoteThread(QQ_Process, NULL, 0, //现在的me指向了QQ
A_DLL_Export_Func_ThreadProc, me.modBaseAddr, //mw.modBaseaddr要卸载的dll加载地址
0, NULL);//打开远程线程
if (hThread == NULL)
exit(-1);
WaitForSingleObject(hThread, INFINITE);//一直等待线程有信号,有就返回

CloseHandle(hThread);//关闭线程
CloseHandle(QQ_Process);//关闭进程
CloseHandle(Target_Snapshot);//关闭快照

return TRUE;
}

CallNextHookEx

正确的消息处理流程应该如下

物理击键

钩子管理函数←→钩子A←→钩子B←→钩子C←→钩子D

Window消息处理函数

在钩子A函数中,如果调用CallNextHookEx函数,则会将按键消息传给钩子B;

如果不调用CallNextHookEx函数,则钩子B不会得到按键消息,换句话说,钩子B失效了,当然此时的钩子C和钩子D也失效了。为了钩子间和平相处,还是应该在钩子函数里添加CallNextHookEx函数的调用。

钩子函数的返回值的问题。在上面的事例中,钩子A的返回值决定按键消息是否丢弃。

返回值0,告诉系统,消息继续传递给Window消息处理函数;

返回值1(非0),告诉系统,消息将丢弃,Window消息处理函数得不到按键的消息。

ContinueDebugEvent

使调试器能够继续先前报告调试事件的线程。

BOOL ContinueDebugEvent(
[in] DWORD dwProcessId,
[in] DWORD dwThreadId,
[in] DWORD dwContinueStatus
);

DLLmain

DOS 程序的入口函数是 main()

Win32 程序的入口函数是 WinMain()

DLL 程序的入口函数是 DllMain()

动态链接库的​​可选​​入口点 (DLL) 。 DLL 使用 LoadLibrary和 FreeLibrary 函数加载或卸载 DLL 时,系统会调用该函数的入口点函数。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的

一些例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。

如果DLL被多次LoadLibrary,那么DllMain只执行一次,引用基数+1。

​​https://www.cctry.com/thread-298586-1-1.html​​

BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // 指向自身的句柄 只有在特定的进程内部有效
DWORD fdwReason, // 调用原因
LPVOID lpvReserved // 隐式加载和显式加载 表示一个保留参数,目前已经很少使用
);

参数一

DLL 模块的句柄。 该值是 DLL 的基址。

DLL 的 HINSTANCE 与 DLL 的 HMODULE 相同,因此可以在调用需要模块句柄的函数时使用 hinstDLL

参数二

其中fdwReason用来表示Dll被调用的状态,一共分为四种:

  • DLL_PROCESS_ATTACH ==1 被进程加载
  • DLL_PROCESS_DETACH ==0 被进程释放
  • DLL_THREAD_ATTACH ==2 被线程加载
  • DLL_THREAD_DETACH ==3 被线程释放

上面的宏定义参数对应了一些case

DllMain函数在以下几种情况被调用:

进程映射

DLL_PROCESS_ATTACH

大家都知道,一个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的​​地址空间​​​。要把一个DLL文件映射到进程的地址空间,有两种方法:​​静态链接​​​和​​动态链接​​的LoadLibrary或者LoadLibraryEx。

当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH,这种调用只会发生在第一次映射时。如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者LoadLibraryEx,操作系统只会增加DLL的使用次数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。不同进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

可参考DllMainTest的DLL_PROCESS_ATTACH_Test函数。

进程卸载

DLL_PROCESS_DETACH

当DLL被从进程的​​地址空间​​解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作。

那么什么时候DLL被从进程的地址空间解除映射呢?两种情况:

◆FreeLibrary解除DLL映射(有几个LoadLibrary,就要有几个FreeLibrary)

◆进程结束而解除DLL映射,在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。(如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)

注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回​​FALSE​​,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保清理那些没有成功初始化的东西。

可参考DllMainTest的DLL_PROCESS_DETACH_Test函数。

线程映射

DLL_THREAD_ATTACH

当进程创建一​​线程​​​时,系统查看当前映射到进程​​地址空间​​中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。

新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许进程开始执行它的线程函数。

注意跟DLL_PROCESS_ATTACH的区别,我们在前面说过,第n(n>=2)次以后地把DLL​​映像文件​​​映射到进程的地址空间时,是不再用DLL_PROCESS_ATTACH调用DllMain的。而DLL_THREAD_ATTACH不同,进程中的每次建立​​线程​​,都会用值DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。

线程卸载

DLL_THREAD_DETACH

如果​​线程​​​调用了​​ExitThread​​来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。

注意:如果线程的结束是因为系统中的一个线程调用了​​TerminateThread​​,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。

DLL加载和应用程序退出的使用都会调用该函数(DllMain)的哦,是应用程序一上来就调用的,不是用到该函数时才调用的

__declspec

应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数。为了导出一些函数,需要在函数前面添加标识符 _declspec(dllexport)。

__declspec(dllexport) 导入 用于Windows中的动态库中,声明导出函数、类、对象等供外面调用,省略给出.def文件。即将函数、类等声明为导出函数,供其它程序调用,作为动态库的对外接口函数、类等。

__declspec(dllimport) 导出 用于Windows中,从别的动态库中声明导入函数、类、对象等供本动态库或exe文件使用。当你需要使用DLL中的函数时,往往不需要显示地导入函数,编译器可自动完成。

EnumWindows 枚举进程PID,和DIY回调函数一起使用

该函数枚举所有屏幕上的顶层窗口,并将​​窗口句柄​​​传送给应用程序定义的​​回调函数​​。

回调函数返回FALSE将停止枚举,否则EnumWindows函数继续到所有顶层窗口枚举完为止。

BOOL EnumWindows(
[in] WNDENUMPROC lpEnumFunc, //指向一个应用程序定义的回调函数指针
[in] LPARAM lParam //指定一个传递给回调函数的应用程序定义值
);

[in] lpEnumFunc

类型:WNDENUMPROC

指向应用程序定义的回调函数的指针。有关详细信息,请参阅​​EnumWindowsProc​​。

[in] lParam

类型:LPARAM

要传递给回调函数的应用程序定义的值

DebugActiveProcess

使调试器附加到一个活动进程并且调试它。

BOOL DebugActiveProcess(
[in] DWORD dwProcessId
);

返回

如果函数成功,则返回值非零。

如果函数失败,则返回值为 0(零)。要获取扩展的错误信息,请调用 ​​GetLastError​​

EnumProcesses

BOOL EnumProcesses(
[out] DWORD *lpidProcess, //是保存进程ID的数组
[in] DWORD cb, //进程组数的大小。
[out] LPDWORD lpcbNeeded //返回进程数组的大小。
);

例子

//#include "stdafx.h"
#include <Windows.h>
#include <string>
#include <Psapi.h>
#include <tchar.h>
#pragma comment (lib, "Psapi.lib")

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwProcessID[0x500] = { 0 }; //开始的预先分配较大的缓冲区,用来存放进程ID
DWORD dwNeeded = 0;
BOOL bEnumRes = EnumProcesses(dwProcessID, sizeof(dwProcessID), &dwNeeded);
UINT uCount = dwNeeded / sizeof(DWORD);//获得枚举到进程的数量
for (UINT i = 0; i < uCount; i++)
{

//只对进程进程枚举,所以申请QUERY权限,具体还得根据应用申请权限

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessID[i]);
if (NULL != hProcess)
{
CHAR szProcessName[0x50] = { 0 };
DWORD dwNameLen = 0x50;
BOOL bRet = QueryFullProcessImageNameA(hProcess, 0, szProcessName, &dwNameLen);
if (bRet)
{
printf("ID:%4d\tprocessName(%s)\n", dwProcessID[i], szProcessName);
}
}
}
getchar();
return 0;
}

//#include "stdafx.h"
#include <windows.h>
#include "psapi.h"
#include "tchar.h"
#pragma comment (lib, "psapi.lib ")


void MyEnumProcess()
{
// Get the list of process identifiers.
DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i;

if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) //枚举进程
return;
cProcesses = cbNeeded / sizeof(DWORD); //计算进程个数
for (i = 0; i < cProcesses; i++)
if (aProcesses[i] != 0)
{

TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, aProcesses[i]); //获得进程句柄

if (NULL != hProcess)
{
HMODULE hMod;
DWORD cbNeeded;

if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) //枚举进程模块信息
{
GetModuleBaseName(hProcess, hMod, szProcessName, sizeof(szProcessName) / sizeof(TCHAR)); //取得主模块全名,每个进程第一模块则为进程主模块
}
}
_tprintf(TEXT("%s (PID: %u)\n"), szProcessName, aProcesses[i]); //输出进程名及PID
CloseHandle(hProcess);
}
}


void main()
{
MyEnumProcess();
system("pause");
}

ExitProcess()

不如C语言的exit好,它存在内存的泄露

fopen_s / _wfopen_s

errno_t fopen_s(
FILE** pFile,
const char *filename,
const char *mode
);
errno_t _wfopen_s(
FILE** pFile,
const wchar_t *filename,
const wchar_t *mode
);

pFile

filename

mode

返回值

如果成功,则为零;如果失败,则为错误代码。

TerminateProcess

终止指定进程及其所有线程。

BOOL TerminateProcess(
[in] HANDLE hProcess,
[in] UINT uExitCode
);

[in] hProcess

要终止的进程的句柄。

句柄必须具有PROCESS_TERMINATE访问权限。

[in] uExitCode

由于此调用,进程和线程要使用的退出代码终止。

使用 GetExitCodeProcess函数检索进程的退出值。使用 GetExitCodeThread函数检索线程的退出值。

如果函数成功,则返回值非零。

如果函数失败,则返回值为零

终止进程具有以下结果:

  • 进程中的任何剩余线程都标记为终止。
  • 释放进程分配的任何资源。
  • 所有内核对象均已关闭。
  • 进程代码已从内存中删除。
  • 设置了进程退出代码。
  • 指示进程对象。

SetWindowsHookExW - 钩取流程

HHOOK SetWindowsHookExW(
int idHook, //处理的类型?键盘类型
HOOKPROC lpfn, //自定义的KeyboardProc函数
HINSTANCE hmod, //是装载dll还是卸载dll
DWORD dwThreadId //是钩取某个线程还是hook所有的线程
);

  1. int hook——指定事件的钩子ID,如键盘事件WH_KEYBOARD。设置后只对键盘输入起反应。
  2. HOOKPROC lpfn——钩子的处理函数,若设置的是键盘输入钩子,必须是微软定义的一个叫KeyboardProc的函数。
  3. HINSTANCE hmod——模块句柄,因此一般设置钩子的地方在DLL中。
  4. DWORD dwThreadId——需要设置钩子的线程ID,倘若为0则为全局钩子(所有程序都钩)。

KeyboardProc

LRESULT CALLBACK KeyboardProc
{
int code,
WPARAM wParam,
LPARAM lParam
}

nCode: 根据这个数值决定怎样处理消息

如果 code 小于0,则 必须让KeyboardProc()函数返回CallNextHookEx()

code可以是下列值:

HC_ACTION:wParam和lParam包含按键消息

​ HC_NOREMOVE:wParam和lParam包含按键消息,并且按键消息不能从​​消息队列​​​中移除(一个被PeekMessage​​函数调用​​的请求,指定 PM_NOREMOVE标志)

wParam: 按键的虚拟键值消息,例如:VK_F1

lParam: 根据不同的位数具有多种不同的含义

LRESULT是一个数据类型,指的是从窗口程序或者回调函数返回的32位值。

CALLBACK是由用户设计却由windows系统呼叫的函数,统称为callback函数。某些API函数要求以callback作为你参数之一。

GetExitCodeProcess

BOOL GetExitCodeProcess(
[in] HANDLE hProcess, //进程句柄
[out] LPDWORD lpExitCode //进程句柄
);

GetCurrentThreadID

GetThreadContext

获取线程的环境

BOOL GetThreadContext(
[in] HANDLE hThread, //要检索其环境的线程的句柄,
//句柄还必须具有THREAD_QUERY_INFORMATION访问权限
[in, out] LPCONTEXT lpContext //向接收指定线程的适当上下文的CONTEXT
//此结构的ContextFlags成员的值指定检索线程上下文的哪些部分
);

GetModuleHandleA

检索指定模块的模块句柄。该模块必须已由调用进程加载。

HMODULE GetModuleHandleA(
[in, optional] LPCSTR lpModuleName
);

参数

[in, optional] lpModuleName //已加载模块的名称(.dll 或 .exe 文件

如果此参数为 NULL, GetModuleHandle返回用于创建调用进程的文件(.exe 文件)的句柄。当前句柄...

返回值

如果函数成功,则返回值是指定模块的句柄。

如果函数失败,则返回值为 NULL

GetModuleFileNameA

检获取当前进程已加载模块的文件的完整​​路径​​,该模块必须由当前进程加载。

DWORD GetModuleFileNameA(
[in, optional] HMODULE hModule, //装载一个程序实例的句柄。如果该参数为NULL,该函数返回该当前应用程序全路径。
[out] LPSTR lpFilename, //是你存放返回的名字的内存块的指针,是一个输出参数
[in] DWORD nSize //lpFilename缓冲区的大小,以TCHARs为单位。
);

返回值

如果函数运行成功,返回值为字符串的长度。包含了空字符

如果字符串的长度大于nSize字节,返回值为nSize。包含了空字符

如果函数运行失败,返回值为0

#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
char Exe_Path[_MAX_PATH] = { 0, };
GetModuleFileNameA(NULL, Exe_Path, MAX_PATH);
cout << Exe_Path << endl;
return 0;
}

最后他会输出当前cpp代码完整的exe路径

GlobalAlloc 全局堆分配

该函数从堆中分配一定数目的字节数

DECLSPEC_ALLOCATOR HGLOBAL GlobalAlloc(
[in] UINT uFlags, // 分配属性(方式)
[in] SIZE_T dwBytes // 分配的字节数
);

返回值

若函数调用成功,则返回一个新分配的内存对象的​​句柄​​。

若函数调用失败,则返回​​NULL​​​。可调用​​GetLastError​​以获得更多错误信息。

若​​函数调用​​成功,将至少分配所需内存.若实际分配量超过所需,则内存仍然能够充分利用之.可用函数 GlobalSize 来确定实际所分配的字节数。

可使用 GlobalFree 来释放内存。

Globallock

锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处。

LPVOID GlobalLock(
[in] HGLOBAL hMem
);

除非用 GlobalUnlock 函数将内存块解锁,否则地址会一直保持有效

参数

[in] hMem

全局内存对象的句柄。此句柄由 ​​GlobalAlloc​​​或 ​​GlobalReAlloc​​函数返回

返回值

如果函数成功,则返回值是指向内存块第一个字节的指针。

如果函数失败,则返回值为NULL

GetProcAddress 获取dll导出函数的地址

从指定的动态链接库 (DLL) 中检索​​导出函数或变量​​的地址。

FARPROC GetProcAddress(
[in] HMODULE hModule, // DLL模块句柄
[in] LPCSTR lpProcName // 函数名
);

FARPROC GetProcAddress(HMODULE hModule,LPCSTR  lpProcName); //gf
FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName); //DIY

参数1

包含函数或变量的 DLL 模块的句柄。

LoadLibrary、 LoadLibraryEx、LoadPackagedLibrary或 GetModuleHandle函数返回此句柄。

参数2

函数或变量名,或函数的序数值。如果该参数为序数值,则必须在低位字中;高位字必须为零

返回值

如果函数成功,则返回值是导出的函数或变量的地址。

如果函数失败,则返回值为 NULL。

操作原理

导出表的数据在PE文件的data区域

先利用AddressOfName寻找你要导出的函数名字

利用AddressOfNameOrdinl 转达Ordinal数组,寻找 func_name_index 对应的 ordinal

然后利用AddressOfFunction成员转到EAT,导出表,addressoffunction[ordianl]

GetSystemDirectory

UINT GetSystemDirectoryA(
[out] LPSTR lpBuffer,//用于装载系统目录路径名的一个字串缓冲区
[in] UINT uSize//字串的最大长度
);

InternetOpenA

初始化应用程序对 WinINet 函数的使用

该函数是第一个由应用程序调用的 WinINet 函数。它告诉 Internet DLL 初始化内部数据结构并准备接收应用程序之后的其他调用。当应用程序结束使用 Internet 函数时,应调用 InternetCloseHandle 函数来释放与之相关的资源。

HINTERNET InternetOpenA(
[in] LPCSTR lpszAgent, //exe调用WinINet函数的应用程序或入口
[in] DWORD dwAccessType, //指定访问类型
[in] LPCSTR lpszProxy, //指定一个可选的主机名列表或IP地址
[in] LPCSTR lpszProxyBypass,
[in] DWORD dwFlags
);

返回值

成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinINet函数。

失败:返回NULL。

InternetOpenUrl

打开由完整的 FTP 或 HTTP URL 指定的资源

HINTERNET InternetOpenUrlA(
[in] HINTERNET hInternet, //会话句柄,由InternetOpen 调用返回
[in] LPCSTR lpszUrl, //指定读取的网址
[in] LPCSTR lpszHeaders, //指定发送到HTTP服务器的头信息
[in] DWORD dwHeadersLength,
[in] DWORD dwFlags,
[in] DWORD_PTR dwContext
);

返回值

如果连接成功建立,则返回 URL 的有效句柄;如果连接失败,则返回**NULL 。**要检索特定错误消息,请调用 ​​GetLastError​​​。要确定拒绝访问服务的原因,请调用 ​​InternetGetLastResponseInfo​​。

InternetReadFile 打开的句柄中读取数据。

打开的句柄中读取数据。

BOOL InternetReadFile(
[in] HINTERNET hFile,
[out] LPVOID lpBuffer, //缓冲器指针
[in] DWORD dwNumberOfBytesToRead, ///欲读数据的字节量
[out] LPDWORD lpdwNumberOfBytesRead //接收读取字节量的变量s
);

[in] hFile

从先前调用 ​​InternetOpenUrl​​​、 ​​FtpOpenFile​​​或 ​​HttpOpenRequest​​返回的句柄。

[out] lpBuffer

指向接收数据的缓冲区的指针。

[in] dwNumberOfBytesToRead

要读取的字节数。

[out] lpdwNumberOfBytesRead

指向接收读取字节数的变量的指针。 InternetReadFile在进行任何工作或错误检查之前将此值设置为零。

返回值

如果成功则返回TRUE ,否则返回****FALSE。要获取扩展的错误信息,请调用 ​​GetLastError​​。

LoadLibraryA

DLL 装载

HMODULE LoadLibraryA( LPCSTR lpLibFileName ); //gf
HMODULE LoadLibraryA( 字符串:要加载的DLL或者exe); //DIY

如果参数给出的是完整的路径,那么优先选择它,指定路径时,请务必使用反斜杠 ( \ ),而不是正斜杠 (/)。有关路径的更多信息,

如果字符串指定了没有路径的模块名称并且省略了文件扩展名,则该函数将默认库扩展名“.DLL”附加到模块名称。

要防止函数将“.DLL”附加到模块名称,请在模块名称字符串中包含一个尾随点字符 (.)

返回值

如果函数成功,则返回值是模块的句柄。

如果函数失败,则返回值为 NULL

LookupPrivilegeValueA

函数查看系统权限的特权值,返回信息到一个LUID结构体里

BOOL LookupPrivilegeValueA(
[in, optional] LPCSTR lpSystemName, //表示所要查看的系统,本地系统直接用NULL
[in] LPCSTR lpName, //指定特权的名称
[out] PLUID lpLuid //接收所返回的制定特权名称的信息接收所返回的制定特权名称的信息
);

一个指向变量的指针,该变量接收由lpSystemName参数指定的系统上的权限已知的 LUID。

typedef struct _LUID {
DWORD LowPart;
LONG HighPart;
} LUID, *PLUID;

返回值

函数调用成功后,信息存入第三个类型为LUID的结构体中,并且函数返回非0

lstrcat

字符串衔接

LPSTR lstrcat(
[in, out] LPSTR lpString1, //S
[in] LPCSTR lpString2 //D
);

[in, out] lpString1

类型:LPTSTR

第一个以 null 结尾的字符串。此缓冲区必须足够大以包含两个字符串。

[in] lpString2

类型:LPTSTR

要附加到lpString1参数中指定的字符串的空终止字符串。

返回值

如果函数成功,则返回值是指向缓冲区的指针。

如果函数失败,则返回值为NULL ,并且lpString1可能不是以 null 结尾的。

UnhookWindowsHookEx

通过UnhookWindowsHookEx函数删除安装在挂钩链中的挂钩过程

BOOL UnhookWindowsHookEx(
HHOOK hhk
);

类型:HHOOK

要移除的挂钩的handle。此参数是通过先前调用SetWindowsHookEx获得的挂钩句柄。

返回值

如果函数成功,则返回值非零。

如果函数失败,则返回值为零。

OutputDebugString

类似于printf,将字符串发送到调试器进行显示。

OpenProcess

位置:Kernel32.dll

OpenProces 打开现有的本地进程对象。 ,并返回进程的句柄。

HANDLE OpenProcess(
[in] DWORD dwDesiredAccess, //渴望得到的访问权限
[in] BOOL bInheritHandle, //表示所得到的进程句柄是否可以被继承
[in] DWORD dwProcessId //被打开进程的PID
);

//参数 访问权限,是否可继承,访问进程的PID //效果 打开某个exe,然后根据访问权限去干坏事 //返回值 成功就返回进程的PID 失败就返回NULL

返回值

如成功,返回值为指定进程的句柄。

如失败,返回值为NULL,可调用GetLastError()获得错误代码。

OpenProcessToken 以某种访问权限打开一个进程

用来打开与进程相关联的访问令牌

OpenProcessToken函数用来打开与进程相关联的访问令牌

​得到指定进程的访问令牌,而第三个参数定义设置不正确可能导致该函数调用失败​

要对一个任意进程(包括系统安全进程和服务进程)进行指定了写相关的访问权的OpenProcess操作,只要当前进程具有​​SeDeDebug​​权限就可以了。要是一个用户是Administrator或是被给予了相应的权限,就可以具有该权限。

可是,就算我们用Administrator帐号对一个系统安全进程执行OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessID)还是会遇到“访问拒绝”的错误。什么原因呢?原来在​​默认的情况下进程的一些访问权限是没有被启用(Enabled)​​的,所以我们要做的首先是启用这些权限。

《WINDOWS核心编程》!

​​https://blog.csdn.net/stonesharp/article/details/7709674​​

BOOL OpenProcessToken(
[in] HANDLE ProcessHandle,//要修改访问权限的进程句柄
[in] DWORD DesiredAccess, //你要进行的操作类型
[out] PHANDLE TokenHandle //返回的访问令牌指针
);

如果函数成功,则返回值非零。

如果函数失败,则返回值为零

TOKEN_PRIVILEGES

typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount; ///下一个数组元素的个数
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; //数组.类型为LUID_AND_ATTRIBUTES
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

PostMessageA 传递消息

BOOL PostMessageA(
[in, optional] HWND hWnd,
[in] UINT Msg, //指定被寄送的消息。
[in] WPARAM wParam, //指定附加的消息特定的信息
[in] LPARAM lParam //指定附加的消息特定的信息。
);

[in, optional] hWnd

类型:HWND

一个窗口句柄,其窗口过程将接收消息。以下值具有特殊含义。

价值

意义

HWND_BROADCAST((HWND)0xffff)

该消息被发布到系统中的所有顶级窗口,包括禁用或不可见的无主窗口、重叠窗口和弹出窗口。该消息不会发布到子窗口。

空值

该函数的行为类似于对​​PostThreadMessage​​的调用,其中dwThreadId参数设置为当前线程的标识符。

从 Windows Vista 开始,消息发布受 UIPI 约束。进程的线程只能将消息发布到完整性级别较低或相等的进程中的线程的消息队列中。

[in] Msg

类型:UINT

要发布的消息。

有关系统提供的消息的列表,请参阅​​系统定义的消息​​。

[in] wParam

类型:WPARAM

其他特定于消息的信息。

[in] lParam

类型:LPARAM

其他特定于消息的信息。

rundll32 进程上的隐藏。后续再了解

Rundll32.exe是什么?顾名思意,”执行32位的DLL文件”。它的作用是执行DLL文件中的内部函数,这样在进程当中,只会有Rundll32.exe,而不会有DLL后门的进程,这样,就实现了进程上的隐藏。

经过如上解释,可以总结为,rundll32能够运行一个32位的dll文件,并且在进程列表中只能看到rundll32.exe,但是遍历rundll32.exe的模块列表可以看到进程加载的dll。

并不是所有的dll都能够被rundll32运行。 为何这么说,因为rundll32只支持特定的函数声明方式,并且该函数必须在dll文件的导出表中

VOID CALLBACK rundll_func (HWND hwnd,HINSTANCE hinst,LPTSTR lpCmdLine,INT nCmdShow);

SetEvent

将指定的事件对象设置为信号状态。

BOOL SetEvent(
[in] HANDLE hEvent
);

参数

[in] hEvent

事件对象的句柄。​​CreateEvent​​​或 OpenEvent ​​函数​​返回此句柄。

句柄必须具有 EVENT_MODIFY_STATE 访问权限。有关详细信息,请参阅 ​​同步对象安全性和访问权限​​。

返回值

如果函数成功,则返回值非零。

如果函数失败,则返回值为零。要获取扩展的错误信息,请调用 ​​GetLastError​​。

_SetWindowText

更改指定窗口标题栏的文本(如果有的话)。如果指定的窗口是控件,则更改控件的文本。但是,SetWindowText不能更改另一个应用程序中控件的文本。

BOOL SetWindowTextA(
[in] HWND hWnd, //改变文本内容的窗口或控件的句柄
[in, optional] LPCSTR lpString //指向一个空结束的字符串的指针
);

SetThreadContext

设置指定线程的环境

BOOL SetThreadContext(
[in] HANDLE hThread, //要设置其环境的线程的句柄。
//句柄必须具有线程的 THREAD_SET_CONTEXT访问权限。有关详细信息
[in] const CONTEXT *lpContext //指向包含要在指定线程中设置的上下文的CONTEXT结构的指针
//此结构的ContextFlags成员的值指定要设置线程上下文的哪些部分
);

如果设置了上下文,则返回值非零。

如果函数失败,则返回值为零。要获取扩展的错误信息

WriteProcessMemory

将数据写入指定进程中的内存区域。要写入的整个区域必须可访问,否则操作将失败。

BOOL WriteProcessMemory(
[in] HANDLE hProcess, //由OpenProcess返回的进程句柄。
[in] LPVOID lpBaseAddress, //指向要写入数据的指定进程中的基地址的指针 ,进程的基地址 D
   //再写入之前,此函数将先检查目标地址是否可用,并能容纳待写入的数据。
[in] LPCVOID lpBuffer, //指向要写的数据来源的指针。 S
[in] SIZE_T nSize, //要写入的字节数。
[out] SIZE_T *lpNumberOfBytesWritten
);

返回值

非零值代表成功。

他是一个复合的API

细节流程如下

  1. 首先调用 NtQueryVirtualMemory 查询内存信息
  2. 如果原内存属性可写,直接调用 NtWriteVirtualMemory 写入内存
  3. 如果原内存属性不可执行,则还原内存属性,然后返回 STATUS_ACCESS_VIOLATION
  4. 如果内存类型为 MEM_IMAGE,则修改内存属性为 OldAccessProtection | PAGE_ENCLAVE_UNVALIDATED | PAGE_EXECUTE_WRITECOPY
  5. 如果内存类型为 MEM_PRIVATE,则修改内存属性为 OldAccessProtection | OldAccessProtection | PAGE_EXECUTE_READWRITE
  6. 调用NtWriteVirtualMemory写入内存
  7. 还原内存属性

ShellExecute -Dev环境

对指定文件执行操作。

HINSTANCE ShellExecuteA(
[in, optional] HWND hwnd,
[in, optional] LPCSTR lpOperation,
[in] LPCSTR lpFile,
[in, optional] LPCSTR lpParameters,
[in, optional] LPCSTR lpDirectory,
[in] INT nShowCmd
);

参数2

这个参数可以是​​"edit"​​​ ​​"explore"​​​ ​​"open"​​​ ​​"print"​​​ ​​"runas"​​​ ​​NULL​​,分别可以打开编辑器、在文件资源管理器中查看、打开、屏幕输出和 以管理员身份运行

最后一个参数

SW_HIDE 隐藏窗口,活动状态给令一个窗口 

SW_MINIMIZE 最小化窗口,活动状态给令一个窗口

SW_RESTORE 用原来的大小和位置显示一个窗口,同时令其进入活动状态

SW_SHOW 用当前的大小和位置显示一个窗口,同时令其进入活动状态

SW_SHOWMAXIMIZED 最大化窗口,并将其激活

SW_SHOWMINIMIZED 最小化窗口,并将其激活

SW_SHOWMINNOACTIVE 最小化一个窗口,同时不改变活动窗口

SW_SHOWNA 用当前的大小和位置显示一个窗口,不改变活动窗口

SW_SHOWNOACTIVATE 用最近的大小和位置显示一个窗口,同时不改变活动窗口

SW_SHOWNORMAL 与SW_RESTORE相同

返回值

返回值大于32表示执行成功
返回值小于32表示执行错误
返回值可能的错误有: = 0 {内存不足}
ERROR_FILE_NOT_FOUND = 2; {文件名错误}
ERROR_PATH_NOT_FOUND = 3; {路径名错误}
ERROR_BAD_FORMAT = 11; {EXE 文件无效}
SE_ERR_SHARE = 26; {发生共享错误}
SE_ERR_ASSOCINCOMPLETE = 27; {文件名不完全或无效}
SE_ERR_DDETIMEOUT = 28; {超时}
SE_ERR_DDEFAIL = 29; {DDE 事务失败}
SE_ERR_DDEBUSY = 30; {正在处理其他 DDE 事务而不能完成该 DDE 事务}
SE_ERR_NOASSOC = 31; {没有相关联的应用程序}

开始一个新的应用程序

ShellExecute(NULL, "open", "notepad.exe", NULL, NULL, SW_SHOWNORMAL); 

打开记事本,并打开一个文件(系统能识别记事本应用程序的路径,因此我们不必使用绝对路径)

res=ShellExecute(NULL, "open", "notepad.exe", "C:\\Users\\0x9D\\Desktop\\Test\\Work_Now.cpp", NULL, SW_SHOWNORMAL);

打印一个文档

ShellExecute(Handle, 'print', PChar('c:/test/test.doc'), nil, nil, SW_SHOW); 

注意:可能你会看到word暂时的被打开,但它会自动关闭。

打开一个HTML页面

ShellExecute(NULL, "open", "http://wus20.com", NULL, NULL, SW_SHOW);
ShellExecute(NULL, "open", "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "bilibili.com", NULL, SW_SHOWNORMAL);

你能通过一个已经注册的文件类型来打开应用程序

  ShellExecute(Handle, 'open', PChar('c:/test/readme.txt'), nil, nil, SW_SHOW); 

用windows Explorer 打开一个目录

 ShellExecute(Handle, 'explore', PChar('c:/windows)', nil, nil, SW_SHOW); 

运行一个DOS命令并立即返回

  ShellExecute(Handle, 'open', PChar('command.com'), PChar('/c copy file1.txt file2.txt'), nil, SW_SHOW); 

运行一个DOS命令并保持DOS窗口存在

res=ShellExecute(NULL, "open", "cmd", "/k dir", NULL, SW_SHOW); 

#include <stdio.h>
#include<windows.h>
#include<tchar.h>

int main(void)
{
SHELLEXECUTEINFO dqx = { sizeof(SHELLEXECUTEINFO) };
dqx.lpVerb = TEXT("runas");
dqx.lpFile = TEXT("cmd.exe");
dqx.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&dqx))
{
DWORD dwStatus = GetLastError();
if (dwStatus == ERROR_CANCELLED)
{
printf("提升权限被用户拒绝\n");
}
else if (dwStatus == ERROR_FILE_NOT_FOUND)
{
printf("所要执行的文件没有找到\n");
}
}
system("pause nul");
return 0;
}

建立一个网页

#include <windows.h>
#include<stdio.h>
#include<stdlib.h>

int main()
{
HINSTANCE ret=0;
ret=ShellExecute(NULL, "open", "http://wus20.com", NULL, NULL, SW_SHOW);
if(ret<(HINSTANCE)32)
exit(-1);
else
return 0;
}

#include <windows.h>
#include<stdio.h>
#include<stdlib.h>

bool IsProcessRunAsAdmin()
{
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup;
BOOL b = AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdministratorsGroup);
if(b)
{
CheckTokenMembership(NULL, AdministratorsGroup, &b);
FreeSid(AdministratorsGroup);
}
return b == TRUE;
}
short GetAdmin(LPCSTR Param, int Showcmd)//一个参数是程序运行时的参数,另一个是启动窗口的状态
{
if (IsProcessRunAsAdmin())
return 0;
TCHAR Path[MAX_PATH];
ZeroMemory(Path, MAX_PATH);
::GetModuleFileName(NULL, Path, MAX_PATH); //获取程序路径
HINSTANCE res;
res = ShellExecute(NULL, "runas", Path, NULL, NULL, Showcmd);
if(res > (HINSTANCE)32)
return 1;
else
return 0;
}

int main()
{
short ret=0;
ret=GetAdmin("calc",SW_SHOW);
if(ret!=1)
{
puts("wrong");
exit(-1);
}
else
{
printf("%d",argv[1]);
return 0;
}

}

/*

*/

ReadProcessMemory

ReadProcessMemory

将​​指定地址范围内的Source数据​​​从指定进程的地址空间​​copy​​​到​​当前进程的指定缓冲区Destation中。​

任何具有 PROCESS_VM_READ 访问句柄的进程都可以调用该函数。

要读取的整个区域必须是可访问的,如果不可访问,则函数失败

BOOL ReadProcessMemory(
[in] HANDLE hProcess, //远程进程句柄。 被读取者
[in] LPCVOID lpBaseAddress, //远程进程中内存地址。 从具体何处读取 S
[out] LPVOID lpBuffer, //本地进程中内存地址. 存放读取数据缓冲区; D
[in] SIZE_T nSize, //一次读取的字节数
[out] SIZE_T *lpNumberOfBytesRead //实际读取的字节数
);

返回值

如果函数成功,则返回值非零。

如果函数失败,则返回值为 0(零)。要获取扩展的错误信息,请调用 ​​GetLastError​​。

如果请求的读取操作进入进程中不可访问的区域,则该函数将失败。

WriteConsole

BOOL WINAPI WriteConsole(
__in HANDLE hConsoleOutput,
__in const VOID* lpBuffer,
__in DWORD nNumberOfCharsToWrite,
__out LPDWORD lpNumberOfCharsWritten,
__reserved LPVOID lpReserved
);

WaitForDebugEvent

等待正在调试的进程中发生调试事件, 获取调试事件

BOOL WaitForDebugEvent(
[out] LPDEBUG_EVENT lpDebugEvent, //接收一个有关调试事件的信息
[in] DWORD dwMilliseconds //等待事件的毫秒数,可以对比
);

等待调试事件的毫秒数。如果此参数为零,则该函数测试调试事件并立即返回。如果参数为 INFINITE,则函数在调试事件发生之前不会返回

返回值

如果函数成功,则返回值非零。

如果函数失败,则返回值为零。要获取扩展的错误信息,请调用 ​​GetLastError​​。

WriteFile

将数据写入指定的文件或输入/输出 (I/O) 设备。

BOOL WriteFile(
[in] HANDLE hFile, //一个文件的句柄
[in] LPCVOID lpBuffer, //指向将写入文件的 数据缓冲区
[in] DWORD nNumberOfBytesToWrite, //要写入数据的字节数量
[out, optional] LPDWORD lpNumberOfBytesWritten, //实际写入文件的字节数
[in, out, optional] LPOVERLAPPED lpOverlapped
);

WideCharToMultiByte

将 UTF-16(宽字符)字符串映射到新字符串。新字符串不一定来自多字节字符集

int WideCharToMultiByte(
[in] UINT CodePage,
[in] DWORD dwFlags,
[in] _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr, //指向将被转换的unicode字符串
[in] int cchWideChar,
//指定由参数lpWideCharStr指向的缓冲区的字符个数。如果这个值为-1,字符串将被设定为以NULL为结束符的字符串,并且自动计算长度
[out, optional] LPSTR lpMultiByteStr, //指向接收被转换字符串的缓冲区
[in] int cbMultiByte,
//指定由参数lpMultiByteStr指向的缓冲区最大值(用字节来计量)。若此值为零,函数返回lpMultiByteStr指向的目标缓冲区所必需的字节数,在这种情况下,lpMultiByteStr参数通常为NULL。
[in, optional] LPCCH lpDefaultChar,
[out, optional] LPBOOL lpUsedDefaultChar
);

VirtualAllocEx() 内存内分配内存

​​https://vimsky.com/examples/detail/cpp-ex-----VirtualAllocEx-function.html​​

​​https://cpp.hotexamples.com/examples/-/-/VirtualAllocEx/cpp-virtualallocex-function-examples.html​​

上面2个网站是关于VirtualAllocEx的运用,里面当然涉及了其它的函数

指定进程的虚拟空间保留或提交内存区域

LPVOID VirtualAllocEx(
[in] HANDLE hProcess, //申请内存所在的进程句柄
[in, optional] LPVOID lpAddress, //保留页面的内存地址;一般用NULL自动分配 。
[in] SIZE_T dwSize, //欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍
[in] DWORD flAllocationType, //内存分配的类型
[in] DWORD flProtect //页面区域的内存保护
);

返回值

如果函数成功,则返回值是分配的页面区域的基地址。

如果函数失败,则返回值为NULL。要获取扩展的错误信息,

WaitForSingleObject

**1.**等待直到指定的对象处于信号状态或超时间隔过去。

DWORD WaitForSingleObject(
[in] HANDLE hHandle,
[in] DWORD dwMilliseconds
);

WaitForSingleObject(A_Handle_Of_Resource, INFINITE);//申请资源的函数,
//第一个参数指定所申请的资源的句柄,
//第二个参数一般指定为INFINITE,表示如果没有申请到资源就一直等待该资源,对象被触发信号后,函数才会返回。
//如果指定为0,表示一旦得不到资源就返回,也可以具体地指定等待多久才返回,单位是千分之一秒

在多线程的情况下,有时候我们会希望等待某一线程完成了再继续做其他事情,要实现这个目的,可以使用Windows API函数WaitForSingleObject,或者WaitForMultipleObjects。这两个函数都会等待Object被标为有信号(signaled)时才返回的。

如果想要等待一条线程,那么你需要指定线程的Handle,

以及相应的Timeout时间。当然,如果你想无限等待下去,Timeout参数可以指定系统常量INFINITE。

2. 使用对象

它可以等待如下几种类型的对象:

Event,Mutex,Semaphore,Process,Thread

**3.**返回类型

有三种返回类型:

WAIT_OBJECT_0, 表示等待的对象有信号(对线程来说,表示执行结束);

WAIT_TIMEOUT, 表示等待指定时间内,对象一直没有信号(线程没执行完);

WAIT_ABANDONED 表示对象有信号,但还是不能执行 一般是因为未获取到锁或其他原因

ResetEvent

指定的事件对象设置为非信号状态。

句法

BOOL ResetEvent(
[in] HANDLE hEvent
);

参数

[in] hEvent

事件对象的句柄。​​CreateEvent​​​或 OpenEvent ​​函数​​返回此句柄。

句柄必须具有 EVENT_MODIFY_STATE 访问权限。有关详细信息,请参阅 ​​同步对象安全性和访问权限​​。

返回值

如果函数成功,则返回值非零。

如果函数失败,则返回值为零。要获取扩展的错误信息,请调用 ​​GetLastError​​。

//#include "stdafx.h"
#include <iostream>
#include <windows.h>
using namespace std;
HANDLE g_hEvent;
DWORD WINAPI XC_Boy(PVOID pParam);
DWORD WINAPI XC_Girl(PVOID pParam);
int main()
{
cout << "Start\n" << endl;
g_hEvent = CreateEvent(NULL, TRUE, FALSE, TEXT("Test"));
CreateThread(NULL, 0, XC_Boy, NULL, 0, NULL);
CreateThread(NULL, 0, XC_Girl, NULL, 0, NULL);
system("pause");
puts("");
return 0;
}
DWORD WINAPI XC_Boy(PVOID pParam)
{
cout << "Boy come in\n" << endl;
DWORD dwReturn = WaitForSingleObject(g_hEvent, INFINITE);
switch (dwReturn)
{
case WAIT_OBJECT_0:
// hProcess所代表的进程在5秒内结束
cout << "Boy Get what her said\n" << endl;
break;
case WAIT_TIMEOUT:
// 等待时间超过5秒
break;
case WAIT_FAILED:
// 函数调用失败,比如传递了一个无效的句柄
break;
}
cout << "Boy leave" << endl;
return 0;
}

DWORD WINAPI XC_Girl(PVOID pParam)
{
Sleep(1000); // 为了证明只有释放信号,线程1才能执行
cout << "Girl come in\n" << endl;
//SetEvent(g_hEvent); // 释放信号
ResetEvent(g_hEvent); // 关闭信号
cout << "Girl tell you something\n" << endl;
cout << "Grl leave\n" << endl;
return 0;
}

不太理理解..

URLDownloadToFile

HRESULT URLDownloadToFile(
LPUNKNOWN pCaller,//如果调用应用程序不是 ActiveX 组件,则可以将此值设置为NULL
LPCTSTR szURL, //指向包含要下载的 URL 的字符串值的指针。
//不能设置为NULL。如果 URL 无效,则返回 INET_E_DOWNLOAD_FAILURE
LPCTSTR szFileName,//指向字符串值的指针,该字符串值包含要为下载创建的文件的名称
//或完整路径。如果szFileName包含路径,则目标目录必须已经存在
_Reserved_ DWORD dwReserved,//保留。必须设置为 0。
LPBINDSTATUSCALLBACK lpfnCB
);

此函数可以返回这些值之一。

返回码

描述

S_OK

下载成功开始。

E_OUTOFMEMORY

缓冲区长度无效,或内存不足,无法完成操作。

INET_E_DOWNLOAD_FAILURE

指定的资源或回调接口无效。

VirtualProtect 不理解

它会在呼叫处理程序的虚拟位置空间里,变更认可页面区域上的保护

BOOL VirtualProtect(
[in] LPVOID lpAddress, // 指向要变更保护属性的内存基址。
[in] SIZE_T dwSize, // 大小
[in] DWORD flNewProtect, //要套用的记忆体保护类型。
[out] PDWORD lpflOldProtect //上一个记忆体保护值的指针。
);

如果函数成功,则返回值非零。

如果函数失败,则返回值为零。要获取扩展的错误信息,请调用GetLastError

VirtualAllocEx 在指定进程中保留或提交内存区

在指定进程的虚拟空间保留或提交内存区域

LPVOID VirtualAllocEx(
[in] HANDLE hProcess, //目标进程句柄
[in, optional] LPVOID lpAddress, //为要分配的页面区域指定所需起始地址的指针
[in] SIZE_T dwSize, //要分配的内存区域的大小
[in] DWORD flAllocationType, //内存分配的类型。
[in] DWORD flProtect //内存保护常量
);

如果函数成功,则返回值是分配的页面区域的基地址。

如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用GetLastError。

举报

相关推荐

0 条评论