C++基础学习(1)

0x01 关于工程

1.1 工程框架

创建MFC工程,可选择界面:

对话框、单文档、多文档、多顶级文档

详情:

https://www.cnblogs.com/hao-hong-sheng/p/8513280.html

 

1.2 添加控件

在资源视图打开对话框节点,从工具箱中拖入新增控件后,vs会将新控件自动添加到Resource.h中定义。但在cpp中还显示不出新添加控件的ID,需要在项目==》重新扫描解决方案,才能自动补全新控件名。

双击button自动添加了单机事件,会在cpp文件中添加消息映射message map。如:

BEGIN_MESSAGE_MAP(CMFCmessage3Dlg, CDialogEx)

    ON_BN_CLICKED(IDC_bt_open, &CMFCmessage3Dlg::OnBnClickedbtopen);

END_MESSAGE_MAP()

 

1.3 Edit Control控件

(1)基本

添加控件后,右键为这个控件添加变量,变量类别为控件(不是值),才能通过这个变量去调用函数操作控件。

VAR_EDIT1.SetWindowTextW(_T("abc"));

控件变量名为VAR_EDIT1,不同于控件的ID为IDC_EDIT1

_T是一个适配宏,把字符串转为适合工程的编码。

当#if def _UNICODE时,_T会把字符以UNICODE双字节存储。

否则会以ANSI单字节存储。

函数SetwindowTextW接受的参数类型是LPCTSTR

它是32-bit指针指向一个常字符串,每字符可能占1字节或2字节,取决于是否定义为Unicode。

控件内数据换行需要 \r\n

(2)更新数据

在资源视图,为控件右键添加变量,类型为值。

变量VALUE_EDIT1为控件的值变量。

UpdateData(1);

可以让数据从控件,传输到变量,VAULE_EDIT1的值被更新。

UpdateData(0);

可以让数据从变量,传输到控件,控件中的值被更新。

其实相当于,新建一个CString变量,对控件取值,运算,再赋值回去。

VAR_EDIT1.GetWindowTextW(ms1);

ms1.Append(_T("hhh"));

VAR_EDIT1.SetWindowTextW(ms1);

 

0x02 消息操控

2.1 循环获取Notepad句柄,并发送关闭指令

HWND txtHWND = ::FindWindow(_T("Notepad"), NULL);

while (txtHWND != NULL)

{

::SendMessage(txtHWND, WM_CLOSE, NULL, NULL);

txtHWND = ::FindWindow(_T("Notepad"), NULL);

VALUE_EDIT1.Append(_T("已关闭记事本\r\n"));

}

VALUE_EDIT1.Append(_T("没有找到记事本"));

UpdateData(0);

return;

 

2.2 关于消息

有3种发送消息的方式,send、post、广播。区别在于,send是阻塞的,要等到程序返回才继续执行,post是送到即刻返回。记得要::再调用系统的SendMessage,否则调的是作用域内的同名函数,不一样。

::SendMessage函数:

第一个参数是句柄

第二个参数是发送的常量消息,具体可发送:

https://blog.csdn.net/rbmwjyc/article/details/78981751

第三个参数wParam:指定附加的消息特定信息。通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。

第四个参数IParam:指定附加的消息特定信息。通常是一个指向内存中数据的指针。

 

2.3 FindWindow函数

用来获取窗口句柄

参数1:LPCTSTR lpClassName  窗口类名

参数2:LPCSTR lpWindowName 窗口名

窗口名有时经常变,比如记事本的名字可能是无标题-记事本。

实际应用1:

通过窗口类名获取txt句柄

HWND txtHWND = ::FindWindow(_T("Notepad"), NULL);

实际应用2:

通过窗口名获取winrar句柄

HWND txtHWND = ::FindWindow(NULL, _T("Downloads (评估版本)"));

::为域名解析符,表示名字冲突时引用的是全局变量,或不在当前类中的函数,在命名空间中的函数。 或者域作用符,在没声明name space std时,仍可用std::cout  << "";

 

2.4 运行程序

静默运行:

WinExec("notepad.exe",SW_HIDE);

参数详解:

----SW_HIDE 隐藏 

----SW_MAXIMIZE 最大化 

