我之前是一个只会编写数值计算的程序的OIer,但我并不甘于这种现状,于是 我 编写了我 的第一个使用API函数的C++程序 , 开发平台是VS2012 :
// ConsoleApplication.cpp : 定义控制台应用程序的入口点。 // #include <stdio.h> #include " stdafx.h " #include <Windows.h> int _tmain( int argc, _TCHAR* argv[]) { HWND wnd; wnd =FindWindowA(NULL, " 无标题 - 记事本 " ); SendMessage(wnd,WM_CLOSE, 0 , 0 ); system( " pause " ); return 0 ; }
之前有在看谷夕写的HACK编程实例精讲,第一节有介绍API函数,他的书上用的是hDesk=OpenDesktop(lpszDesktop,0,FALSE,DESKTOP_ENUMERATE);在这之前我连如何调用API函数都不知道,尝试过#include<stdafx.h>等等乱七八糟的东西,结果都不行.后来百度了一下,才发现原来是要加#include<windows.h>
明白如何才能可以调用API函数之后,尝试使用书上介绍的OpenDesktop函数,因为我什么都不知道,于是程序我就这么写了:
// ConsoleApplication.cpp : 定义控制台应用程序的入口点。 // #include <stdio.h> #include " stdafx.h " #include <Windows.h> int _tmain( int argc, _TCHAR* argv[]) { int hDesk=OpenDesktop(lpszDesktop, 0 ,FALSE,DESKTOP_ENUMERATE); system( " pause " ); return 0 ; }
结果是关于lpszDesktop发生了某种我理解不了的错误.于是再次百度API函数应用实例,就找到了第一个程序.
一开始仍然编译不过,编译器提示错误.
我也不知道LPCWSTR是 什么 ,不过看网上关于FindWindow函数的介绍是这么说的: 函数的定义:HWND WINAPI FindWindow(LPCSTR lpClassName ,LPCSTR lpWindowName); 这上边居然是LPCSTR,于是我试了试类似的函数,发现FindWindowA函数上是LPCWSTR,于是改成FindWindowA终于编译过了,实现的功能就是把名为无标题 - 记事本的窗口关闭.
我学习这类东西一向喜欢先弄出来一个能正确运行的样例,然后再弄明白有问题的地方,这次也不例外.关于这个程序我主要有这几个方面不太明白:1.句柄是个啥玩意;2.LPCSTR和LPCWSTR又是什么东西;3.FindWindow函数还有啥其他调用方式.
先搞明白句柄是什么东西,当然还要靠百度.
句柄 , 是整个windows编程的基础 . 一个句柄是指使用的一个唯一的整数值 , 即一个四字节长的数值 , 来标志应用程序中的不同对象和同类对象中的不同的实例 , 诸如 , 一个窗口 , 按钮 , 图标 , 滚动条 , 输出设备 , 控件或者文件等 . 应用程序能够通过句柄访问相应的对象的信息 , 但是句柄不是一个 指针, 程序不能利用句柄来直接阅读文件中的信息 . 如果句柄不用在I/O文件中 , 它是毫无用处的 . 句柄是windows用来标志应用程序中建立的或是使用的唯一整数 , windows使用了大量的句柄来标志很多对象 .
关于句柄我是这样理解的:它有点类似于算法中的哈希值,对于一个对象系统分配给它一个唯一的值来标记存储该对象变动情况的位置.
LPCWSTR是一个指向unicode编码字符串的32位指针 , 所指向字符串是wchar型 , 而不是char型 .
原来LPCWSTR是 unicode 字符串指针,那unicode又是什么东西呢(请原谅我的无知,我真的连 unicode 是啥都不知道)?看过 这篇文章 之后才大概有了个了解.比较特别之处在于定义unicode字符串时要在左边加一个L,就像这样:
wchar_t wStr[] = L"中文";
除此之外,我又了解到在VS2005以后,编码方式默认为unicode,部分函数在使用时默认调用unicode方式(函数名加W),而非ansi方式(函数名加A).之前那个程序由于"无标题 - 记事本"是编码为ansi的字符串,所以调用默认的unicode方式时出现错误,而使用FindWindowA之后采用ansi方式才编译通过.然后我又把刚才的调用方式改为wnd=FindWindow(NULL,L"无标题 - 记事本");终于编译通过.wchar_t的输出:一般说来有两种方式,一个是wcout,还有是wprintf()之类的函数,悲剧的是无论哪种方法都没编译过.后来看一个博客里说要在main函数里加上_tsetlocale(LC_ALL, L"CHS");于是wcout能用了,wprintf仍然不行.先不管它,继续往后看.
第二个API函数:SendMessage函数.SendMessage函数能向指定句柄所对应的对象发送信息,示例程序:
POINT curpos; while ( 1 ) { GetCursorPos( & curpos); HWND hWnd = WindowFromPoint(curpos); SendMessage(hWnd,WM_CHAR,WPARAM( ' g ' ), 0 ); Sleep( 300 ); }
该程序实现的功能是向鼠标所指向的编辑窗口输入g.
POINT就是表示坐标的数据类型.GetCursorPos(&curpos)表示取当前鼠标的坐标.WindowFromPoint(curpos)表示获得curpos处的对象的句柄.
WM_CHAR表示输出字符.
接着,我想写一个程序给自己发送标准输入数据:
while ( 1 ) { GetCursorPos( & curpos); HWND hWnd = WindowFromPoint(curpos); SendMessage(hWnd,WM_CHAR,WPARAM( ' g ' ), 0 ); SendMessage(hWnd,WM_CHAR,WPARAM( ' \r ' ), 0 ); Sleep( 300 ); char ch; cerr << " waiting for reading " << endl; cin >> ch; if (ch== ' g ' ) break ; }
意思就是当通过SendMessage函数向程序窗口发送一个g,如果被成功读入就跳出循环,一开始我第2个SendMessage里发送的是 ’ \n ’ ,结果没法触发读入,接着改成 ’ \0 ’ ,还是不行,又改成 ’ \r ’ ,这次终于成功了,原来回车键输入的是一个 ’ \r ’ 啊,之前一直以为是 ’ \n ’ 呢...
再往下又看到一个更有趣的:模拟鼠标动作
void RightClick(HWND wnd) { SendMessage(wnd,WM_RBUTTONDOWN, 0 , 0 ); Sleep( 100 ); SendMessage(wnd,WM_RBUTTONUP, 0 , 0 ); }
其中,WM_RBUTTONDOWN表示鼠标右键按下,WM_RBUTTONUP表示鼠标右键抬起.测试了一下运行没什么大问题,不过看起开必须等我再单击一下鼠标左键才能让程序继续执行.这样感觉用起来很不舒服,而且也没有什么应用价值.后来有看到专门有一个函数mouse_event能模拟鼠标的点击.关于这个函数的详细信息我看不太懂,就只用了一个最简单的应用:鼠标左击.再结合刚才的SendMessage函数,做了一个刷屏软件,虽然功能很弱,但做完的时候还是感觉很激动.弱弱地贴一下我的代码:
// 刷屏软件.cpp : 定义控制台应用程序的入口点。 // #include<math.h> #include <stdio.h> #include <stdlib.h> #include < string .h> #include " stdafx.h " #include <Windows.h> #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <locale> #include <cstring> using namespace std; void Home_Page() { printf( " 1.使用说明.\n " ); printf( " 2.开始运行.\n " ); printf( " 3.退出程序.\n " ); } void Left_Click() { mouse_event(MOUSEEVENTF_LEFTDOWN, 0 , 0 , 0 , 0 ); mouse_event(MOUSEEVENTF_LEFTUP, 0 , 0 , 0 , 0 ); } void Software_Description() { printf( " 本软件使用很简单,只要按提示一步一步操作即可.\n " ); printf( " 也许有些使用者觉得软件写的太脑残,请原谅,我刚接触这方面,这个程序纯属练手.\n " ); printf( " 特别提示:由于作者水平问题,请您在所有信息发送完成之前不要移动您的鼠标,否则可能会产生难以预料的结果.\n " ); printf( " 同样由于作者的水平问题,目前只支持英文文本(Ansi字符集)的发送.\n " ); printf( " 如果发送内容中包含多个单词,建议用下划线\'_\'连接\n " ); printf( " 联系作者:winifred.oier@gmail.com\n " ); system( " pause " ); } void Main_Program() { printf( " 请输入您希望发送的内容(一行):\n " ); string TexttobeSend; cin >> TexttobeSend; printf( " 请输入您希望每隔多长时间发送一次(单位:毫秒):\n " ); int DelayTime; cin >> DelayTime; printf( " 您希望将该信息发送多少遍:\n " ); int SendTime; cin >> SendTime; printf( " 请在5秒内将鼠标放在发送信息的编辑框里\n " ); HWND InputBox; POINT curpos; Sleep( 5000 ); GetCursorPos( & curpos); InputBox = WindowFromPoint(curpos); cerr << " The Input Box has been engaged " << endl; printf( " 请在5秒内将鼠标放在发送按钮上.再次提示:由于作者水平太烂,执行完之前千万别乱动鼠标.\n " ); Sleep( 5000 ); for ( int i= 1 ;i<=SendTime;i++ ) { for ( int j= 0 ;j<TexttobeSend.length();j++ ) SendMessage(InputBox,WM_CHAR,WPARAM(TexttobeSend[j]), 0 ); Left_Click(); Sleep(DelayTime); } } int _tmain( int argc, _TCHAR* argv[]) { int Command_Code; while ( true ) { Home_Page(); cin >> Command_Code; switch (Command_Code) { case 1 : Software_Description(); break ; case 2 : Main_Program(); break ; case 3 : return 0 ; } system( " cls " ); } return 0 ; }
不过在写代码的时候发现一个问题,不同的头文件包含顺序可能会影响能否编译,还有就是编译出来的可执行文件只能在我的电脑上执行,我用同学的电脑测试发现缺少一个MS...的动态链接库,不明白为什么.