----SW_MINIMIZE 最小化,并把Z order顺序在此窗口之后(即窗口下一层)的窗口激活 

----SW_RESTORE 激活窗口并还原为初始化大小 SW_SHOW 以当前大小和状态激活窗口 

----SW_SHOW 用当前的大小和位置显示一个窗口,同时令其进入活动状态 

----SW_SHOWDEFAULT 以默认方式运行

----SW_SHOWMAXIMIZED 激活窗口并最大化 

----SW_SHOWMINIMIZED 激活窗口并最小化 

----SW_SHOWMINNOACTIVE 最小化但不改变当前激活的窗口 

----SW_SHOWNA 以当前状态显示窗口但不改变当前激活的窗口 

----SW_SHOWNOACTIVATE 以初始化大小显示窗口但不改变当前激活的窗口 

----SW_SHOWNORMAL 激活并显示窗口,如果是最大(小)化,窗口将会还原。第一次运行程序 时应该使用这个值 

 

2.5 利用消息修改窗体标题

也是发送一个消息,利用函数SendMessage

但是,总是出现乱码,需要利用SendMessageA函数。

函数后的A表示ANSI字符,单字节;W表示wide宽字节Unicode。

::SendMessageA(hTxt, WM_SETTEXT, WPARAM(0), (LPARAM)newTitle2);

发送修改标题的参数要用WM_SETTEXT

第三个参数不起作用,把0强行转换为WPARAM类型。

第四个参数就是要修改成的标题字符,强制转换为LPARAM类型,但它只接受char *类型转换。

 

2.7 字符串操作、类型转换

(1)转换

常用_T("abc")宏,可以有效解决编码问题,建议每个字符常量都使用它,再做参数。

它根据程序定义的,自动将字符串转为ANSI或Unicode

还能将char *类型转为LPCWSTR 宽字节。

用GetWindowText读取到的窗体标题文字是CString类型,需要转换为char *时,可以:

USES_CONVERSION;

char * mTitle = T2A(mCString);

LPCSTR ps1 = "ggg";

CString t7 = CString(ps1);

反之,char *转CString可以用A2T函数。

(注意 USES_CONVERSION在堆栈分配空间,函数结束才释放,不要用在循环中,否则不停声明它,堆栈会占满)

(2)CString和LPCSTR

CString是MFC封装的字符串,操作起来非常方便,在MFC中尽量用它就好了。

CString的构造方法非常多

CString t1("aaa");

CString t2 = CString("bbb");

CString t3 = _T("ccc");

CStringA t4 = "ddd";

CString t5 = _T("e");  t5 += "ee";

CString t6 = CString("ff") + CString("f");

它附带的操纵字符串的方法也非常多,支持各种运算符 !=     <    >    += 

本质上是一个封装好的Class

LPCSTR中

L是long;P是指针;C是constant常量

它本质上是,字符数组常量指针。

(LPCWSTR是宽字符的数组常量指针。)

补充:const常量修饰的变量,表示变量的值将不能改变,若改变则报错。

(3)TCHAR

1字节的字符是char

2字节的字符是wchar_t

TCHAR是根据实际环境编码自行调节。

 

0x03 关于程序启动和流程

3.1 窗口主程序WinMain()

主程序在RegisterClassEX()注册窗口类之后,不断循环GetMessage()获取消息,获取到消息后,DispatchMessage()分发消息。

消息分发后,并非直接在窗口过程中对消息做处理,而是先由系统模块查找这个窗口的窗口类,通过窗口类再找到窗口过程地址,送达后,窗口过程再实际处理消息。

WinMain函数中,

参数1:HINSTANCE hInstance 是实例句柄(handler instance),和普通句柄不一样,实例句柄是程序加载入内存后的起始地址。可以通过GetModuleHandle()获得。

参数2:HINSTANCE hPrevInstance 已弃用。

参数3:LPSTR lpCmdLine 是启动参数,如notepad c:\1.txt 其中这个路径就是启动参数。

参数4:int nCmdShow 是启动方式,隐藏启动、最大化、最小化等。

WinMain()主函数的流程是,先注册窗口类、创建并显示窗口、不断收Message、分发、直到收到消息WM_QUIT结束。

摘录注册窗口类和初始化显示:

//注册窗口类

MyRegisterClass(hInstance);

// 执行应用程序初始化:

if (!InitInstance (hInstance, nCmdShow))

{ return FALSE; }

摘录一段主消息循环:

MSG msg;

// 主消息循环:

while (GetMessage(&msg, nullptr, 0, 0))

{

    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

    {

    TranslateMessage(&msg);

    DispatchMessage(&msg);

    }

}

发现在dispatch分发消息前,还有一个translate消息。

TranslateMessage(CONST MSG *lpMsg)

它是用来处理键盘消息的,得到的参数是message,若发现是按下键盘的msg,它会自动处理,与下个抬起键盘的msg合成为,按下了某键。

WM_KEYDONW + WM_KEYUP  ==>> WM_CHAR

 

3.2 注册窗口类MyRegisterClass()

参数仅仅为进程的实例句柄 HINSTANCE hInstance

共2步流程:

填充WNDCLASSEX结构体

调用RegisterClassEx()进行注册

(1)WNDCLASSEX结构体

WNDCLASSEX WndCls;

对WndCls的各个属性填充。

包括:

结构体大小、窗口过程的地址、实例句柄、窗口类类名、窗口类风格、窗口类背景色、鼠标句柄、图标句柄。

(赋值窗口过程地址的方法是 WndCls.lpfnwndProc = WindowProc; 把WindowProc函数名赋过去,即是地址。)

(2)注册

在最后,填充完结构体,把结构体地址&wcex作为注册类的参数,异步操作。

return RegisterClassExW(&wcex);

 

3.3 初始化显示窗体

InitInstance(HINSTANCE hInstance, int nCmdShow)

传入参数是实例句柄、显示模式。

分别用:

CreateWindowEX

ShowWindow

UpdateWindow

完成初始化。

 

3.4 窗口过程

WindowProc函数,在结构体中注册了地址,表示由此函数处理各种消息。

参数1:HWND hWnd 主窗体的句柄

参数2:UINT uMsg 消息 WM_XXX

参数3:WPARAM wParam 消息附带的数据放入此参数

参数4:LPARAM lParam

注意,没有case的消息类别都在最后default中由函数DefWindowProc处理。

比如CreateWindow()时发送的WM_CREATE消息就自动处理了。只有需要特殊处理写代码的消息类别,我们用case拦下来,自定义。

 

0x04 模拟鼠标键盘操作

两种方式,一是向目标控件发送消息;二是利用系统API函数keybd_event()、mouse_event()。

4.1 发送消息的模拟

(1)

模拟键鼠最好用PostMessage,非阻塞的,比send成功率大很多。这种方法可以窗口在后台运行。

移动鼠标消息:WM_MOUSEMOVE

左键按下消息:WM_LBUTTONDOWN

左键释放消息:WM_LBUTTONUP

键盘按下消息:WM_KEYDOWN

键盘释放消息:WM_KEYUP

常用键盘击键消息:WM_CHAR

此外还有很多。

::PostMessage(hTarget, WM_KEYDOWN, VK_F5, 1);

Sleep(50);

::PostMessage(hTarget, WM_KEYUP, VK_F5, 1);

(2)相关技术

MFC中的函数比起原生API,基本都少了句柄参数,不需要填入句柄,因为MFC自动维护了句柄,想获取它,可以通过GetSafeHwnd()函数。

从输入框中直接获取Int类型,可以:

int i = GetDlgItemInt(IDC_EDIT3, FALSE, TRUE);

也可以获取字符串,存入预先定好的CString类型变量var_cs中

GetDlgItemText(IDC_EDIT2, var_cs);

总结:从输入框获取文字有3种,第一种如上,第二种是IDC_EDIT2.GetWindowText,第三种是对输入框添加value型变量VALUE_EDIT3,正向更新数据updateData(1)

VAR_EDIT2.EnableWindow(FALSE);

可以用来把编辑框暂时锁死。

(3)SetTimer()函数

用函数设定定时器,和加入定时器控件效果相同。

MFC中不需要写第一个参数句柄,所以::SetTimer是4个参数,直接SetTimer是3个参数

其余也有略微不同。

3个参数时,第一个参数是定时器ID,UINT_PTR nIDEvent 后边监听的OnTimer的参数就是它,可以switch case对不同定时器的触发,做出不同处理。

第二个参数是间隔时间,5秒就是5*1000

第三个参数写NULL就行 (MFC中)

添加函数OnTimer(UINT nIDEvent)去处理时间触发后要做的事。

需要在制作界面处,右键打开类向导,不是添加函数,而是在消息菜单中,为WM_TIMER添加处理程序,就会自动生成此函数。

停止计时,要用KillTimer(1) 参数是定时器ID

4.2 API模拟键鼠

必须确保窗口处于激活状态。

::SetForegroundWindow(hTop);

函数调用失败了

SetWindowPos不能替代它,不光是最上层的问题,必须还是焦点。

先把窗口从最小化恢复显示也没用

::ShowWindow(hTop, SW_SHOWNORMAL);

直接调用键盘吧

keybd_event(0x40, 0, 0, 0);

0x40就是a 字母不需要VK_xx

鼠标:

定义一个坐标,获取当前位置,设定新位置。

POINT pt = { 0 };

::ClientToScreen(hTop, &pt);

SetCursorPos(pt.x + 100, pt.y + 200);

这样会往左上方移动,坐标起始是右下角。

两次按键之间,最好Sleep(200)

 

实际应用中发挥模拟鼠标操作不容易,有些游戏做到:

Hook了mouse_event。

过滤了来自PostMessage的部分消息。

使用DX响应键鼠操作。

 

0x05 进程间消息通信

前提:必须有窗口,有Handle才行,否则不是基于消息驱动。

有两种方法。

5.1 使用WM_COPYDATA消息

必须用SendMessage不能Post,而且不是太靠谱,不能数据过多。

四个参数中,

第一个填目标窗口句柄,

第二个是消息类型,WM_COPYDATA,

第三个填本窗口句柄(其实可以省略为NULL),

第四个填结构体指针。

结构体是COPYDATASTRUCT,包含:

ULoing_PTR dwData, //可以设为0

DWORD cbData, //获取长度strText.GetLength()+1

PVOID lpData。//要发送的实际内容

接收方,可以在图形化界面中添加对WM_COPYDATA消息的处理函数,类向导==》消息中添加。

(通过句柄取得进程号:GetWindowThreadProcessId)

 

5.2 自定义消息通信

只能用第3、4个参数传递简单的数值型数据。

系统定好的WM消息是0到0x3ff 自定的消息从WM_USER往后加就行。

如自定的第一个消息可以为: 

#define WM_MYMSG1 WM_USER + 1

(在发送端、接收端都要有此定义)

在接收消息端,要补充映射,把ON_WM_MYMSG1添加进map里。

在BEGIN_MESSAGE_MAP(CUserWMDlg, CDialog)

和END_MESSAGE_MAP()之间

加入:

ON_WM_MYMSG1(WM_MYMSG1, RevcMsg)

也就是收到消息后,交给自定的RevcMsg函数去处理。

VOID XXX::RevcMsg(WPARAM wParam, LPARAM lParam)

{

xxx

}

两个参数收的是什么,完全可以自定,比如发来两个数字,由接收端计算后发回。

直接强制类型转换就好。

 

另:

查看错误代码工具:

E:\VisualStudio\program\Common7\Tools\errlook.exe

用GetLastError()即可看到错误号码,打开工具,输入查询,即可获得解释。

(从vs的工具==》外部工具中可以看到,里面还有Spy++)

关于堆:

释放后的堆空间,在内存中的值会变成:

EE FE    EE FE   EE FE  ......

C中,malloc和free是申请堆和释放堆

C++中 new关键字就是申请堆,delete关键字释放堆。

关于调试:

VC6需要在View中Debug windows中手动调出各种调试窗口

VS2019 可以在菜单==》调试==》窗口中找出:

调试线程的窗口、内存状况的窗口、寄存器窗口、看模块pdb、看汇编码的反汇编窗口

特别是反汇编窗口,有C++语句和汇编语句的对应!

 

 

你可能感兴趣的:(C++,SendMessage,Windows编程)