第一個:FindWindow根據窗口類名或窗口標題名來獲得窗口的句柄,該函數返回窗口的句柄
函數的定義:HWND WINAPI FindWindow(LPCSTR lpClassName ,LPCSTR lpWindowName);
第一個參數填窗口的類名,第二個填窗口的標題名,其實是不需要同時填兩個參數的,也就是說,你只要知道窗口的類名或窗口的標題就可以了,沒有的那個就用NULL代替。
比如現在有一個窗口名為"無標題.txt - 記事本"的記事本程序。那么我就可以用上面的函數獲得這個窗口的句柄,那獲得了這個窗口的句柄我可以干什么呢?作用可大了,因為很多操作窗口的函數,都需要窗口句柄作參數,如移動、改變窗口大小的MoveWindow函數,在這里舉個例子,大家就更能體會到這個FindWindow的用法、用處。
FindWindow例子:已知一個窗口名稱,寫一個程序關閉該窗口,假設當前電腦正有一個窗口名為"無標題.txt - 記事本"的記事本程序運行
#include<windows.h> //API函數的頭文件
int main()
{
HWND wnd; //定義一個窗口句柄變量,用以存儲找到的窗口句柄
wnd=FindWindow(NULL,"無標題.txt - 記事本"); //獲得窗口名為"無標題.txt - 記事本"的窗口句柄
SendMessage(wnd,WM_CLOSE,0,0); //調用SendMessage函數,發送一個WM_CLOSE(關閉)消息給wnd窗口句柄。
return 0;
}
如果要根據窗口類名來獲得窗口句柄話,只要給函數的第一個參數填類名,第二個參數窗口名填NULL,即可,用Spy++可查看窗口類名。
第二個:SendMessage根據窗口句柄發送一個消息給窗口
函數定義:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);
第一個參數是窗口句柄,第二參數個是消息類型,下面的消息表列舉了所有消息,第三,四個參數是消息附帶信息,解釋依賴於消息類型,比如一個字符消息(WM_CHAR),那么第三個參數就儲存有一個字符的ASCII碼。
消息機制大家都應該知道吧,Windows是基於消息的系統,鼠標移動鍵盤按鍵都會產生消息。
接下來舉一個例子,發送一個WM_CHAR消息給窗口,也就是模仿鍵盤按鍵,接收消息的窗口依舊以"無標題.txt - 記事本"為例:
SendMessage例子:模仿鍵盤按鍵
#include<windows.h>
int main()
{
HWND wnd;
wnd=FindWindow(NULL,"無標題.txt - 記事本");
while(1)
{
SendMessage(wnd,WM_CHAR,WPARAM('a'),0);
Sleep(300);
}
return 0;
}
呵呵上面的例子是不是沒用,這是為什么呢,哪里出錯了嗎?錯倒是沒有錯,只是窗口句柄有問題,消息發送給了主窗口。接收消息的窗口不對。記事本窗口界面有些有什么東西呢?菜單,編輯框, 狀態欄等控件,控件也是窗口,既然是窗口,那當然它們也有窗口句柄,而在記事本里是在哪里打字的?編輯框控件里打字的嘛!所以消息應該發送編輯框控件,那如何獲得記事本里編輯框控件的窗口句柄呢?用FindWindow嗎?不知道編輯框窗口標題名,類名也不知道,當然也有其它方法獲取編輯框窗口標題名和窗口類名,如Spy++。關於如何獲得編輯框句柄,將在以后的函數中會有介紹,這里我們就用WindowFromPoint這個函數來獲取,這個函數獲取窗口句柄的方法比較笨,(相對於我這個例子來說),這個函數是根據什么來獲取窗口句柄的呢?根據屏幕坐標點,如屏幕坐標點20,20,當前是哪個窗口占有,就返回哪個窗口的句柄。有了這個函數,我們還需要一個函數GetCursorPos獲取鼠標當前位置(針對於屏幕);
可行的例子:模仿鍵盤按鍵:
#include<windows.h>
int main()
{
POINT curpos; //一個可儲存坐標點的結構體變量,x橫坐標,y,縱坐標,如curpos.x curpos.y
while(1)
{
GetCursorPos(&curpos); //獲取當前鼠標的位置,位置將儲存在curpos里。
HWND hWnd = WindowFromPoint(curpos); //根據curpos所指的坐標點獲取窗口句柄
SendMessage(hWnd,WM_CHAR,WPARAM('g'),0); //發送一個字符(按鍵)消息g給當前鼠標所指向的窗口句柄
Sleep(300); //睡眠三百毫秒,相當於等待三分之一秒
}
}
這個程序一運行后,只要把鼠標指向要輸入字符的窗口句柄,那么就相當於鍵盤每三分之一秒按了一個g鍵,試試吧!
如果這樣覺得模仿鍵盤按鍵太麻煩的話,那么就用keybd_event這個函數,這個專門用於模仿鍵盤按鍵的,關於怎么用,自己百度一搜,就知道了。既然SendMessage能模仿鍵盤按鍵的話,那也能模仿鼠標左擊,右擊。而此時SendMessage函數第三,四個參數的解釋就是儲存有鼠標左擊,右擊時的位置。如模仿鼠標右擊,想一想,一次鼠標右擊有哪幾步,分別是鼠標右鍵按下,鼠標右鍵松開,如果你按下鼠標右鍵不松開,那它是不是鼠標右擊,不是的,直到你松開鼠標右鍵,才能算是一次完整的鼠標右擊.鼠標右鍵按下的消息類型是“WM_RBUTTONDOWN”,右鍵松開的消息是“WM_RBUTTONUP”,那么一次完整的鼠標右擊應該是:
SendMessage(wnd,WM_RBUTTONDOWN,0,0); //鼠標右鍵按下,第三,四個參數說明了鼠標按下時的位置
Sleep(100); //間隔100毫秒
SendMessage(wnd,WM_RBUTTONUP,0,0); //鼠標右鍵松開
同樣,也有一個專門模仿鼠標動作的函數,mouse_event這個函數,可以模仿鼠標的移動,單擊,雙擊等。以后會有專門介紹。
第三個:GetCursorPos獲取鼠標當前位置(屏幕)
這個函數在SendMessage函數有介紹,這里僅舉一個例子,在界面里不停的輸出鼠標當前位置。
#include<windows.h>
#include<stdio.h>
int main()
{
POINT curpos;
while(1)
{
GetCursorPos(&curpos);
printf("x:%d,y:%d",curpos.x,curpos.y);
Sleep(300);
printf("\n");
}
}
第四個:WindowFromPoint根據坐標點獲得對應的窗口句柄
在SendMessage有解釋,這里僅舉一個例子,鼠標指向哪個窗口,就關閉哪個窗口。
#include<windows.h>
int main()
{
Sleep(2500); //等待一會兒,用於把鼠標移到其它窗口上去,避免指向本身進程的窗口,關掉自己的窗口。
POINT curpos;
while(1)
{
GetCursorPos(&curpos);
HWND wnd=WindowFromPoint(curpos);
SendMessage(wnd,WM_CLOSE,0,0);
Sleep(300);
}
}
第五個MoveWindow根據窗口句柄移動窗口,改變窗口大小
函數定義:BOOL MoveWindow( HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint );
hWnd是要改變大小的窗口的句柄,x,y相對於屏幕的坐標,窗口左上角的位置與之相對應,nWidth和nHeight是窗口新的寬高,bRepaint指定窗口是否重畫。
這里依舊以"無標題.txt - 記事本"為例子,改變這個窗口大小,並把窗口移到左上角去。
#include<windows.h>
int main()
{
HWND wnd;
wnd=FindWindow(NULL,"無標題.txt - 記事本");
MoveWindow(wnd,0,0,220,120,NULL);
return 0;
}
第六個ShowWindow設置窗口顯示狀態,如隱藏,最大化,最小化
函數定義BOOL ShowWinow(HWND hWnd,int nCmdShow);
SW_HIDE:隱藏窗口並激活其他窗口。第一個參數hWnd指明了窗口句柄,第二個參數指明了窗口的狀態,現在給出第二個參數常用取值范圍:
SW_MAXIMIZE:最大化指定的窗口。
SW_MINIMIZE:最小化指定的窗口並且激活在Z序中的下一個頂層窗口。
SW_RESTORE:激活並顯示窗口。如果窗口最小化或最大化,則系統將窗口恢復到原來的尺寸和位置。在恢復最小化窗口時,應用程序應該指定這個標志。
SW_SHOW:在窗口原來的位置以原來的尺寸激活和顯示窗口。
ShowWindow例子:程序運行后,在桌面上隱藏一個指定的窗口,並在4秒后再將其顯示
#include<windows.h>
int main()
{
HWND wnd;
wnd=FindWindow(NULL,"無標題.txt - 記事本");
ShowWindow(wnd,SW_HIDE);
Sleep(5000);
ShowWindow(wnd,SW_SHOW);
return 0;
}
第七個SetCursorPos設置鼠標的位置、把鼠標移動到指定的位置
函數定義:BOOL SetCursorPos(int x,int y);
這個函數的兩個參數我想大家應該知道是什么意思吧,屏幕的坐標點。
直接看例子:
#include<windows.h>
int main()
{
int sec=0;
while(sec<200)
{
SetCursorPos(rand()%1024,rand()%768); //隨機設置鼠標的位置
Sleep(20);
sec++;
}
return 0;
}
第八,九,十 CopyFile復制一個文件 ,DeleteFile刪除一個文件,CreateDirectory創建一個文件夾
如何復制一個文件,比如,我要把E盤的abb.txt的文本文件復制到d盤的zhengyong.txt,則調用語句
CopyFile("e:\\abb.txt","d:\\zhengyong.txt",FALSE);
DeleteFile("e\\abb.txt");
CreateDirectory("e:\\aaa\\bbb",NULL)
第三個參數有以下說明:
如果設為TRUE(非零),那么一旦目標文件已經存在,則函數調用會失敗。否則目標文件會被覆蓋掉。
第十一個:GetClientRect獲得窗口大小
看例子:
#include<windows.h>
#include<stdio.h>
int main(int argc, char* argv[])
{
HWND wnd;
while(1)
{
wnd=FindWindow(NULL,"無標題.txt - 記事本");
RECT rect; //專門用來存儲窗口大小
GetClientRect(wnd,&rect); //獲取窗口大小
printf("%d,%d,%d,%d\n",rect.left,rect.top,rect.right,rect.bottom); //輸出窗口大小,試着用鼠標改變窗口大小
Sleep(300);
}
}
第十二個:GetCWindowRect獲得窗口大小(相對屏幕)
例子:
#include<windows.h>
#include<stdio.h>
int main(int argc, char* argv[])
{
HWND wnd;
while(1)
{
wnd=FindWindow(NULL,"無標題.txt - 記事本");
RECT rect; //專門用來存儲窗口大小
GetWindowRect(wnd,&rect); //獲取窗口大小
printf("%d,%d,%d,%d\n",rect.left,rect.top,rect.right,rect.bottom); //輸出窗口大小,試着用鼠標改變窗口大小
Sleep(300);
}
}
試着去找一下GetClientRect和GetWindowRect之間有什么區別;
第十三個FindFirstFile尋找文件以及獲得文件的信息
這里舉一個例子吧,列舉E盤第一目錄下的所有文件,包括文件夾,結合FindNextFile
#include<windows.h>
#include<stdio.h>
int main()
{
BOOL done=TRUE;
WIN32_FIND_DATA fd;
HANDLE hFind = FindFirstFile("e:\\*.*", &fd); //第一個參數是路徑名,可以使用通配符,懂DOS的人應該知道吧!fd存儲有文件的信息
while (done)
{
printf("%s\n",fd.cFileName);
done=FindNextFile(hFind, &fd); //返回的值如果為0則沒有文件要尋了
}
return 0;
}
當然也可以直接找一個文件,不使用通配符,但這樣有什么意義呢?,如FindFirstFile("e:\\aaa.txt",&fd);其實這個可以獲取一個文件的信息,如文件是不是隱藏的,或者有沒有只讀屬性等。
當然通過控制通配符,也可以尋找特定類型的文件,比如我只要找文本文件,那么就是這個語句FindFirstFile("e:\\*.txt",&fd);就行了,關鍵看你自己靈活運用。
前面說過fd里存儲有文件的信息,那怎么根據fd里面的成員判斷這個文件的屬性,文件是否隱藏,是不是文件夾。
fd里的dwFileAttributes存儲有文件的信息,如判斷是否為文件夾,只要把這個變量和FILE_ATTRIBUTE_DIRECTORY進行按位與運算,如果為1的話,表明為文夾件,如if(fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY==1) printf("%s是文件夾\n",fd.cFileName);
其它判斷也是一樣,現在給出文件的屬性(常用幾個):FILE_ATTRIBUTE_HIDDEN(隱藏)
FILE_ATTRIBUTE_READONLY(只讀)FILE_ATTRIBUTE_SYSTEM(系統)
第十六個GetClassName根據窗口句柄獲得窗口類名
函數定義:int GetClassName(HWND hWnd, LPTSTR IpClassName, int nMaxCount);
這種函數不需要再解釋了吧,前面有太多類似的例子。
第十七個SetFileAttributes設置文件屬性
函數定義:BOOL SetFileAttributes( LPCTSTR lpFileName, DWORD dwFileAttributes);
這個函數的第二個參數dwFileAttributes和前面講過的WIN32_FIND_DATA結構里的dwFileAttributes成員相對應。假設E盤第一目錄下有一個文本文件a.txt的正常文件,我要把它設為只讀和隱藏那要如何做呢?在前面介紹過WIN32_FIND_DATA結構里dwFileAttributes成員的幾個常用屬性,根據這個我們知道隱藏是FILE_ATTRIBUTE_HIDDEN,只讀是FILE_ATTRIBUTE_READONLY。
那么把E盤下文本文件的屬性設為隱藏和只讀的語句就是:
SetFileAttributes("e:\\a.txt",FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_READONLY);
(說明:這個函數同樣也能設置文件夾屬性)
雖然這個語句可以達到要求,但不建議用,因為會覆蓋掉文件的原來屬性,也就是說如果這個文件之前有系統屬性(系統文件)的話,那么這個語句一旦執行后,文件就只有隱藏和只讀屬性了。
比如一個文件原先就有隱藏屬性,依舊以a.txt為例子,那么我把它設為只讀,是不是這個語句就可以呢?
SetFileAttributes("e:\\a.txt",FILE_ATTRIBUTE_READONLY);這樣的話,雖然文件有只讀屬性了,但隱藏屬性卻沒有了。
那要如何在不覆蓋掉原來的屬性下,把文件設為只讀呢,其實說了這么多的廢話,總結起來就一句話:如何增加一個文件的屬性!
前提是要獲得這個文件的原有屬性:獲得文件的屬性,在FindFirstFile函數講過。好吧!直接看例子:
假設e盤的a.txt文件屬性為隱藏,給它增加只讀屬性:
#include<windows.h>
int main()
{
WIN32_FIND_DATA fd;
FindFirstFile("e:\\a.txt",&fd);
fd.dwFileAttributes|=FILE_ATTRIBUTE_READONLY; //在原來的屬性下增加只讀屬性
SetFileAttributes("e:\\a.txt",fd.dwFileAttributes); //設置文件的屬性
return 0;
}
第二個例子:如何去掉一個文件的屬性
(補習一下,懂的人直接跳過)
我想懂這里的按位或、按位與或者按位異或運算的人應該知道該如何去掉一個文件的屬性。其實一個文件信息都是以二進制代碼說明的。
比如一個八位二進制碼:10000010,這里的每一位是不是只有0和1取值,不是0,就是1,正好符合一個文件屬性的有無,如這個文件是隱藏的嗎?只有是和不是,這樣我們規定把這八位二進制碼的第一位用於確定文件是否具有隱藏屬性,如果為1那便是隱藏,無則沒有,以此類推第二位就代表文件的只讀,第三位系統。。。但要如何判斷呢,或者把某一位的值改變呢,用按位運算就可以,00000010,我要把第2位的值設為0,其它位上的值保持不變,用按位異或運算即可,與00000010進行按位異或運算,但這里並不是與它本身進行運算,不管任何八位二進制數的值是多少只要與00000010進行按位異或運算,那第二位都會變成0,而其它的位保持不變。這樣為了方便,我們就把00000010進行宏定義,方便記憶,這個二進制數的十進制為2。宏定義#define FILE_ATTRIBUTE_READONLY 2
明白了這個我們就來清除一個文件的一種屬性吧!
清除一個文件的隱藏屬性,假設a.txt為隱藏文件:
#include<windows.h>
int main()
{
WIN32_FIND_DATA fd;
FindFirstFile("e:\\a.txt",&fd); //獲取文件信息
fd.dwFileAttributes^=FILE_ATTRIBUTE_HIDDEN; //在原來的屬性下刪除隱藏屬性
SetFileAttributes("e:\\a.txt",fd.dwFileAttributes); //設置文件的屬性
return 0;
}
如果單單只針對文件的屬性進行操作的話,可以用GetFileAttributes函數獲取文件的屬性,該函數只一個參數,那就是文件的路徑,函數返回一個DWORD值,包含文件屬性信息。
第十八個ShellExecute運行一個程序
函數定義:ShellExecute(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd);
第一個參數hwnd是父窗口的句柄,可以為NULL,第二個參數lpOperation表示行為,第三個參數lpFile是程序的路徑名,第四個參數lpParameters是給所打開程序的參數,可以為NULL,第五個參數lpDirectory可以為NULL,第六個參數nShowCmd跟ShowWindow函數的第二個參數一樣,作用也一樣,如果打開的程序有窗口的話,這個參數就指明了窗口如何顯示.
例如打開一個記事本:
ShellExecute(NULL,"open","NOTEPAD.EXE",NULL,NULL,SW_SHOWNORMAL);
而且這個函數還可以指定程序打開一個屬於程序本身類型的文件,假如e盤有一個a.txt文件;我調用函數運行記事本程序並打開這個文本文件.
ShellExecute(NULL,"open","NOTEPAD.EXE","e:\\a.txt",NULL,SW_SHOWNORMAL);
這里由於記事本程序屬於系統本身自帶的程序,所以沒有絕對路徑.
這個函數還可以打開一個網站:
ShellExecute(NULL,"open","http://www.baidu.com",NULL,NULL,SW_SHOWNORMAL);
ShellExecute(NULL,"open","C:",NULL,NULL,SW_SHOWNORMAL);
類似的函數還有WinExec,只有兩個參數,它的最后一個參數跟ShellExecute函數的最后一個參數一樣.
而第一個參數則是程序路徑名.舉個例子:WinExce("NOTEPAD.EXE",SW_SHOWNORMAL);
這個函數也可以給程序傳遞一個文件名供要運行的程序打開,那要如何加進去呢,這里又沒有第三個參數,
方法把路徑名加在NOTPEPAD.EXE的后面,要以空格來分開如:
WinExce("NOTEPAD.EXE e:\\a.txt",SW_SHOWNORMAL);
第十九個PlaySound播放一個WAV文件
函數定義:BOOL PlaySound(LPCSTR pszSound, HMODULE hmod,DWORD fdwSound);
第一個參數是WAV文件的路徑名,第二個參數如果不是播放MFC里以資源ID命名的文件,則可以為空,第三個參數,指明了以何種方式播放文件。注意這個函數只能播放100K以下的WAV文件。
假如E盤有個a.wav文件,下面這個例子播放這個文件:
#include<windows.h>
#include<mmsystem.h> //PlaySound函數的頭文件
#pragma comment(lib, "winmm.lib") //鏈接庫,PlaySound函數必須使用
int main()
{
PlaySound("e:\\19.wav",NULL,SND_SYNC);
return 0;
}
第二十個GetModuleFileName根據模塊導入表獲程序的完整路徑
函數定義:DWORD GetModuleFileName( HMODULE hModule, LPTSTR lpFilename, DWORD nSize );
關於第一個參數,將在以后的動態鏈接庫里會有介紹,這里我們只要獲得程序本身的路徑,那么第一個參數可以為空。
第二個參數用以存儲路徑,nSize指明字符數組大小。
這個舉個例子,運行后,把自身程序移動到e盤下,並改名為a.exe;
#include<windows.h>
int main()
{
char szAppName[128]={0};
GetModuleFileName(NULL,szAppName,128);
MoveFile(szAppName,"e:\\a.exe");
return 0;
}
第二十一個CreateWindow創建一個窗口
//補習懂的人直接跳過
之前API函數的例子,都是針對DOS編程的,嚴格來說是在windows下的仿DOS(cmd)進行編程,編寫控制台應用程序大家都知道,主函數是main,那針對windows編程的主函數也是main嗎?不是的,windows下的主函數(入口函數)是WinMain。在定義main主函數的時候,可以給它帶兩個參數,也可以不帶。而WinMain函數就不能這樣了,它有固定的格式,它必須帶四個參數。
現給出WinMain函數的固定格式:
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, instance LPSTR lpCmdLine, int nCmdShow)
大家如果有興趣可以通過其它渠道了解一下各參數的意思,現在我們只需要知道WinMain函數就是這樣定義的,不理解也沒關系。
知道了這個我們就來編一個WINDOWS程序吧!
因為我們是針對windows編程,所以要建一個Win32 Application工程,步驟是點擊文件,然后選擇新建,在彈出的對話框里選擇工程,再選中Win32 Application 接着在右邊的工程名稱下填工程名稱,名字隨便取。之后點確定。接着又彈出了一個對話框,這里為了方便,我們選擇“一個簡單的 Win32 程序”,點完成。接着雙擊WinMain彈出代碼編輯窗口,做完這個我們就可以打代碼了。
簡單的例子如下:
#include "stdafx.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
while(1)
Sleep(100);
return 0;
}
怎么樣夠簡單吧,是不是覺得奇怪,怎么沒有窗口,因為窗口要自己創建,不像控制台程序,只要一運行便會有窗口。雖然沒有窗口,但你創建了一個進程,打開任務管理器,可以找到你所創建的那個進程,其實也沒什么奇怪的,像WINDOWS本身的一些系統服務,也是只有進程,沒有窗口的像spoolsv.exe,svchost.exe。
那要如何創建一個窗口呢?要創建一個窗口,就必須要向系統提供窗口的信息,如你要創建的窗口名字叫什么,窗口圖標是什么,窗口大小,窗口背景色等,不然,系統怎么給你創建窗口呢?所以為了方便,VC就定義了一個結構,專門用存儲窗口信息。
現給出這個結構的定義。
typedef struct _WNDCLASS {
UINT style; //描述類風格
WNDPROC lpfnWndProc; //窗口處理函數
int cbClsExtra; //表示窗口類結構之后分配的額外的字節數。系統將該值初始化為0
int cbWndExtra; //表示窗口實例之后分配的額外的字節數。系統將該值初始化為0
HINSTANCE hInstance; // 應用程序實例句柄由WinMain函數傳進來
HICON hIcon; //窗口圖標句柄
HCURSOR hCursor; //窗口光標句柄
HBRUSH hbrBackground; //畫刷句柄
LPCTSTR lpszMenuName; //窗口菜單名
LPCTSTR lpszClassName; //窗口類名
} WNDCLASS, *PWNDCLASS;
好了,如果我們已經把窗口信息填好了,那我們要怎樣把這個信息告訴系統呢,也就是把要創建窗口的信息傳給系統。這里我們調用RegisterClass函數就能實現這個功能。注冊完窗口,我們就要創建窗口,用CreateWindow函數就能實現,不要問為什么注冊窗口后直接顯示不就行了,還要搞什么創建窗口。這我也不知道,反正你只要記住這格式就行了,硬式規定的,你想創建一個窗口,就必須按這些步驟來。
好了,窗口創建了,我們就要調用ShowWindow函數顯示窗口,然后用UpdateWindow函數刷新一下,確保窗口能立即顯示。
以下詳細實現代碼:
#include "stdafx.h"
#include<windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASS wndcls; //定義一個存儲窗口信息WNDCLASS變量
wndcls.cbClsExtra=0; //默認為0
wndcls.cbWndExtra=0; //默認為0
wndcls.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //背景畫刷
wndcls.hCursor=LoadCursor(NULL,IDC_CROSS); //十字光標
wndcls.hIcon=LoadIcon(NULL,IDI_ERROR); //窗口圖標
wndcls.hInstance=hInstance; //應用程序實例句柄由WinMain函數傳進來
wndcls.lpfnWndProc=NULL; //窗口消息處理函數
wndcls.lpszClassName="windowclass"; //窗口類名
wndcls.lpszMenuName=NULL; //窗口菜單名,沒有菜單,為NULL
wndcls.style=CS_HREDRAW | CS_VREDRAW; //窗口類型,CS_HREDRAW和CS_VERDRAW 表明
//當窗口水平方向垂直方向的寬度變化時重繪整個窗口
RegisterClass(&wndcls); //把窗口信息提交給系統,注冊窗口類
HWND hwnd; //用以存儲CreateWindow函數所創建的窗口句柄
hwnd=CreateWindow("windowclass","first windows",
WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);//創建窗口
ShowWindow(hwnd,SW_SHOWNORMAL); //窗口創建完了,顯示它
UpdateWindow(hwnd); //更新窗口,讓窗口毫無延遲的顯示
return 0;
}
是不是出錯了,內存不能讀取,為什么了呢,因為你創建的窗口沒有消息處理函數,windows系統當然不允許這樣一個窗口存在,對按鍵,鼠標都沒有反應,這樣的窗口是沒有實際意義的。 wndcls.lpfnWndProc=NULL; //窗口消息處理函數,就是前面這句,必須要填
窗口過程(消息)處理函數,那這個函數是怎樣定義的呢,像WinMain一樣,它也有固定的格式。
窗口過程處理函數的格式:LRESULT CALLBACK WinSunProc(HWND wnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
下面的這個是一個窗口創建的完整例子:
#include "stdafx.h"
#include<windows.h>
LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
if(uMsg==WM_LBUTTONDOWN) MessageBox(NULL,"kdjfkdf","Kjdfkdfj",MB_OK); //處理鼠標按下消息,彈出消息框
return DefWindowProc(hwnd,uMsg,wParam,lParam); //未處理的消息通過DefWindowProc函數交給系統處理
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASS wndcls; //定義一個存儲窗口信息WNDCLASS變量
wndcls.cbClsExtra=0; //默認為0
wndcls.cbWndExtra=0; //默認為0
wndcls.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //背景畫刷
wndcls.hCursor=LoadCursor(NULL,IDC_ARROW); //光標
wndcls.hIcon=LoadIcon(NULL,IDI_ERROR); //窗口圖標
wndcls.hInstance=hInstance; //應用程序實例句柄由WinMain函數傳進來
wndcls.lpfnWndProc=WinSunProc; //窗口消息處理函數
wndcls.lpszClassName="windowclass"; //窗口類名
wndcls.lpszMenuName=NULL; //窗口菜單名,沒有菜單,為NULL
wndcls.style=CS_HREDRAW | CS_VREDRAW; //窗口類型,CS_HREDRAW和CS_VERDRAW 表明
//當窗口水平方向垂直方向的寬度變化時重繪整個窗口
RegisterClass(&wndcls); //把窗口信息提交給系統,注冊窗口類
HWND hwnd; //用以存儲CreateWindow函數所創建的窗口句柄
hwnd=CreateWindow("windowclass","first windows",
WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL); //創建窗口
ShowWindow(hwnd,SW_SHOWNORMAL); //窗口創建完了,顯示它
UpdateWindow(hwnd); //更新窗口,讓窗口毫無延遲的顯示
MSG msg; //消息結構類型
while(GetMessage(&msg,NULL,0,0)) //獲取消息
{
//TranslateMessage(&msg); //此函數用於把鍵盤消息(WM_KEYDOWN,WM_KEYUP)轉換成字符消息WM_CHAR
DispatchMessage(&msg); //這個函數調用窗口過程處理函數,並把MSG里的信息處理后傳給過程函數的四個參數
}
return 0;
}
WinSunProc函數的四個參數,分別對應着SendMessage函數四個參數,詳情參見SendMessage函數參數解釋。
MSG類型解釋 :
結構定義:
typedef struct tagMSG
{
HWND hwnd; //hwnd表示消息將要發送給的窗口句柄
UINT message; //消息類型,如WM_WMCLOSE,WM_CHAR,WM_LBUTTONDOWN,參見消息表
WPARAM wParam; //消息附帶信息,取值的意思具體依據消息類型而定
LPARAM lParam; //消息附帶信息,取值的意思具體依據消息類型而定
DWORD time; //消息的發送時間,不常用
POINT pt; //消息發送時,鼠標所在的位置,不常用
}MSG;
大家試着把上面的例子運行一遍,然后關掉窗口,再運行一遍,是不是出錯了,因為前一個程序雖然窗口關閉了,但進程還在運行,還記得那個循環語句嗎?while(GetMessage(&msg,NULL,0,0))就是這個。只要條件成立,進程就會一直運行下去。如何讓這個循環結束呢?用 PostQuitMessage(0); 這個語句就行了,參數0表示給自身窗口發送一個退出消息,當GetMessage函數接到PostQuitMessage函數發出的消息后,就會返回0值。
如在窗口過程函數中處理窗口關閉WM_CLOSE消息:if(uMsg==WM_CLOSE)PostQuitMessage(0); 這樣只要一關閉窗口,它的進程也會結束。
接下來解釋一下CreateWindow函數參數的意思,函數定義
HWND CreateWindow(LPCTSTR lpClassName, //窗口類名,應與WNDCLASS結構里的成員lpszClassName一致
LPCTSTR lpWindowName,, //窗口標題名
DWORD dwStyle, //窗口的風格,取值參見表Style
int x,
int y, //x,y表示所創建窗口左上角位置
int nWidth,
int nHeight, //nWidth,nHeight表示窗口的寬高
HWND hWndParent, //父窗口句柄,如果不是子窗口,這里取值為NULL
HMENU hMenu, //菜單句柄,沒菜單的話,取NULL值
HANDLE hlnstance, //對應着WinMain函數的第一個參數
LPVOID lpParam); //NULL
表Style:(參考:百度)
WS_BORDER:創建一個單邊框的窗口。
WS_CAPTION:創建一個有標題框的窗口(包括WS_BODER風格)。
WS_CHILD:創建一個子窗口。這個風格不能與WS_POPUP風格合用。
WS_CHLDWINDOW:與WS_CHILD相同。
WS_CLIPCHILDREN:當在父窗口內繪圖時,排除子窗口區域。在創建父窗口時使用這個風格。
WS_CLlPBLINGS;排除子窗口之間的相對區域,也就是,當一個特定的窗口接收到WM_PAINT消息時,WS_CLIPSIBLINGS 風格將所有層疊窗口排除在繪圖之外,只重繪指定的子窗口。如果未指定WS_CLIPSIBLINGS風格,並且子窗口是層疊的,則在重繪子窗口的客戶區時,就會重繪鄰近的子窗口。
WS_DISABLED:創建一個初始狀態為禁止的子窗口。一個禁止狀態的窗口不能接受來自用戶的輸入信息.
WS_DLGFRAME:創建一個帶對話框邊框風格的窗口。這種風格的窗口不能帶標題條。
WS_GROUP:指定一組控制的第一個控制。這個控制組由第一個控制和隨后定義的控制組成,自第二個控制開始每個控制,具有WS_GROUP風格,每個組的第一個控制帶有WS_TABSTOP風格,從而使用戶可以在組間移動。用戶隨后可以使用光標在組內的控制間改變鍵盤焦點。
WS_HSCROLL:創建一個有水平滾動條的窗口。
WS_ICONIC:創建一個初始狀態為最小化狀態的窗口。
與WS_MINIMIZE風格相同。
WS_MAXIMIZE:創建一個初始狀態為最大化狀態的窗口。
WS_MAXIMIZEBOX:創建一個具有最大化按鈕的窗口。該風格不能與WS_EX_CONTEXTHELP風格同時出現,同時必須指定WS_SYSMENU風格。
WS_OVERLAPPED:產生一個層疊的窗口。一個層疊的窗口有一個標題條和一個邊框。與WS_TILED風格相同。 WS_OVERLAPPEDWINDOW:創建一個具有WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU WS_THICKFRAME,WS_MINIMIZEBOX,WS_MAXIMIZEBOX風格的層疊窗口,與WS_TILEDWINDOW風格相同。 WS_POPUP;創建一個彈出式窗口。該風格不能與WS_CHLD風格同時使用。
WS_POPUWINDOW:創建一個具有WS_BORDER,WS_POPUP,WS_SYSMENU風格的窗口,WS_CAPTION和WS_POPUPWINDOW必須同時設定才能使窗口某單可見。
WS_SIZEBOX:創建一個可調邊框的窗口,與WS_THICKFRAME風格相同。
WS_SYSMENU:創建一個在標題條上帶有窗口菜單的窗口,必須同時設定WS_CAPTION風格。
WS_TABSTOP:創建一個控制,這個控制在用戶按下Tab鍵時可以獲得鍵盤焦點。按下Tab鍵后使鍵盤焦點轉移到下一具有WS_TABSTOP風格的控制。
WS_THICKFRAME:創建一個具有可調邊框的窗口,與WS_SIZEBOX風格相同。
WS_TILED:產生一個層疊的窗口。一個層疊的窗口有一個標題和一個邊框。
與WS_OVERLAPPED風格相同。
WS_TILEDWINDOW:創建一個具有WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU, WS_THICKFRAME,WS_MINIMIZEBOX,WS_MAXMIZEBOX風格的層疊窗口。與WS_OVERLAPPEDWINDOW風格相同。
WS_VISIBLE創建一個初始狀態為可見的窗口。
WS_VSCROLL:創建一個有垂直滾動條的窗口。
第二十六個LoadCursorFromFile從磁盤加載一個光標文件,函數返回該光標句柄
假設e盤下有一個名為a.cur的光標文件。
HCURSOR cursor //定義一個光標句柄,用於存放LoadCursorFromFile函數返回的光標句柄
cursor=LoadCursorFromFile("e:\\a.cur");
獲得了光標句柄有什么用呢?看一下窗口類WNDCLASS里的hCursor成員,這個成員也是一個光標句柄,明白了吧!
第二十七個CreateSolidBrush創建一個畫刷,函數返回畫刷句柄
HBRUSH hbr=CreateSolidBrush(RGB(12,172,59));//三個數字分別表明RGB的顏色值,RGB根據三種顏色值返回一個COLORREF類型的值
第二十八個LoadImage裝載位圖、圖標、光標函數
函數定義:HANDLE LoadImage(HINSTANCE hinst,LPCTSTR lpszName,UINT uType,int cxDesired,int CyDesired,UINT fuLoad)
這里我們只要這個函數的幾個簡單功能:從磁盤加載位圖,從磁盤加載圖標,從磁盤加載光標。所以第一個參數hinst我們不用管它,直接填NULL就行,第二個參數lpszName是圖片文件所在路徑名,第三個參數uType指明要加載的是什么類型的圖片,
是位圖(填IMAGE_BITMAP),還是光標(填IMAGE_CURSOR),還是圖標(填IMAGE_ICON)。第四個cxDesired和第五個參數CyDesired,指定要加載的圖片的寬高(可以放大光標,或者縮小),如果加載的是位圖的話,則兩個參數必須為0,第六個參數fuLoad表示以何種方式加載文件,這里我們是從磁盤加載文件,所以填LR_LOADFROMFILE;
好了,假設e盤下有一個c.cur和i.ico文件。例子:設置窗口圖標和光標,還有背景色
#include "stdafx.h" //這個頭文件是編譯器自動生成的,不是空工程,都會有,
//如果是直接建C++源文件,包含這個頭文件,會出錯
#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WinSunProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
); //窗口過程函數聲明
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
//設計一個窗口類
WNDCLASS wndcls;
wndcls.cbClsExtra=0;
wndcls.cbWndExtra=0;
wndcls.hbrBackground=CreateSolidBrush(RGB(12,172,59)); //畫刷
wndcls.hCursor=(HCURSOR)LoadImage(NULL,"e:\\c.cur",IMAGE_CURSOR,24,24,LR_LOADFROMFILE); //加載光標
wndcls.hIcon=(HICON)LoadImage(NULL,"e:\\i.ico",IMAGE_ICON,48,48,LR_LOADFROMFILE); //加載圖標
wndcls.hInstance=hInstance; //應用程序實例句柄由WinMain函數傳進來
wndcls.lpfnWndProc=WinSunProc; //定義窗口處理函數
wndcls.lpszClassName="windowclass";
wndcls.lpszMenuName=NULL;
wndcls.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
//創建窗口,定義一個變量用來保存成功創建窗口后返回的句柄
HWND hwnd;
hwnd=CreateWindow("windowclass","first window",
WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);
//顯示及刷新窗口
ShowWindow(hwnd,SW_SHOWNORMAL);
UpdateWindow(hwnd);
//定義消息結構體,開始消息循環
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//編寫窗口過程函數
LRESULT CALLBACK WinSunProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch(uMsg)
{
case WM_CHAR: //字符消息
char szChar[20];
sprintf(szChar,"char code is %c",wParam);
MessageBox(hwnd,szChar,"char",0);
break;
case WM_LBUTTONDOWN: //鼠標左鍵按下消息
MessageBox(hwnd,"mouse clicked","message",0);
break;
case WM_CLOSE:
if(IDYES==MessageBox(hwnd,"是否真的結束?","message",MB_YESNO))
{
DestroyWindow(hwnd); //銷毀窗口,並發送WM_DESTROY消息給自身窗口
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
第二十九個GetDC根據窗口句柄獲取設備上下文(DC)返回DC句柄
得到了一個窗口的設備上下文,就可以進行畫圖操作了,像畫圓,畫正方形,顯示圖片等函數都是要設備上下文(DC)句柄做參數的。
HDC dc //定義一個DC句柄
HWND wnd=FindWindow(NULL,"無標題.txt - 記事本"); //獲取窗口句柄
dc=GetDC(wnd) //獲取這個窗口的設備上下文
第三十個Rectnagle在窗口中畫一個矩形
以"無標題.txt - 記事本"窗口為例,在這個窗口簡單的畫一個矩形
#include<windows.h>
void main()
{
HDC dc;
HWND wnd=FindWindow(NULL,"無標題.txt - 記事本");
dc=GetDC(wnd); //獲取窗口設備上下文(DC)
while(1) //用循環語句重復畫,是為了確保不會被窗口刷新給刷掉
{
Rectangle(dc,50,50,200,200); //畫一個矩形
Sleep(200);
}
}
第三十一個Process32First根據CreateToolhelp32Snapshot函數返回的句柄獲取進程信息
結合Process32Next函數使用,有點像文件尋找函數。
看完整例子:顯示系統進程名,以及進程ID號
#include<windows.h>
#include<tlhelp32.h> //聲明快照函數的頭文件
#include<stdio.h>
int main()
{
PROCESSENTRY32 pe32; //進程的信息將會存儲在這個結構里
//在使用這個結構之前,先設置它的大小
pe32.dwSize=sizeof(pe32);
//給系統內的所有進程拍一個快照
HANDLE hProcessSnap=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
BOOL bMore=::Process32First(hProcessSnap,&pe32); //第一次查找
while(bMore)
{
printf("進程名稱:%s\n",pe32.szExeFile); //szExeFile是進程名
printf("進程ID號:%u\n\n",pe32.th32ProcessID); //th32ProcessID是進程ID號
bMore=::Process32Next(hProcessSnap,&pe32); //尋找下個進程,函數返回0,則沒有進程可尋
}
return 0;
}
第三十二個OpenProcess根據進程ID號獲得進程句柄,句柄通過函數返回
函數定義:HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
第一個參數不要管它,填PROCESS_ALL_ACCESS,第二個參數也一樣,填FALSE,那最后一個參數就是進程ID號。
第三十三個TerminateProcess結束一個進程(需進程句柄做參數)
該函數只有兩個參數,第一個是進程句柄,第二個填0就行了。
現在給個例子:假設當前有一個進程名為abc.exe的進程正在運行,編一個程序結束它。
#include<windows.h>
#include<tlhelp32.h> //聲明快照函數的頭文件
int main(int argc,char *argv[])
{
PROCESSENTRY32 pe32;
//在使用這個結構之前,先設置它的大小
pe32.dwSize=sizeof(pe32);
//給系統內的所有進程拍一個快照
HANDLE hProcessSnap=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
//遍歷進程快照,輪流顯示每個進程的信息
BOOL bMore=::Process32First(hProcessSnap,&pe32);
while(bMore)
{
if(strcmp("abc.exe",pe32.szExeFile)==0) //如果找到進程名為abc.exe
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pe32.th32ProcessID); //獲取句柄
::TerminateProcess(hProcess,0); //結束它
}
bMore=::Process32Next(hProcessSnap,&pe32); //尋找下一個
}
return 0;
}
上面的這個例子,只能結束普通權限進程,如果為系統進程的話,則沒有用,結束不了。在后面的提升權限函數,會有例子說明如何結束系統進程。
第三十四個CreatePen創建一個畫筆(返回畫筆句柄)
函數定義:BOOL CreatePen(int nPenStyle, int nWidth, COLORREF crColor);
第一個參數,表示是什么類型的線,取值有以下:
如創建一個畫筆:HPEN pen=CreatePen(PS_SOLID,3,RGB(255,78,99));
PS_SOLID 畫筆畫出的是實線 PS_DASH 畫筆畫出的是虛線(nWidth必須是1) PS_DOT 畫筆畫出的是點線(nWidth必須是1)
PS_DASHDOT 畫筆畫出的是點划線(nWidth必須是1) PS_DASHDOTDOT 畫筆畫出的是點-點-划線(nWidth必須是1)
第二個參數是畫筆的寬度,第三個參數是畫筆的顏色,COLORREF類型可以RGB來獲得如RGB(233,128,88);分別是紅綠藍。
第三十五個CreateSolidBrush創建一個畫刷
只有一個COLORREF類型的參數
HBRUSH brush=CreateSolidBrush(RGB(22,182,111));
第三十六個SelectObject把GDI對象選入相應的DC中
像畫筆(句柄HPEN),畫刷(HBURSH),位圖(HBITMAP)等都是GID對象。因為畫圖函數,如畫圓,畫矩形,畫直線,它們所畫出圖形,默認屬性都是不變的,如線的寬度。那么想要改變畫出來時線的寬度,比如我想畫出來的圖形它的線條寬度為5(像素),那么就要創建一個寬度為5的畫筆,然后再通過SelectObject函數,給這個畫筆選入,就可以了.
接下舉個例子:SelectObject應用
#include "stdafx.h"
#include<windows.h>
LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
static HPEN pen=CreatePen(PS_SOLID,3,RGB(255,78,99)); //創建畫筆
static HBRUSH brush=CreateSolidBrush(RGB(22,182,111)); //創建畫刷
if(uMsg==WM_PAINT) //窗口需要重畫的時候
{
HDC hDC;
PAINTSTRUCT ps;
hDC=BeginPaint(hwnd,&ps); //BeginPaint只能在響應WM_PAINT,不能用GetDC獲取設備上下文
SelectObject(hDC,pen); //選入畫筆
SelectObject(hDC,brush); //選入畫刷
Rectangle(hDC,100,100,200,200);
EndPaint(hwnd,&ps);
}
else if(uMsg==WM_CLOSE) //用戶關閉了窗口
DestroyWindow(hwnd); //銷毀窗口,並發送WM_DESTROY消息
else if(uMsg==WM_DESTROY) //如果窗口被銷毀
PostQuitMessage(0); //讓進程退出
return DefWindowProc(hwnd,uMsg,wParam,lParam); //未處理的消息通過DefWindowProc函數交給系統處理
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASS wndcls; //定義一個存儲窗口信息WNDCLASS變量
wndcls.cbClsExtra=0; //默認為0
wndcls.cbWndExtra=0; //默認為0
wndcls.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //背景畫刷
wndcls.hCursor=LoadCursor(NULL,IDC_ARROW); //光標
wndcls.hIcon=LoadIcon(NULL,IDI_ERROR); //窗口圖標
wndcls.hInstance=hInstance; //應用程序實例句柄由WinMain函數傳進來
wndcls.lpfnWndProc=WinSunProc; //窗口消息處理函數
wndcls.lpszClassName="windowclass"; //窗口類名
wndcls.lpszMenuName=NULL; //窗口菜單名,沒有菜單,為NULL
wndcls.style=CS_HREDRAW | CS_VREDRAW; //窗口類型,CS_HREDRAW和CS_VERDRAW 表明
//當窗口水平方向垂直方向的寬度變化時重繪整個窗口
RegisterClass(&wndcls); //把窗口信息提交給系統,注冊窗口類
HWND hwnd; //用以存儲CreateWindow函數所創建的窗口句柄
hwnd=CreateWindow("windowclass","first windows",
WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL); //創建窗口
ShowWindow(hwnd,SW_SHOWNORMAL); //窗口創建完了,顯示它
UpdateWindow(hwnd); //更新窗口,讓窗口毫無延遲的顯示
MSG msg; //消息結構類型
while(GetMessage(&msg,NULL,0,0)) //獲取消息
{
//TranslateMessage(&msg); //此函數用於把鍵盤消息(WM_KEYDOWN,WM_KEYUP)轉換成字符消息WM_CHAR
DispatchMessage(&msg); //這個函數調用窗口過程處理函數,並把MSG里的信息處理后傳給過程函數的四個參數
}
return 0;
}
第三十七個 ReadProcessMemory根據進程句柄讀取相應的一段內存(讀其它進程里的內存)
函數定義:BOOL ReadProcessMemory(HANDLE hProcess,PVOID pvAddressRemote,PVOID pvBufferLocal,DWORD dwSize,
PDWORD pdwNumBytesRead);總共四個參數
第一個參數hProcess是遠程進程句柄,被讀取者 。第二個pvAddressRemote是遠程進程中內存地址。 從具體何處讀取
pvBufferLocal是本地進程中內存地址. 函數將讀取的內容寫入此處 ,dwSize是要讀取的字節數。要讀取多少
pdwNumBytesRead是實際讀取的內容(函數執行后,實際讀了多少字節,將存儲在該變量里)
遠程進程的內存地址是什么意思呢,比如我現在定義一個變量a,int a;就是了,大家知道int型是占四個字節的,也就是說如果a變量所占的內存起始地址是0x1234,那么變量a就占用0x1234,0x1235,0x1236,0x1237這四個字節,這四個字節的內容決定了a變量的值。
好了知道了這個,我們就來舉個例子,讀取另一個進程里一個變量的值:需設計兩個程序,一個用於讀(Read)一個用於被讀(BeRead);
那么要如何獲得另一個進程中一個變量的地址呢?這里我們用一個簡單的方法,讓另一個進程自己去獲取,然后輸出地址值。
被讀的程序代碼如下:假設該進程名為:BeRead.exe
#include<stdio.h>
int main()
{
int a=10; //要讀取的變量。
printf("%x\n",&a); //輸出這個變量的起始地址,假設輸出為12ff7c
while(1)
{
Sleep(1000);
}
return 0;
}
必須先讓這個程序運行,然后根據輸出的地址值,才能在下面的程序填入地址值。
讀取的程序代碼如下:
#include<windows.h>
#include<stdio.h>
#include<tlhelp32.h>
int main()
{
//先要獲取進程句柄,如何獲取,參照TerminateProcess函數,結束一個進程
HANDLE ReProcess;
PROCESSENTRY32 pe32;
pe32.dwSize=sizeof(pe32);
HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
BOOL bMore=::Process32First(hProcessSnap,&pe32);
while(bMore)
{
if(strcmp(pe32.szExeFile,"BeRead.exe")==0) //如果是BeRead.exe
{
ReProcess=::OpenProcess(PROCESS_ALL_ACCESS,FALSE,pe32.th32ProcessID); //獲取該進程句柄
break;
}
bMore=Process32Next(hProcessSnap,&pe32);
}
int *ReAddress=(int *)0x12ff7c; //要讀取的內存的地址值
int *p=new int;
unsigned long size;
ReadProcessMemory(ReProcess,ReAddress,p,4,&size); //讀取BeRead進程的內存
printf("%d\n",*p); //輸出讀取來的值
return 0;
}
第三十八個WriteProcessMemory根據進程句柄寫入相應的一段內存(寫入其它進程里的內存)
這個函數里的參數跟ReadProcessMemory函數參數意思一樣,只不過一個是寫,一個是讀。
下面直接舉個例子,形式跟讀內存函數的例子一樣。
被寫的程序代碼如下:假設該進程名為:BeWrite.exe
#include<stdio.h>
int main()
{
int a=10;
printf("%x\n",&a); //假設輸出為12ff7c
while(1)
{
printf("%d\n",a); //每隔一秒輸出,查看值有沒有改變
Sleep(1000);
}
return 0;
}
寫入的代碼如下:
#include<windows.h>
#include<stdio.h>
#include<tlhelp32.h>
int main()
{
HANDLE ReProcess;
PROCESSENTRY32 pe32;
pe32.dwSize=sizeof(pe32);
HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
BOOL bMore=::Process32First(hProcessSnap,&pe32);
while(bMore)
{
if(strcmp(pe32.szExeFile,"BeWrite.exe")==0)
{
ReProcess=::OpenProcess(PROCESS_ALL_ACCESS,FALSE,pe32.th32ProcessID);
break;
}
bMore=Process32Next(hProcessSnap,&pe32);
}
int *ReAddress=(int *)0x12ff7c;
int *p=new int;
*p=300;
unsigned long size;
WriteProcessMemory(ReProcess,ReAddress,p,4,&size);
return 0;
}
第三十九個CreateThread創建一個線程(多線程)
線程是什么意思呢,代碼是由線程來執行的,一個程序默認只有一個線程(主線程),打個比方,線程就好比一個人,而不同功能的代碼或函數就好是一件件不同的事情,如洗碗,洗衣服,擦地。一個人要把這幾種事情做完,可以有好幾種方案,第一種就是,洗完碗,就去洗衣服,衣服洗完了,再去擦地。第二種就是:洗一分鍾碗,再去洗一分鍾衣服,再去擦一分鍾,然后又去洗一分鍾衣服.......直到做完。好了,現在你可以再創造一個人幫你做事,創造這個人后,你就叫他洗衣服,而你就洗碗,這樣兩件事就可以同時被做了。而這里的創造一個人指的就是CreateThread函數。
函數定義:HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);
該函數有六個參數,第一個參數不用管它,填NULL,第二個參數dwStackSize用於新線程的初始堆棧大小,默認為0,第三個lpStartAddress填函數名(指標),但這個函數必須是這種固定格式的DWORD _stdcall ThreadProc(LPVOID lpParameter),新的線程將會執行這個函數里面的代碼,直到函數結束,線程死亡。第四個lpParameter是一自定義參數,用戶可以通過這個參數,傳遞需要的類型,這個參數與線程函數的參數相對應。第五個dwCreationFlags填0表示立即執行,如果是CREATE_SUSPENDED表示掛起,直到用ResumeThread函數喚醒。第六個lpThreadId填NULL就行了。
現舉個例子,兩個線程同時每隔一秒輸出一個數字,也就是一秒會有兩數字輸出。
#include<windows.h>
#include<stdio.h>
DWORD _stdcall ThreadProc(LPVOID lpParameter) //線程執行函數
{
int si=100;
while(si>0)
{
printf("子線程輸出數字:%d\n",si--);
Sleep(1000);
}
return 0;
}
int main()
{
int mi=0;
CreateThread(NULL,0,ThreadProc,NULL,0,NULL); //創建一個線程,去執行ThreadProc函數
while(mi<100)
{
printf("主線程輸出數字:%d\n",mi++);
Sleep(1000);
}
return 0;
}
第四十個GetCurrentProcessId獲得當前進程ID
DWORD currentPID;
currentPID=::GetCurrentProcessId(); //返回進程ID號
cout<<currentPID<<endl;
第四十一個CreateCompatibleDC創建一個兼容的內存設備上下文(DC)
簡單的來說,就是復制一個模一樣的DC。就把窗口看成一幅幅圖畫,窗口有大有小,里面的內容也不一樣(顏色值),每個像素點的顏色值可能不一樣,所以就用設備上下文來描述每個窗口的信息,對於DC具體是怎樣描述設備上下文的,我們暫時還不需要知道,只要了解這個概念就行了。這個窗口信息,獲得一個窗口設備上下文,就用GetDC函數就行了,如HDC hDC=GetDC(hWnd);而CreateCompatibleDC的作用是根據一個設備上下文,再創建一個兼容的設備上下文,如 HDC mDC=CreateCompatibleDC(hDC)。這樣mDC里的信息就跟hDC里的一樣,那這有什么用呢?這個將會在后面的BitBltl輸出一個位圖(合並兩個DC)函數里會用到。
第四十二個GetObject獲取一個對象信息(如位圖,圖標,光標)
函數定義:int GetObject(HGDIOBJ hgdiobj, int cbBuffer, LPVOID lpvObject);
第一個參數hgdiobj是對象句柄,第二個參數cbBuffer是待寫入lpvObject指針指向緩存區數據大小,第三個參數lpvObject是一個指針,指向一個緩存區。
這里舉一個獲取位圖的信息,獲取位圖的大小,假設E盤下有一個aa.bmp的位圖文件,輸出位圖的寬高
#include<windows.h>
#include<stdio.h>
int main()
{
BITMAP bmInfo; //這個結構存儲位圖信息
HBITMAP bmp;
bmp=(HBITMAP)LoadImage(NULL,"e:\\aa.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
GetObject(bmp,sizeof(BITMAP),&bmInfo);
printf("位圖寬:%d,位圖高:%d\n",bmInfo.bmWidth,bmInfo.bmHeight);
return 0;
}
第四十三個BitBlt在窗口輸出一個位圖
其實倒不如說這個BitBlt函數是拷貝一個設備上下文(DC),或者合並兩個窗口,再延伸一下,合並兩個圖片?也並無不可,往大了說,窗口難道不是圖片嗎?用截屏軟件,把窗口截成圖片,這樣窗口便成了圖片。可能有點瞎說,大家還是按照標准來吧,反正,你只要掌握這個函數就行了,而且這個概念也不會有什么影響,那就足夠了。
BitBlt的作用跟把兩幅圖片合在一起一樣,合並兩幅圖片。可能兩幅圖片大小不一樣也可以合並,但合並DC就不行了,必須兩個信息一樣的DC才可以合並,那要如何確保兩個DC一樣呢?這就要用到CreateCompatibleDC函數了。
函數定義:BOOL BitBlt(HDC hdcDest,int nXDest,int nYDest,int nWidth,int nHeight,HDC hdcSrc,int nXSrc,int nYSrc,DWORD dwRop);
第一個參數hdcDest是原DC句柄,被覆蓋的DC,nXdest,nYDest,nWidth,nHeight這四個參數,指明了一個矩形,覆蓋原DC哪塊區域。
第六個參數hdcSrc是覆蓋的DC句柄,nXSrc,nYSrc參數指明從哪里開始覆蓋。(覆蓋DC的左上角),第九個參數dwPop表示以何種方式覆蓋。因為這里我們只要輸出一個位圖,所以用SRCCOPY,直接覆蓋。
好了,直接舉個例子,在窗口輸出一副圖片,假設e盤下有一個aa.bmp的位圖。為了方便,我們直接在記事本窗口輸出位圖,先運行一個窗口名為"無標題.txt - 記事本"記事本窗口程序。
#include<windows.h>
#include<stdio.h>
int main()
{
BITMAP bmInfo; //這個結構存儲位圖信息
HBITMAP bmp;
bmp=(HBITMAP)LoadImage(NULL,"e:\\aa.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
GetObject(bmp,sizeof(BITMAP),&bmInfo); //獲取位圖信息
HWND wnd=FindWindow(NULL,"無標題.txt - 記事本");
HDC hdc=GetDC(wnd);
HDC memDC=::CreateCompatibleDC(hdc); //創造兼容的DC
SelectObject(memDC,bmp); //選入位圖
while(1)
{
BitBlt(hdc,0,0,bmInfo.bmWidth,bmInfo.bmHeight,memDC,0,0,SRCCOPY); //輸出位圖
Sleep(200);
}
return 0;
}
下面介紹一下BitBlt函數最后一個參數的常用取值及意思。
參考(百度)
BLACKNESS:表示使用與物理調色板的索引0相關的色彩來填充目標矩形區域,(對缺省的物理調色板而言,該顏色為黑色)。
DSTINVERT:表示使目標矩形區域顏色取反。
MERGECOPY:表示使用布爾型的AND(與)操作符將源矩形區域的顏色與特定模式組合一起。 MERGEPAINT:通過使用布爾型的OR(或)操作符將反向的源矩形區域的顏色與目標矩形區域的顏色合並。 NOTSRCCOPY:將源矩形區域顏色取反,於拷貝到目標矩形區域。
NOTSRCERASE:使用布爾類型的OR(或)操作符組合源和目標矩形區域的顏色值,然后將合成的顏色取反。 PATCOPY:將特定的模式拷貝到目標位圖上。
PATPAINT:通過使用布爾OR(或)操作符將源矩形區域取反后的顏色值與特定模式的顏色合並。然后使用OR(或)操作符將該操作的結果與目標矩形區域內的顏色合並。
PATINVERT:通過使用XOR(異或)操作符將源和目標矩形區域內的顏色合並。
SRCAND:通過使用AND(與)操作符來將源和目標矩形區域內的顏色合並。
SRCCOPY:將源矩形區域直接拷貝到目標矩形區域。
SRCERASE:通過使用AND(與)操作符將目標矩形區域顏色取反后與源矩形區域的顏色值合並。 SRCINVERT:通過使用布爾型的XOR(異或)操作符將源和目標矩形區域的顏色合並。
SRCPAINT:通過使用布爾型的OR(或)操作符將源和目標矩形區域的顏色合並。
WHITENESS:使用與物理調色板中索引1有關的顏色填充目標矩形區域。(對於缺省物理調色板來說,這個顏色就是白色)
第四十四個GetWindowText根據窗口句柄獲得窗口標題名
函數定義:int GetWindowText(HWND hWnd,LPTSTR lpString,int nMaxCount);
第一個參數hWnd是要獲取窗口標題名的窗口句柄,第二個lpString是個字符串,窗口標題名,將會存儲在這里面,第三個參數nMaxCount指明了第二個參數字符數組的大小。
下面結合GetCursorPos和WindowFromPoint舉個例子,鼠標指向哪個窗口,就在界面顯示那窗口的標題名
#include<windows.h>
#include<stdio.h>
int main()
{
char Text[256]={0};
HWND wnd;
POINT curpos;
while(1)
{
GetCursorPos(&curpos);
wnd = WindowFromPoint(curpos);
GetWindowText(wnd,Text,256);
printf("%s\n",Text);
Sleep(300);
}
return 0;
}
第四十五個SetWindowText根據窗口句柄設置窗口標題名
這個函數有兩個參數,一個是窗口句柄,一個是標題名,這里就不需要解釋了吧,直接看例子,設置一個窗口標題名,依舊以
"無標題.txt - 記事本"為例。
#include<windows.h>
#include<stdio.h>
int main(int argc, char* argv[])
{
HWND wnd;
wnd=FindWindow(NULL,"無標題.txt - 記事本"); //獲取窗口句柄
SetWindowText(wnd,"新的窗口標題"); //設置窗口標題名
return 0;
}
第四十六個GetCurrentProcess獲得當前線程句柄
沒有參數,直接調用即可,該函數返回線程句柄
第四十七個OpenProcessToken獲得一個進程的訪問令牌句柄
獲得一個進程的訪問令牌有什么用呢?主要是為了修改它的權限,前面在介紹結束一個進程的時候說過了,無法結束系統進程,是什么原因呢,原因是調用OpenProcess函數失敗,無法獲取系統進程句柄而引起的,那為什么會失敗呢,權限不夠,普通程序的進程沒有SeDeDebug權限,而一個進程的權限是與訪問令牌相關的,這樣我們只要獲取一個進程的訪問令牌句柄,再以這個句柄為參數調用相應的函數提升進程的權限為SeDeDebug就可以獲取系統進程句柄,進而結束它。
函數定義:BOOL OpenProcessToken(HANDLE ProcessHandle,DWORD DesiredAccess,PHANDLE TokenHandle)
第一個參數ProcessHandle待獲取的進程句柄,第二個參數DesiredAccess操作類型,填TOKEN_ADJUST_PRIVILEGES就行了,
第三個TokenHandle是訪問令牌句柄的指針,該參數接收句柄。
如獲得本進程的訪問令牌句柄:HANDLE hToken;
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken);
第四十七個LookupPrivilegeValue函數查看對應系統權限的特權值,返回信息到一個LUID結構體里
上面講過了,進程有權限一說,那么大家也能猜到,進程權限的信息也一定存儲在一個結構體里,這個結構體描述了進程權限相關的一些信息。這個結構體在這里就不具體描述了,我們所要做的,只是把一個進程權限設置成SeDeDebug就行了,所以我們只要知道TOKEN_PRIVILEGES便是描述進程權限的結構體就可以了。而LookupPrivilegeValue函數是根據訪問令牌句獲取相應的權限信息嗎?
不是的。TOKEN_PRIVILEGES結構里的Privileges[0].Luid跟這個函數所查詢的東西相對應,也就是說,如果進程是SeDeDeBug權限,那Privileges[0].Luid的取值是怎樣的呢?用LookupPrivilegeValue函數便可以獲取其取值。
這個函數是這樣定義的:BOOL LookupPrivilegeValue(LPCTSTR lpSystemName,LPCTSTR lpName,PLUID lpLuid);
第一個參數lpSystemName通常都填NULL,本地系統調用,第二個參數lpName填要查詢的權限名,如要查詢的是SeDeDebug權限則取值是SE_DEBUG_NAME,第三個參數lpLuid接收其取值。
如LUID luid;LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid);
第四十八個AdjustTokenPrivileges調整一個進程的訪問令牌信息(權限)
函數定義:BOOL AdjustTokenPrivileges(HANDLE TokenHandle,BOOL DisableAllPrivileges,PTOKEN_PRIVILEGES NewState,DWORD BufferLength,PTOKEN_PRIVILEGES PreviousState,PDWORD ReturnLength)
第一個參數TokenHandle是令牌句柄,第二個是禁用所有權限標志,后面填FALSE就行了。第三個NewState是待刷進令牌句柄的PTOKEN_PRIVILEGES結構信息指針,第四個BufferLength指明TOKEN_PRIVILEGES結構大小,第五,六個參數填NULL就行了。
那么結束上面兩個函數,提升一個進程權限制,讓它能夠結束系統進程的代碼就是:
HANDLE hToken;
OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken );
TOKEN_PRIVILEGES tp;
LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid );
tp.PrivilegeCount = 1; //tp里其它一些屬性設置
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof( TOKEN_PRIVILEGES ), NULL, NULL );
只上把上面的代碼,加入結束普通進程例子的前面,那么就能結束系統進程了。
第四十九個LoadLibrary加載動態鏈接庫,返回動態鏈接庫模塊句柄
該函數只有一個參數,那就是動態鏈接庫的名稱,如user32.dll,函數返回HMOUDLE類型的模塊句柄,獲得了一個動態鏈接庫的模塊句柄,就可以調用GetProcAddress函數獲得模塊里面的函數地址,從而調用動態鏈接庫里的函數。
第五十個GetProcAddress根據模塊句柄獲取相應的函數地址
提到GetProcAddress函數,不得不講一下怎么設計一個動態鏈接庫,這里我們就以自己設計動態鏈接庫作為GetProcAddress函數的例子。
動態鏈接庫里的函數相對於頭文件的函數有什么優勢呢?更節省內存,相對於比較常用的函數而已。如果在一個程序里,調用一個頭文件里的函數的話,那不管如何,函數的代碼就會被復制一份到當前程序里,所以,當有十幾個程序調用同一個函數的時候,這個函數在內存中所占用的空間,就會有十幾份,分別在各自調用的進程內存空間里,而動態鏈接庫的函數,只在內存中有一份空間(公用空間)如果哪個程序要用到這個函數的話,只要給這個函數的地址,進程就可以跑到這個空間執行函數,那么如何獲取函數地址呢,用GetProcAddress函數就行了。
下面我們就自己設計一個動態鏈接庫,點“文件->新建->工程",然后選中“Win32 Dynamic-Link Library”,再在右邊給工程取一個名,點確定。接着彈出了一個對話框,詢問希望創建什么類型,我們選擇第二個“一個簡單的DLL工程”,點完成->確定.然后單擊右邊的“+”號,很小的一個,接着下面會出現一個Globals的"+"號,單擊該加號,然后再雙擊DllMain函數,進入代碼編輯區,在這里編寫代碼,這里已經有了一些代碼了,編譯器自動生成的。那個DllMain函數,便是動態鏈接庫的主函數。在程序進程加載動態鏈接的時候,進程會自動調用DllMain函數,也就是說會自動執行DllMain函數里的代碼,也就是說,如果哪程序執行了這個語句“LoadLibrar("user32.dll")",那么執行這個語句的進程,便會自動執行user32.dll里的DllMain函數。如果是主線程加載動態庫的話,那么該DllMain函數里的代碼會被執行兩次,分別是加載的時候執行一次,調用FreeLibrary函數釋放或程序結束自動釋放動態鏈接庫的時候執行一次,至於是什么原因導致DllMain函數被調用,DllMain函數的第二個參數ul_reason_for_call說明了原因,它有四個取值,代表了四個原因。分別是:
DLL_PROCESS_ATTACH(進程加載),DLL_THREAD_ATTACH (線程加載)
DLL_THREAD_DETACH(線程釋放),DLL_PROCESS_DETACH(進程釋放)
因為這里我們只要設計一個動態鏈接函數,所以便不用管DllMain函數,DllMain函數將會在介紹CreateRemoteThread(創建一個遠程線程)函數的時候講到,所以我們只要在DllMain函數外定義一個函數就行了。
那么在動態鏈接庫是如何定義函數呢?如果函數不需要導出的話,則跟普通函數定義沒什么兩樣,導出是什么意思,就是可以用GetProcAddress函數獲取地址的函數。那導出的函數要如何定義呢?
只要在函數前面加上extern "C" __declspec(dllexport)就行了,聲明導出函數,防止函數重命名。那么接下來就舉個例子。
動態鏈接里的代碼:
#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
extern "C" __declspec(dllexport) int Add(int a,int b)
{
return a+b;
}
點編譯執行,然后就會彈出一個調試對話框,直接點取消,接着便生成了動態鏈接庫DLL,然后到你的工程里把后綴名為dll的文件找到,
位置在MyProject\"你的工程名"\Debug下。接着把這個文件復制到要調用的工程下,或者直接復制C:\windows\system32目錄下。
假設這個文件名為"sss.dll",那么要調用里面的Add函數便是如下代碼:
HMODULE hmod=::LoadLibrary("sss.dll"); //獲取sss.dll的模塊,加載sss.dll動態鏈接庫
typedef int (*pAdd)(int a,int b); //定義一個對應的函數型,以便識別
pAdd add=(pAdd)GetProcAddress(hmod,"Add"); //獲取hmod模塊里的Add函數地址
int a=add(3,5); //調用模塊里的Add函數
第五十一個SetWindowsHookEx安裝一個鈎子
WINDOWS是基於消息的系統,鼠標移動,單擊,鍵盤按鍵,窗口關閉等都會產生相應的消息,那么鈎子是什么意思呢,它可以監控一個消息,比如在一個窗口里單擊了一下,首先獲得這個消息的,不是應用程序,而是系統,系統獲取這個消息后,就去查看這個消息是在哪個窗口產生的,找到窗口后,再把消息投遞到相應程序里的消息隊列里,這之間有一個傳遞過程,那么鈎子的作用就是在消息到達應用程序之前截獲它,鈎子可以關聯一個函數(鈎子處理函數),也就是說,如果對一個進程安裝了一個鈎子,進程再接收到相應在消息之前,會先去執行鈎子所關聯的函數,
先來看一下這個函數定義:
HHOOK WINAPI SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hmod,DWORD dwThreadId)
第一個參數idHook指明要安裝的鈎子類型,如WH_KEYBOARD(鍵盤鈎子),WH_MOUSE(鼠標鈎子),第二個參數是鈎子處理函數的地址,該函數必須是這種固定的格式:LRESULT WINAPI HookProc(int nCode,WPARAM wParam,LPARAM lParam)
第三個參數hmod是鈎子函數所在模塊的句柄,第四個參數dwThreadId是線程ID,待監視消息的ID,如果為0,則為全局鈎子,監視所有消息
好,接下來我們舉一個例子,鈎子類型為WH_KEYBOARD,全局鈎子。截獲鍵盤按鍵消息,並扔掉該消息,讓鍵盤失靈。
由於是裝的是全局鈎子,所以鈎子處理函數必須放在動態鏈接庫里。那么我們就設計一個動態鏈接庫吧。
現給出動態鏈接庫的所有代碼:(KeyDll.dll)
#include "stdafx.h"
#include<windows.h>
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
HMODULE WINAPI ModuleFromAddress(PVOID pv) //該函數根據內存地址,獲得其所在的模塊句柄
{
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pv,&mbi,sizeof(mbi));
return (HMODULE)mbi.AllocationBase;
}
LRESULT CALLBACK HookKey(int nCode,WPARAM wParam,LPARAM lParam)
{
return TRUE; //返回真,扔掉該消息
}
extern "C" __declspec(dllexport) void SetHook(void)
{
SetWindowsHookEx(WH_KEYBOARD,HookKey,ModuleFromAddress(HookKey),0);
}
生成dll文件后,把它復制到相應的目錄下去。
再新建一個工程,調用用動態鏈接庫里的函數,代碼如下:
#include<windows.h>
int main()
{
HMODULE hMod=LoadLibrary("KeyDll.dll");
typedef void(*pSetHook)(void);
pSetHook SetHook=(pSetHook)GetProcAddress(hMod,"SetHook");
SetHook();
while(1)
{
Sleep(1000); //避免程序結束,自動釋放動態鏈接庫
}
return 0;
}
這樣當按下了一個鍵后,接收該按鍵消息的進程,會先去執行鈎子處理函數,然后再處理消息,而鈎子處理函數的幾個參數說明了按鍵的詳細信息,如按了哪個鍵,是按下(KEYDOWN)還是松開(KEYUP)。如果有興趣的話,把上面那鈎子處理函數的代碼換成下面這個
LRESULT CALLBACK HookKey(int nCode,WPARAM wParam,LPARAM lParam)
{
char sz[25];
sprintf(sz,"%c",wParam); //這個函數頭文件#include<stdio.h>
MessageBox(NULL,sz,sz,MB_OK);
return FALSE;
}
每按下一個鍵,就會彈出一個提示框,並輸出所按下的鍵,只對字符鍵有用。
第五十二個SHGetFileInfo獲取一個文件的各項信息(文件關聯圖標,屬性等)
函數定義: DWORD SHGetFileInfo(LPCSTR pszPath, DWORD dwFileAttributes, SHFILEINFOA FAR *psfi, UINT cbFileInfo, UINT uFlags);
pszPath是文件的路徑,dwFileAttributes一般取0,如果想要獲取文件夾信息的話,則取值為FILE_ATTRIBUTE_DIRECTORY,psfi是一個SHFILEINFO結構的指針,該結構存儲文件信息,定義如下:
typedef struct _SHFILEINFOA
{
HICON hIcon; // 文件關聯圖標句柄
int iIcon; // 系統圖標列表索引
DWORD dwAttributes; // 文件的屬性
CHAR szDisplayName[MAX_PATH]; // 文件的路徑名
CHAR szTypeName[80]; // 文件的類型名,如是bmp文件,還是執行文件exe,或者其它
} SHFILEINFO;
第四個參數cbFileInfo指明SHFILEINFO結構的大小,填sizoef(SHFILEINFO);
最后一個參數uFlags指定獲取文件的什么信息,可選取值如下:(對應着SHFILEINFO里的成員)
SHGFI_ICON; //獲得圖標
SHGFI_DISPLAYNAME; //獲得顯示名
SHGFI_TYPENAME; //獲得類型名
SHGFI_USEFILEATTRIBUTES; //獲得屬性
SHGFI_LARGEICON; //獲得大圖標
SHGFI_SMALLICON; //獲得小圖標
SHGFI_PIDL; // pszPath是一個標識符
比如,我只要獲取文件圖標,那么參數填SHGFI_LARGEICON就行了。如果又想獲取文件關聯的圖標,又想獲取文件類型名,那么就是
SHGFI_LARGEICON|SHGFI_TYPENAME;
函數例子:
SHFILEINFO sfi;
SHGetFileInfo("e:\\aa.bmp",0,&sfi,sizeof(sfi),
SHGFI_ICON|SHGFI_LARGEICON|SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME);
接着可以用DrawIcon函數畫出文件關聯圖標:該函數定義:BOOL DrawIcon(HDC hDC,int X,int Y, HICON hlcon );
第五十三個RegCreateKeyEx在注冊表里創建一個子鍵,或獲取一個子鍵的句柄
在這里我們先來了解一下注冊表的基本概念,打開運行對話框,輸入regedit,然后回車,便打開了注冊表編輯器,首先映入眼前的,便是五個根鍵
HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USER
HKEY_CURRENT_CONFIG
在根鍵下面便是主鍵了,如HKEY_CURRENT_CONFIG根鍵下有兩個主鍵,分別是Software和System(可能會不一樣),那么主鍵下面是什么呢,對了,就是跟 RegCreateKeyEx函數相關的子鍵,子鍵下面就是具體的鍵值項了,但也可以又是子鍵。鍵值有五種可選類型,分別是:字符串值(REG_SZ),二進制值(REG_BINARY),DWORD值(REG_DWORD),多字符串值(REG_MULTI_SZ)和可擴充字符值(REG_EXPAND_SZ)。鍵值項還有其它信息,它的名稱,數據。
了解了上面這些東西,接着就來了解下RegCreateKeyEx函數的各個參數吧,先來看一下函數定義:
LONG RegCreateKeyEx (
HKEY hKey, //根鍵句柄,指明要在哪個根鍵下創建子鍵,填根鍵名既可
LPCSTR lpSubKey, //子鍵名,包含完整路徑名
DWORD Reserved,. //一般取0
LPSTR lpClass, //一般取NULL
DWORD dwOptions, //創建子鍵時的選項,可選值REG_OPTION_NON_VOLATILE,REG_OPTION_VOLATILE,這里取0既可
REGSAM samDesired, //打開方式,填KEY_ALL_ACCESS,在任何情況都行。
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指定繼承性,還是取0
PHKEY phkResult, //子鍵對應句柄,待創建或打開的子鍵句柄將存儲在該句柄里
LPDWORD lpdwDisposition //打開還是創建子鍵,對應REG_CREATED_NEW_KEY和REG_OPENED_EXISTING_KEY
);
在這里舉一個例子,以便我們能更好的理解該函數。
在HKEY_CURRENT_CONFIG根鍵下的Software主鍵里創建一個名為MySelf的子鍵。
#include<windows.h>
int main()
{
HKEY hroot; //子鍵句柄
DWORD dwDisposition; //對應着最后一個參數
RegCreateKeyEx(HKEY_CURRENT_CONFIG,"Software\\MySelf",0,NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);
return 0;
}
第五十四個RegSetValueEx根據子鍵句柄在其下創建或修改一個鍵值
函數定義:
LONG RegSetValueEx(
HKEY hKey, // 子鍵句柄
LPCTSTR lpValueName, // 鍵值名稱,如果提供的子鍵下沒有該名稱,則創建
DWORD Reserved, // 保留,填0
DWORD dwType, // 鍵值類型,
CONST BYTE *lpData, // 鍵值的數據
DWORD cbData // 鍵值的數據的大小
);
接着我們以增加開機自啟動為例,來看一下函數是如何創建一個鍵值的,我們知道,像程序添加開機自啟動一般都在
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run下添加一個鍵值,鍵值類型為二進制(REG_SZ),而鍵值的數據就為要自啟動程序的路徑。
假設e盤下有一個AutoRun.exe的應用程序,讓電腦開機時自動運行它。
#include<windows.h>
int main()
{
HKEY hroot; //子鍵句柄
DWORD dwDisposition;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",0,
NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);
RegSetValueEx(hroot,"AutoRun",0,REG_SZ,(BYTE *)"e:\\AutoRun.exe",sizeof("e:\\AutoRun.exe"));
return 0;
}
第五十五個RegDeleteValue根據子鍵句柄刪除其下的一個鍵值
這里直接舉一個例子,刪除RegSetValueEx函數創建的鍵值
#include<windows.h>
int main()
{
HKEY hroot; //子鍵句柄
DWORD dwDisposition;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",0,
NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition);
RegDeleteValue(hroot,"AutoRun"); //刪除子鍵下名為AutoRun的鍵值
return 0;
}
第五十六個RegQueryValueEx根據子鍵句柄獲取一個鍵值數據,類型。
函數定義:
LONG RegQueryValueEx (
HKEY hKey, //根鍵句柄
LPCWSTR lpValueName, //鍵值名稱
LPDWORD lpReserved, //預留,填0
LPDWORD lpType, //接收鍵值類型
LPBYTE lpData, //接收鍵值數據
LPDWORD lpcbData //接收數據的大小
);
例子,獲取RegSetValueEx函數創建的鍵值的類型,數據
int main()
{
char Data[52];
DWORD Size,Type;
HKEY hroot; //子鍵句柄
DWORD dwDisposition;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",0,
NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition); //獲取根鍵句柄
RegQueryValueEx(hroot,"AutoRun",0,&Type,(BYTE *)Data,&Size); //獲取AutoRun的信息
printf("鍵值名稱:AutoRun ");
switch(Type)
{
case REG_SZ:printf("鍵值類型:REG_SZ");break;
case REG_BINARY:printf("鍵值類型:REG_BINARY");break;
case REG_DWORD:printf("鍵值類型:REG_DWORD");break;
case REG_MULTI_SZ:printf("鍵值類型:REG_MULTI_SZ");break;
case REG_EXPAND_SZ:printf("鍵值類型:REG_EXPAND");break;
}
printf(" 鍵值數據:%s %d\n",Data,Size);
return 0;
}
第五十七個RegEnumValue根據子鍵句柄返回對應索引的鍵值信息(名稱,數據,類型,子鍵下第一個鍵值索引為0,以此類推,函數成功執行返回ERROR_SUCCESS)
函數定義:
LONG RegEnumValue (
HKEY hKey, //子鍵句柄
DWORD dwIndex, //鍵值索引
LPWSTR lpValueName, //接收鍵值名稱,字符數組
LPDWORD lpcbValueName, //指明數組大小
LPDWORD lpReserved, //預留,0
LPDWORD lpType, //鍵值類型,填NULL,不獲取
LPBYTE lpData, //鍵值數據,填NULL,不獲取
LPDWORD lpcbData //接收數據的大小,如果鍵值數據那項參數為NULL,則該項也為NULL
);
例子:輸出Run下的所有鍵值名
#include<windows.h>
#include<stdio.h>
int main()
{
char Name[52];
int Index=0;
DWORD dwSize=52;
DWORD Size,Type;
HKEY hroot; //子鍵句柄
DWORD dwDisposition;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",0,
NULL,0,KEY_ALL_ACCESS,NULL,&hroot,&dwDisposition); //獲取根鍵句柄
while(RegEnumValue(hroot,Index,Name,&dwSize,NULL,NULL,NULL,NULL)==ERROR_SUCCESS)
{
printf("%s\n",Name);
Index++; //索引從0開始每次自增一,函數如果執行失敗,則索引已到頭
}
return 0;
}
其實也還可以擴充一下,可以像msconfig程序那樣列出當前計算機的所有開機自啟動程序,當然,注冊表也不只就前面的那一個子鍵下可以添加自啟動程序,在HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run下也可以添加,所以這些子鍵都需要去查看,更多添加自啟動程序的子鍵可以到百度里去搜一下,大家如果掌握前面那幾個注冊表操作函數,可以結合起來試着做一個可以添加,查看,刪除開機自啟動程序的小程序。
第五十八個ExitWindowsEx關機,重啟,注銷計算機函數
這個函數只有兩個參數,后一個參數為系統預留,填0就可以了,而第一個參數則,指明關機,還是重啟,或注銷,可選值如下:
EWX_LOGOFF//注銷 EWX_REBOOT//重啟 NT系統中需SE_SHUTDOWN_NAME 特權 EWX_SHUTDOWN//關機,需權限。
例子:關閉計算機,由於需要SE_SHUTDOWN_NAME權限,所以我們得先提升權限,代碼如下:
#include<windows.h>
int main()
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken);
LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);
::ExitWindowsEx(EWX_SHUTDOWN,0);
return 0;
}
第五十九個VirtualAllocEx在其它的進程中分配內存空間
函數定義:
LPVOID VirtualAllocEx(
HANDLE hProcess, //進程句柄,將會在該進程句柄相關的進程分配空間
LPVOID lpAddress, //默認為系統指定,填NUL
DWORD dwSize, //分配多大的內存
DWORD flAllocationType, //填MEM_COMMIT
DWORD flProtect //指定分配的內存屬性,為PAGE_READWRITE,內存可讀寫
);
函數返回分配的內存首地址,
第六十個CreateRemoteThread創建一個遠程線程(在其它進程中創建線程)
函數定義:
HANDLE WINAPI
CreateRemoteThread(HANDLE hProcess, //進程句柄,函數將在這個進程句柄關聯的進程創建線程
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
這個函數比CreateThread函數多了一個參數,就是這個函數的第一個hProcess(函數在該進程里創建線程),后面的六個參數跟第三十九個函數CreateThread的六個參數一樣,這里就不再解釋了。
例子:遠程線程注入
創建一個遠程線程,就必須得有一個線程函數供線程執行,而線程函數又不能在其它程序里。那要怎么辦呢?大家看一下線程函數的定義,和LoadLibrary函數的定義,它們的定義相似,都是只有一個參數,而且每個程序都能調用LoadLibrary函數,這樣我們便能把LoadLibrary函數作為線程函數。這樣創建的線程就會去執行LoadLibrary函數。因而我們就有了一次讓其它程序調用LoadLibrar函數的機會,並還可以指定LoadLibrary函數的參數(通過創建遠程線程函數傳遞)。前面在動態鏈接庫提到,一個程序如果調用LoadLibrary函數,它都會自動去執行相應動態鏈接庫里的DllMain函數,所以我們自己可以編寫一個動態鏈接庫,在DllMain函數里寫入想要其它程序執行的代碼。再通過CreateRemoteThread函數在其它程序創建一個線程去執行LoadLibary加載我們已經編寫好的動態鏈接庫,這樣就可以讓其它程序執行我們的代碼了。這里還有一個問題,CreateRemoteThread函數傳遞過去的參數,因為要供注入的那個程序訪問,所以參數數據所存儲的空間不能在調用CreateRemoteThread函數的程序里。必須調用VirtualAllocEx函數,在注入程序里分配一個空間,把數據(動態鏈接庫的名稱)存在里面,而新分配空間的首地址則作為CreateRemoteThread函數的參數傳過去。這樣注入程序訪問的是自己的地址空間。
遠程線程注入:
假設動態鏈接庫為“ReCode.dll”它的代碼如下:
#include<windows.h>
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
) //DllMain函數,只要加載這個動態鏈接庫的程序,都會跑來執行這個函數
{ //在這里填讓其它程序執行的代碼
while(1)
{
MessageBox(NULL,"aaaa","aaaa",MB_OK); //簡單的讓其它程序每隔3秒彈出一個提示框
Sleep(3000);
}
return TRUE;
}
編譯運行,然后把生成的“ReCode.dll”文件復制到c:\\windows\\system23下去。
注入線程的代碼:
//選擇ctfmon.exe(輸入法管理)作為我們要注入進線程的程序
#include<windows.h>
#include<tlhelp32.h>
#include<stdio.h>
int main()
{
char DllName[25]="ReCode.dll";
HANDLE hProcess; //用於存儲ctfmon.exe的進程句柄
//先提升進程權限,使其能獲取任何進程句柄,並對其進行操作
HANDLE hToken;
OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken );
TOKEN_PRIVILEGES tp;
LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid );
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof( TOKEN_PRIVILEGES ), NULL, NULL );
////////////////////////////////////////////////////////////////////////////
//Process32First和Process32Next函數結合(尋找)獲取ctfmon.exe進程ID號
//再調用OpenProcess函數根據進程ID獲得進程句柄
PROCESSENTRY32 pe32; //進程相關信息存儲這個結構里
pe32.dwSize=sizeof(pe32);
//給系統內的所有進程拍一個快照
HANDLE hProcessSnap=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
BOOL bMore=::Process32First(hProcessSnap,&pe32);
while(bMore)
{
if(strcmp("ctfmon.exe",pe32.szExeFile)==0) //如果找到進程名為ctfmon.exe
hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pe32.th32ProcessID); //獲取句柄
bMore=::Process32Next(hProcessSnap,&pe32); //尋找下一個
}
//在ctfmon進程中分配空間
LPVOID lpBuf=VirtualAllocEx(hProcess,NULL,strlen(DllName),MEM_COMMIT, PAGE_READWRITE );
DWORD WrSize;
//把DllName里的數據寫入到分配的空間里
WriteProcessMemory(hProcess, lpBuf, (LPVOID)DllName, strlen(DllName), &WrSize);
//創建遠程線程
CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)LoadLibraryA,lpBuf,0,NULL);
return 0; //程序使命完成,結束
}
當然,給一個程序安裝鈎子,也可以讓指定的應用程序加載特定的動態鏈接庫,但要了解,加載動態鏈接庫的是是應用程序的主程序,你總不能讓應用程序不干它自己的事,而來一直執行DllMain函數里的代碼吧!而且即使這樣,當安裝鈎子的程序退出或卸載鈎子的時候,那么被系統強迫加載動態鏈接庫的程序,也會自動釋放動態鏈庫,退出DllMain函數。如此,那就沒有辦法了嗎?,辦法肯定是有的,用CreateThread函數。當其它程序主線程執行DllMain函數的時候,使其調用CreateThread再創建一個線程,就行了
第六十一個GetWindowThreadProcessId根據窗口句柄獲得對應進程ID號,和線程ID號
函數只有兩個參數,第一個參數是窗口句柄,第二個參數是一個DOWRD類型的指針,函數返回線程ID
如:
DWORD ThreadId,ProcessId;
ThreadId=GetWindowThreadProcessId(wnd,&ProcessId);
第六十二個EnumWindows枚舉當前正運行的所有主窗口,不包括子窗口
調用這個函數,還必須定義一個回調函數,它的格式是這樣的:BOOL CALLBACK lpEnumFunc(HWND wnd, LPARAM lParam);
EnumWindows函數有兩個參數,第一個就是回調函數的地址,另一個是自定義參數,對應着回調函數的第二個參數。
每枚舉一次窗口,這個回調函數就會被執行一次,而獲得的窗口句柄,就會傳遞給回調函數,對應着回調函數的第一個參數。直到枚舉完所有窗口。而在回調用函數里,返回真表示繼續枚舉,返回假則停止枚舉。
例子:枚舉窗口
#include<windows.h>
#include<stdio.h>
BOOL CALLBACK lpEnumFunc(HWND hwnd, LPARAM lParam);
int main()
{
::EnumWindows(lpEnumFunc,0);
return 0;
}
BOOL CALLBACK lpEnumFunc(HWND wnd, LPARAM lParam)
{
char WindowName[52]={0};
GetWindowText(wnd,WindowName,sizeof(WindowName)); //根據窗口句柄獲得窗口名
printf("窗口句柄:%d 窗口名稱:%s\n,",wnd,WindowName);
//可以在這里加個判斷當前是否有一個窗口正在運行
//如if(strcmp(WindowName,"Windows 任務管理器")==0) return 0;結束枚舉,
return 1;
}
第六十三個MessageBox彈出一個消息提示框
int MessageBox(
HWND hWnd , //指明消息框是屬於哪個窗口的,可以為NULL
LPCSTR lpText, //消息提示具體內容
LPCSTR lpCaption, //提示框窗口標題
UINT uType); //指明提示框類型,按鈕,圖標
這里只說下uType的常用可選值:
按鈕:
MB_ABORTRETRYIGNORE 終止 重試 忽略
MB_OK 確定
MB_OKCANCEL 確定 取消
MB_RETRYCANCEL 重試 取消
MB_YESNO 是 否
MB_YESNOCANCEL 是 否 取消
圖標:
MB_ICONWARNING 感嘆號
MB_ICONINFORMATION 提示i
MB_ICONQUESTION 問號
MB_ICONSTOP 紅X號
按鈕和圖標可以結合用,如:
MessageBox(NULL,"該內存不能訪問","出錯",MB_OK|MB_ICONSTOP);
系統模式:MB_SYSTEMMODAL
函數的返回值確定用戶選擇了哪個按鈕,正面給各按鈕的定義:
IDABORT “放棄”按鈕
IDCANCEL “取消”按鈕
IDIGNORE “忽略”按鈕
IDNO “否”按鈕
IDOK 確定
IDRETRY 重試
IDYES 是
判斷返回值是否與其相等即可。
第六十四個GetForegroundWindow獲得當前激活的窗口句柄
函數沒參數,調用即返回最前窗口句柄
這里舉一個例子:每當切換窗口的時候彈出一個消息提示框
#include<windows.h>
int main()
{
char WindowName[52];
HWND wnd,Orgwnd;
wnd=Orgwnd=NULL;
while(1)
{
wnd=GetForegroundWindow();
if(wnd!=Orgwnd)
{ GetWindowText(wnd,WindowName,sizeof(WindowName));
MessageBox(NULL,WindowName,"切換窗口到",MB_OK);
Orgwnd=wnd;
}
Sleep(200);
}
}
第六十五個GetTopWindow根據窗口句柄獲得其下第一子窗口句柄(如果有)
用過MFC的人都知道,在對話編輯區,如果要為控件排序的話,就按CTRL+D顯示出每個控件的順序,如下圖:
而GetTopWindow函數獲取的就是控件順序為1的窗口句柄。
例子:改變一個主窗口下的第一子窗口的顯示內容(前提得它有),這里就以上面那個abc對話框為例:
#include<windows.h>
int main()
{
HWND wnd;
wnd=FindWindow(NULL,"abc");
HWND FirstWnd=GetTopWindow(wnd);
SetWindowText(FirstWnd,"first");
return 0;
}
執行效果:
第六十六個GetNextWindow根據子窗口句柄獲得下一個或上一個同級的窗口句柄(返回NULL,函數執行失敗)
函數有兩個參數,第一個是子窗口句柄,第二個參數指明尋找上一個,還是一下個窗口句柄,值:GW_HWNONEXT(下一個),GW_HWNDPREV(上一個)。比如子窗口句柄在主窗口的順序為3,那么獲取的是順序為2或順序為3的窗口句柄(具體取決於第二個參數),函數返回獲得的窗口句柄.這樣GetNextWindow結合GetTopWindow函數就可以遍歷一個主窗口里的所有子窗口了。
例子:遍歷一個窗口里的所有子窗口,以上面的abc窗口為例
#include<stdio.h>
#include<windows.h>
int main()
{
char Name[52];
HWND wnd;
wnd=FindWindow(NULL,"abc");
wnd=GetTopWindow(wnd);
while(wnd!=NULL)
{
GetWindowText(wnd,Name,sizeof(Name));
printf("窗口句柄:%d,名稱:%s\n",wnd,Name);
wnd=GetNextWindow(wnd,GW_HWNDNEXT); //GW_HWNDNEXT獲取下一個
}
return 0;
}
第六十七個InvalidateRect發送一個WM_PAINT消息給窗口(刷新窗口)
函數定義:
BOOL InvalidateRect(
HWND hWnd , //要刷新窗口的句柄
CONST RECT *lpRect, //刷新的范圍
BOOL bErase //重畫為TRUE
);
例子:在SetTimer函數里會舉例
第六十八個SetTimer設置一個定時器(每隔一段時間執行一次定時器函數)
函數定義:
UINT SetTimer(
HWND hWnd , //窗口句柄
UINT nIDEvent, //定時器ID號,為了能設置多個定時器
UINT uElapse, //時間,指明間隔多久執行一次定時器函數,單位:毫秒
TIMERPROC lpTimerFunc); //定時器回調函數的地址
定時器函數的固定格式:VOID CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT_PTR idEvent, DWORD dwTime);
例子:在用w,a,s,d鍵控制一個矩形移動的同時,一個相同的矩形自動移動。
#include<windows.h>
LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);//函數聲明
VOID CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT_PTR idEvent, DWORD dwTime);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASS wndcls; //定義一個存儲窗口信息WNDCLASS變量
wndcls.cbClsExtra=0; //默認為0
wndcls.cbWndExtra=0; //默認為0
wndcls.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //背景畫刷
wndcls.hCursor=LoadCursor(NULL,IDC_ARROW); //光標
wndcls.hIcon=LoadIcon(NULL,IDI_ERROR); //窗口圖標
wndcls.hInstance=hInstance; //應用程序實例句柄由WinMain函數傳進來
wndcls.lpfnWndProc=WinSunProc; //窗口消息處理函數
wndcls.lpszClassName="windowclass"; //窗口類名
wndcls.lpszMenuName=NULL; //窗口菜單名,沒有菜單,為NULL
wndcls.style=CS_HREDRAW | CS_VREDRAW; //窗口類型,CS_HREDRAW和CS_VERDRAW 表明
//當窗口水平方向垂直方向的寬度變化時重繪整個窗口
RegisterClass(&wndcls); //把窗口信息提交給系統,注冊窗口類
HWND hwnd; //用以存儲CreateWindow函數所創建的窗口句柄
hwnd=CreateWindow("windowclass","first windows",
WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL); //創建窗口
ShowWindow(hwnd,SW_SHOWNORMAL); //窗口創建完了,顯示它
UpdateWindow(hwnd); //更新窗口,讓窗口毫無延遲的顯示
SetTimer(hwnd,1,200,(TIMERPROC)TimerProc); //設置定時器
MSG msg; //消息結構類型
while(GetMessage(&msg,NULL,0,0)) //獲取消息
{
TranslateMessage(&msg); //此函數用於把鍵盤消息(WM_KEYDOWN,WM_KEYUP)轉換成字符消息WM_CHAR
DispatchMessage(&msg); //這個函數調用窗口過程處理函數,並把MSG里的信息處理后傳給過程函數的四個參數
}
return 0;
}
VOID CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT_PTR idEvent, DWORD dwTime) //定時器函數
{
static int x=0,y=0;
x+=15;
if(x>500)
{y+=15;x=0;}
HDC dc=GetDC(hwnd);
Rectangle(dc,x,y,x+30,y+30);
}
LRESULT CALLBACK WinSunProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{ static int x=0,y=0;
switch(uMsg)
{
case WM_CHAR:
{
if(wParam=='a') x-=10;
else if(wParam=='d') x+=10;
else if(wParam=='w') y-=10;
else if(wParam=='s') y+=10;
RECT rect;
GetClientRect(hwnd,&rect);
::InvalidateRect(hwnd,&rect,TRUE); //發送WM_PAINT消息,刷新窗口
}
break;
case WM_PAINT:
HDC dc;
PAINTSTRUCT ps;
dc=BeginPaint(hwnd,&ps); //BeginPaint只能在響應WM_PAINT,不能用GetDC獲取設備上下文
Rectangle(dc,x,y,x+30,y+30);
break;
case WM_CLOSE: //用戶關閉了窗口
DestroyWindow(hwnd); //銷毀窗口,並發送WM_DESTROY消息
break;
case WM_DESTROY: //如果窗口被銷毀
PostQuitMessage(0); //讓進程退出
break;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam); //未處理的消息通過DefWindowProc函數交給系統處理
}
第六十九個RegisterHotKey注冊一個熱鍵
函數定義:
BOOL RegisterHotKey(
HWND hWnd ,
int id,
UINT fsModifiers,
UINT vk);
第一個參數hWnd表明熱鍵消息(HOT_KEY)發送給哪個窗口,為NULL表明直接把消息投遞給調用這個函數進程的消息隊列。
第二個參數可以自定取值,取值范圍0xC000-0xFFFF,這個參數是為了程序能同時擁有多個熱鍵而存在。
第三個參數fsModifiers的可選取值如下:MOD_ALT(Alt鍵),MOD_CONTROL(Ctrl鍵),MOD_SHIFT(Shift鍵),MOD_WIN(‘田’圖標鍵)
最一個參數是一個ASCII值,指明具體和哪個非系統鍵組合。
如QQ的熱鍵ctrl+alt+z,注冊這個熱鍵的語句是RegisterHotKey(NULL,0x0001,MOD_CONTROL|MOD_ALT,‘Z’)
如QQ的截圖熱鍵 RegisterHotKey(NULL,0x0001,MOD_CONTROL|MOD_ALT,'A')
例子:按下ctrl+alt+x熱鍵,彈出消息提示框,詢問是否要退出。
//#include "stdafx.h" 新建空工程,不需要該頭文件
#include<windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
RegisterHotKey(NULL,0x0001,MOD_CONTROL|MOD_ALT,'X'); //注冊熱鍵
MSG msg;
while(GetMessage(&msg,NULL,0,0)) //從消息隊伍獲取消息
{
if(msg.message==WM_HOTKEY) //熱鍵消息
{
if(MessageBox(NULL,"你確定要退出程序嗎?","熱鍵提示",MB_YESNO|MB_SYSTEMMODAL)==IDYES)
{
UnregisterHotKey(NULL,0x0001); //卸載熱鍵
break;
}
}
}
return 0;
}
記得要退出程序
第七十個StretchBlt在窗口輸出一個位圖
這個函數比BitBlt多了兩個參數,那就是源目標DC的寬高,像BitBlt函數,只有目標DC的寬高。
有了這兩個參數的加入,StretchBlt函數功能就比BitBlt函數強大了許多,它可以縮小或放大圖片,可以把一張圖片上的任意矩形區域覆蓋到另一張圖片上的任意區域。
函數語句:StretchBlt(hdc,0,0,bmInfo.bmWidth,bmInfo.bmHeight,memDC,0,0,50,50,SRCCOPY);
具體用法參考BitBlt函數。
第七十一個TextOut根據設備DC在窗口輸出文字
函數定義:
BOOL TextOut(HDC hdc, // 設備DC
int nXStart, // 窗口x坐標
int nYStart, // 窗口y坐標,字符串左上角是將是x,y
LPCTSTR lpString, // 字符串
int cbString // 字符串中字符的個數
);
例子:在窗口輸出文字,為了方便,這里依舊在"無標題.txt - 記事本",窗口里輸出文字
#include<windows.h>
#include<string.h>
int main()
{
char Text[52]="從坐標點50,50開始輸出文字";
HWND wnd=FindWindow(NULL,"無標題.txt - 記事本");
HDC dc=GetDC(wnd);
SetTextColor(dc,RGB(255,0,0)); //設置文字顏色
while(1)
{
TextOut(dc,50,50,Text,strlen(Text));
Sleep(200);
}
return 0;
}
第七十二個DrawText根據設備DC在窗口的一個矩形區輸出文字。
int DrawTextW(
HDC hDC, //設備DC
LPCWSTR lpString, //字符串
int nCount, //字符串的個數
LPRECT lpRect, //指明一個矩形區
UINT uFormat); //輸出格式
uFormat的常用取值
值 說明
DT_BOTTOM 將正文調整到矩形底部。此值必須和DT_SINGLELINE組合。
DT_CENTER 使正文在矩形中水平居中。
DT_VCENTER 使正文在矩形中垂直居中。
DT_END_ELLIPSIS 對於顯示的文本,如果結束的字符串的范圍不在矩形內,它會被截斷並以省略號標識。
如果一個字母不是在字符串的末尾處超出了矩形范圍,它不會被截斷並以省略號標識。
字符串不會被修改,除非指定了DT_MODIFYSTRING標志。
T_WORD_ELLIPSIS, DT_PATH_ELLIPSIS和DT_END_ELLIPSIS不能和此參數一起使用
DT_LEFT 正文左對齊。
T_RIGHT 正文右對齊。
DT_RTLREADING 當選擇進設備環境的字體是希伯來文或阿拉伯文字體時,為雙向正文安排從右到左的閱讀順序都是從左到右的。
DT_TOP 正文頂端對齊(僅對單行)。
DT_WORDBREAK 斷開字。當一行中的字符將會延伸到由lpRect指定的矩形的邊框時,此行自動地在字之間斷開。一個回車一換行也能使行折斷。
DT_WORD_ELLIPSIS 截短不符合矩形的正文,並增加省略號。
第七十三個GetLogicalDriveStrings獲取系統分區信息
函數定義:
DWORD GetLogicalDriveStrings(
DWORD nBufferLength, //指明lpBuffer參數大小
LPSTR lpBuffer //分區信息將會存儲在這個參數,格式是“分區NULL分區NULL分區NULL NULL”兩個NULL結尾
//假設當前電腦有C,D,E分區,那字符串的存儲格式是 "C:\\\0D:\\\0E:\\\0\0"; ('\\'轉義字符\)
);
例子:枚舉當前磁盤所有分區
#include<windows.h>
#include<stdio.h>
int main()
{
char *szBuffer=new char[52];
GetLogicalDriveStrings(52,szBuffer);
while(*szBuffer!='\0')
{
printf("%s\n",szBuffer);
szBuffer+=strlen(szBuffer)+1;
}
return 0;
}
第七十四個GetDiskFreeSpaceEx獲取一個分區(盤符)的信息(已用空間,總大小,可用空間)
這個函數必須用ULARGE_INTEGER聯合類型來存儲磁盤使用信息。因為要獲取磁盤的已用空間,總大小,可用空間,所以我們必須定義三個ULARGE_INTEGER類型變量來存儲這三個信息。而具體信息就存儲在ULARGE_INTEGER類型的QuadPart成員變量(該成員占八位字節)
如獲取C盤的信息:ULARGE_INTEGER dwAvailable,dwFree,dwTotal;
GetDiskFreeSpaceEx("c:\\",&dwAvailable,&dwTotal,&dwFree);//獲取分區信息
下面結合GetLogicalDriveStrings舉個例子:獲取當前磁盤所有分區信息並輸出
#include<windows.h>
#include<stdio.h>
int GetSpace(char *P);
int main()
{
char *szBuffer=new char[52];
GetLogicalDriveStrings(52,szBuffer);
while(*szBuffer!='\0')
{
printf("%s ",szBuffer);
GetSpace(szBuffer);
szBuffer+=strlen(szBuffer)+1;
}
return 0;
}
int GetSpace(char *Par)
{
ULARGE_INTEGER dwAvailable,dwFree,dwTotal;
double fDwa,fDwf,fDwt;
char szSize[128]={0};
int Size;
GetDiskFreeSpaceEx(Par,&dwAvailable,&dwTotal,&dwFree); //獲取分區信息
Size=dwTotal.QuadPart/1024/1024; //獲取總大小
fDwt=Size/1024.0;
Size=dwAvailable.QuadPart/1024/1024; //獲取已用空間
fDwa=Size/1024.0;
Size=dwFree.QuadPart/1024/1024; //獲取可用空間
fDwf=Size/1024.0;
sprintf(szSize,"總大小:%0.2fGB 已用空間:%0.2fGB 可用空間:%0.2fGB",fDwt,fDwa,fDwf);
printf("%s\n",szSize);
return 0;
}
第七十五個WritePrivateProfileString修改或創建一個INI文件里的數據
INI文件的內容一般由節名,鍵名,鍵值組成,先來看一下INI文件的結構,打開一個INI文件,我們可能會看到以下內容
////////////////////////////////////////////////////////////////////////////////////
[gmy_p]
exist_p=0
linux_p=
[boot]
a20=0
a21=0
///////////////////////////////////////////////////////////////////////////////////
上面的內容中[gmy_p]和[boot]便是INI文件的節名,節名包含鍵名,和鍵值。一個INI文件可以有多個節名.
那么哪些是鍵名,那些是鍵值呢,在“=”左邊的是鍵名,而在右邊的就是鍵值,鍵值可以為NULL。
好了,看一下WritePrivateProfileString的函數的定義:
BOOL WritePrivateProfileString(
LPCWSTR lpAppName, //節名
LPCWSTR lpKeyName, //鍵名
LPCWSTR lpString, //修改的數據
LPCWSTR lpFileName //INI文件名
);
如果要修改鍵值,那么要提供哪些信息呢,首先,必須要知道INI文件的路徑(lpFileName),要修改的鍵值是在哪個節名下(lpAppName),以及具體是哪個鍵名(lpKeyName),還有修改的鍵值數據(lpString).
比如我要把之前INI文件里節名為gmy_p下的鍵名exist_p的鍵值改為100(假設這個文件的路徑為d:\gho.ini).
那么就是語句:WritePrivateProfileString("gmy_p","exist_p","100","d:\\gho.ini");
WritePrivateProfileString函數功能不止於此,當函數提供的INI文件名,節名,鍵名不存在時,那么函數就會創建他們。這樣,我們就可以用這個函數創建一個INI文件,或在一個INI文件里創建一個節名,或在一個節名下創建一個鍵名。
如:WritePrivateProfileString("ZhengYong","QQ","***980073","d:\\Info.ini");
第七十六個GetPrivateProfileString獲取一個INI文件里的數據
函數定義:
DWORD GetPrivateProfileStringW(
LPCWSTR lpAppName, //節名
LPCWSTR lpKeyName, //鍵名
LPCWSTR lpDefault, //默認值,填0既可
LPWSTR lpReturnedString, //接收數據的緩存區(字符串)
DWORD nSize, //指明緩存區的大小
LPCWSTR lpFileName //INI文件名
);
例子獲取一個鍵值:假設D盤下有一個名為Info.ini文件,它的內容如下:
////////////////////////////////////////////////////////////////////////////
[ZhengYong]
QQ=***980073
Age=100
Weight=65kg
[LiYang]
QQ=***990129
Age=22
Weight=55kg
///////////////////////////////////////////////////////////////////////////
如果我想要獲取節名為"ZhengYong"下的鍵名QQ的鍵值,那么就是:
#include<windows.h>
#include<stdio.h>
int main()
{
char KeyValue[252];
::GetPrivateProfileString("ZhengYong","QQ",0,KeyValue,252,"d:\\Info.ini");
printf("%s\n",KeyValue);
return 0;
}
同WritePrivateProfileString類似,如果提供的節名,或鍵名為NULL,則獲取當前所有的節名或鍵名。跟分區信息存儲格式一樣,字符串里,多個節名,或鍵名以'\0'間隔,字符串最終以兩個'\0'結束。
例子:枚舉ZhengYong節名下的所有鍵名:
#include<windows.h>
#include<stdio.h>
int main()
{
char Data[252];
::GetPrivateProfileString("ZhengYong",NULL,0,Data,252,"d:\\Info.ini");
char *pKey=Data;
while(*pKey!='\0')
{
printf("%s\n",pKey);
pKey+=strlen(pKey)+1;
}
return 0;
}
那么枚舉節名只要在上面的例子中,把函數的節名參數設為NULL就行了,如:
GetPrivateProfileString(NULL,NULL,0,Data,252,"d:\\Info.ini");
大家可以用這個函數編一個讀取INI文件內容的程序,以便更好的掌握這個函數。記得把接收數據的緩存區設置大一點。
第七十七個GetSystemMetrics獲得特定窗口的高寬度
該函數只有一個參數,常用取值如下:
SM_CXSCREEN 屏幕寬度
SM_CYSCREEN屏幕高度
SM_CXFULLSCREEN窗口客戶區寬度
SM_CYFULLSCREEN窗口客戶區高度
SM_CYMENU菜單欄高度
SM_CYCAPTION//標題欄高度
SM_CXBORDER窗口邊框寬度
SM_CYBORDER窗口邊框高度
例子:獲取屏幕分辨率(桌面寬高度)
#include<windows.h>
#include<stdio.h>
int main()
{
int ScreenX=::GetSystemMetrics(SM_CXSCREEN);
int ScreenY=::GetSystemMetrics(SM_CYSCREEN);
printf("屏幕分辨率:%dX%d\n",ScreenX,ScreenY);
return 0;
}
第七十八個SetWindowPos設置一個窗口的大小和它的Z序
窗口的Z序是什么意思呢?用過MFC的人應該都清楚,在對話框編輯區按CTRL+D就會顯示出每個控件的順序。如下圖:
設置控件的順序有什么用呢,大家看到上面兩個控件有什么特別的嗎?對了,兩個控件正好有一部分重疊,這時候問題就來了,重疊的部分顯示的是那個窗口呢,或者說是以什么來確定顯示哪個窗口,我想大家也應該猜到了,是以控件的順序來確定的。順序較大的會被顯示。這個程序運行如下圖如示:
明白窗口的Z序了,我們就來看一下這個函數的參數及其意思。
函數定義:
BOOL SetWindowPos(
HWND hWnd, //要設置的窗口句柄
HWND hWndInsertAfter,
int X,
int Y, //X,Y指明窗口左上角的位置
int cx, //窗口寬度
int cy, //窗口高度
UINT uFlags);
第二個參數hWndInsertAfter的常用取值:
HWND_BOTTOM:
將窗口置於Z序的底部.
HWND_NOTOPMOST:如果窗口在Z序頂部,則取消頂部位置,如果不是,則該參數無效
HWND_TOP:將窗口置於Z序的頂部。
HWND_TOPMOST:將窗口置於Z序的頂部。窗口當前未被激活,也依然是頂部位置
最后一個參數uFlags可以是Z序中hWnd的前一個窗口句柄的或以下常用取值:
SWP_HIDEWINDOW;隱藏窗口
SWP_SHOWWINDOW:顯示窗口
SWP_NOMOVE指明X,Y參數無效
SWP_NOSIZE指明CX ,CY參數無效
SWP_NOZORDER指明hWndInsertAfter參數無效
例子:設置一個窗口像PPS和任務欄那樣,總在最前顯示。
以"無標題.txt - 記事本"窗口為例
#include<windows.h>
int main()
{
HWND wnd=::FindWindow(NULL,"無標題.txt - 記事本");
::SetWindowPos(wnd,HWND_TOPMOST,0,0,0,0,SWP_SHOWWINDOW|SWP_NOMOVE|SWP_NOSIZE);
return 0;
}
例子2:全屏一個窗口
依舊以"無標題.txt - 記事本"窗口為例:
#include<windows.h>
int main()
{
int ScreenX=::GetSystemMetrics(SM_CXSCREEN);
int ScreenY=::GetSystemMetrics(SM_CYSCREEN);
HWND wnd=::FindWindow(NULL,"無標題.txt - 記事本");
::SetWindowPos(wnd,HWND_TOPMOST,0,0,ScreenX,ScreenY,SWP_SHOWWINDOW);
return 0;
}
第七十九個CreateFile創建一個文件,或打開一個文件用於讀寫,函數返回文件句柄
函數定義:
HANDLE CreateFile(
LPCSTR lpFileName, //文件名
DWORD dwDesiredAccess, //指明對文件進行何種操作,是要讀它(GENERIC_READ)還是要寫入(GENERIC_WRITE)
DWORD dwShareMode, //指明文件可以同時被多個程序讀寫嗎?FILE_SHARE_READ可以同時讀,FILE_SHARE_WRITED可以同時寫
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向一個SECURITY_ATTRIBUTES結構的指針,一般為NULL
DWORD dwCreationDisposition, //安全屬性,指明以何種方式打開或創建文件
DWORD dwFlagsAndAttributes, //指明文件的屬性,隱藏?只讀?系統文件?為NULL表示默認屬性
HANDLE hTemplateFile //如果不為零,則指定一個文件句柄。新文件將從這個文件中復制擴展屬性
);
第五個參數dwCreationDisposition的常用取值及意思
TRUNCATE_EXISTING 將現有文件縮短為零長度,清空文件的內容,文件必須已經存在
CREATE_ALWAYS 創建一個文件,如果文件已經存在,則覆蓋它
CREATE_NEW 創建文件,如果文件已經存在,則函數執行失敗
OPEN_ALWAYS打開文件,如果文件不存在,則創建它
OPEN_EXISTING 打開文件,文件必須存在。
第六個參數dwFlagsAndAttributes的常用取值及其意思
FILE_ATTRIBUTE_NORMAL 默認屬性
FILE_ATTRIBUTE_HIDDEN 隱藏
FILE_ATTRIBUTE_READONLY 只讀
FILE_ATTRIBUTE_SYSTEM 系統文件
第八十個ReadFile根據文件句柄,從文件中讀取一段數據
函數定義:
BOOL WINAPI ReadFile(
HANDLE hFile, //文件句柄
LPVOID lpBuffer, //接收文件數據的緩存區
DWORD nNumberOfBytesToRead, //指明讀取多少數據(字節)
LPDWORD lpNumberOfBytesRead, //實際讀取數據
LPOVERLAPPED lpOverlapped //一般為NULL,如文件打開時指定了FILE_FLAG_OVERLAPPED,該參才有具體取值。
);
例子:讀取txt文件的內容,假設E盤下有一個名a.txt的文件,文件內容為123456789
#include<windows.h>
#include<stdio.h>
int main()
{
char Text[25]={0};
DWORD dwSize;
HANDLE FileHandle=CreateFile("e:\\a.txt",GENERIC_READ,0,NULL,OPEN_EXISTING
,FILE_ATTRIBUTE_NORMAL ,NULL); //獲取文件句柄
ReadFile(FileHandle,Text,15,&dwSize,NULL); //從文件中讀取15個字節
printf("內容:%s 實際讀入字節:%d\n",Text,dwSize);
return 0;
}
第八十一個WriteFile根據文件句柄,寫入一段數據到文件中
函數定義:
BOOL WriteFile(
HANDLE hFile, //文件句柄
LPCVOID lpBuffer, //該緩存區的數據將要寫入到文件里
DWORD nNumberOfBytesToWrite, //指明寫入多少數據
LPDWORD lpNumberOfBytesWritten, //實際寫入數據
LPOVERLAPPED lpOverlapped //一般為NULL
);
例子:在E盤創建一個名為aa.txt的文件,並向其寫入數據
#include<windows.h>
#include<stdio.h>
int main()
{
char Text[25]="123456789";
DWORD dwSize;
HANDLE FileHandle=CreateFile("e:\\aa.txt",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
::WriteFile(FileHandle,Text,9,&dwSize,0);
return 0;
}
第八十二個SetFilePointer移動一個文件指針的位置
移動一個文件指針的位置有什么用呢,作用是讀取一個文件里指定位置的數據,比如我只要讀取文件中第四個字節到第七個字節這一段的數據,用SetFilePointer函數就可以完成。
函數定義:
DWORD SetFilePointer(
HANDLE hFile, //文件句柄
LONG lDistanceToMove, //移動字節,負數表示反向移動
PLONG lpDistanceToMoveHigh, //為了支持超大文件而存在,一般為NULL
DWORD dwMoveMethod //從哪里開始移動,FILE_BEGIN 從文件開始處開始移動,FILE_CURRENT當前位置,FILE_END文件末尾
);
例子:假設E盤下有一個名為a.txt的文件,內容為"123456789",讀取該文件第四個字節到第七個字節的數據
#include<stdio.h>
int main()
{
char Text[25]={0};
DWORD dwSize;
HANDLE FileHandle=CreateFile("e:\\a.txt",GENERIC_READ,0,NULL,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
SetFilePointer(FileHandle,3,NULL,FILE_BEGIN);
ReadFile(FileHandle,Text,4,&dwSize,NULL);
printf("%s\n",Text);
return 0;
例子2:從文件中第四個字節開始寫入數據,被新數據所占位置的數據會被覆蓋掉,依舊以上面a.txt文件為例子
#include<windows.h>
#include<stdio.h>
int main()
{
char Text[25]={"abcd"};
DWORD dwSize;
HANDLE FileHandle=CreateFile("e:\\a.txt",GENERIC_WRITE,0,NULL,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
SetFilePointer(FileHandle,3,NULL,FILE_BEGIN);
WriteFile(FileHandle,Text,4,&dwSize,NULL);
return 0;
}
則寫入后a.txt文件里的數據為123abcd89
如果要在文件的末尾添加數據,就用這個語句:SetFilePointer(FileHandle,0,NULL,FILE_END);
第八十三個GetFileSize獲取一個文件的大小
函數定義:
DWORD GetFileSize(
HANDLE hFile, //文件句柄
LPDWORD lpFileSizeHigh //一般為NULL
);
如獲取a.txt文件的大小:
#include<windows.h>
#include<stdio.h>
int main()
{
DWORD FileSize;
HANDLE FileHandle=CreateFile("e:\\a.txt",GENERIC_WRITE,0,NULL,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
FileSize=GetFileSize(FileHandle,NULL);
printf("%d字節\n",FileSize);
return 0;
}
第八十四個SetTextColor根據窗口輸出文本顏色
第一個參數是設備DC,第二個參數是一個COLORREF類型的顏色值,可用RGB進行轉換。
第八十五個SetBkColor設置背景顏色
參數跟SetTextColor函數一樣,第一個DC,第二個COLORREF
第八十六個GetWindowDC獲取整個窗口設備上下文DC
像GetDC獲取的只是客戶區DC,不能對窗口標題欄,狀態欄等進行操作。該函數用法跟GetDC一樣,僅區域不一樣。
例子:在一個窗口的標題欄輸出文字
#include<windows.h>
int main()
{
HWND wnd=FindWindow(NULL,"無標題.txt - 記事本");
HDC dc=GetWindowDC(wnd);
SetTextColor(dc,RGB(255,0,0)); //文本顏色設置為紅色
::SetBkColor(dc,RGB(0,0,255)); //文本背景顏色設置為藍色
while(1)
{
TextOut(dc,0,0,"123456",6);
Sleep(200);
}
return 0;
}
第八十七個GetDesktopWindow獲取桌面窗口句柄
該函數沒有參數,調用返回桌面窗口句柄
例子:
#include<windows.h>
int main()
{
HWND DeskWnd=GetDesktopWindow(); //獲取桌面窗口句柄
HDC DeskDC=GetWindowDC(DeskWnd); //獲取DC
HBRUSH brush=::CreateSolidBrush(RGB(255,0,0)); //紅色畫刷
SelectObject(DeskDC,brush); //選入畫刷
while(1)
{
::Rectangle(DeskDC,50,50,200,200);
Sleep(200);
}
return 0;
}
第八十八個CreateCompatibleBitmap根據DC創造一個兼容的位圖
該函數需與CreateCompatibleDC函數配合使用
函數第一個參數是窗口DC,第二,三參數指明窗口寬高,函數返回位圖句柄(HBITMAP)
創建一個兼容的位圖是什么意思呢?就好比給HBITMAP分配內存以及指定這位圖相關的一些信息(跟DC相關的信息),如位圖的寬高,數據大小,但此時數據沒有具體取值。就好比一個字符串,我已經知道字符串大小了,但卻不知道字符串具體是什么:
如:
char *p;
p=new char[15]; //知道字符串大小為15
但此時p所指向的緩存區,沒有具體取值。
而用CreateCompatibleBitmap函數,創建的位圖,只是一個空殼子。數據沒有賦值,那要怎樣給數據賦值呢?
首先得把這個位圖句柄選入一個DC(該DC必須為CreateCompatibleDC函數創建的)里,然后再用BitBlt函數具體給數據賦值就行了。
例子:實時獲取屏幕圖像
為了方便,在記事本窗口輸出圖像,自己就不創建窗口了(打開"無標題.txt - 記事本")
#include<windows.h>
int main()
{
HWND TextWnd=FindWindow(NULL,"無標題.txt - 記事本");
HDC TextDC=GetDC(TextWnd);
HWND DeskWnd=::GetDesktopWindow();
RECT DeskRC;
::GetClientRect(DeskWnd,&DeskRC);
HDC DeskDC=GetDC(DeskWnd);
HBITMAP DeskBmp=::CreateCompatibleBitmap(DeskDC,DeskRC.right,DeskRC.bottom);
HDC memDC;
memDC=::CreateCompatibleDC(DeskDC);
SelectObject(memDC,DeskBmp);
while(1)
{
StretchBlt(memDC,0,0,DeskRC.right,DeskRC.bottom,DeskDC,0,0,DeskRC.right,DeskRC.bottom,SRCCOPY);
RECT TextRC;
GetClientRect(TextWnd,&TextRC);
::StretchBlt(TextDC,0,0,TextRC.right,TextRC.bottom,memDC,0,0,DeskRC.right,
DeskRC.bottom,SRCCOPY);
Sleep(300);
}
return 0;
}
第八十九個GetDIBits從一個兼容位圖里獲取位圖數據
先來分析一下位圖文件信息結構,一個位圖由以下四部分組成:
位圖文件頭(BITMAPFILEHEADER)//占14字節
位圖信息頭(BITMAPINFOHEADER)//占40字節
調色板(LOGPALLETE)//如果真彩位圖,那該部分沒有,直接是位圖數據
實際位圖數據
而GetDIBits函數獲取的就是實際位圖數據這一部分了。
接來看一下BITMAPFILEHEADER這個結構以及它成員的意思和取值
typedef struct tagBITMAPFILEHEADER {
WORD bfType; //表示文件類型,值必須為0x4d42
DWORD bfSize; //文件的大小
WORD bfReserved1; //保留,必須為0
WORD bfReserved2; //保留,必須為0
DWORD bfOffBits; //位圖前三部分所占的字節,真彩色位圖為54
} BITMAPFILEHEADER;
上面的成員,只有bfSize的取值不確定,其它都一樣,也就是說,每個真彩位圖,這幾個成員取值都是一樣的.下面的例子可以說明。
讀取一個真彩位圖的文件信息頭。
#include<windows.h>
#include<stdio.h>
int main()
{
BITMAPFILEHEADER bfh;
HANDLE hFile=CreateFile("e:\\aa.bmp",GENERIC_READ,0,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
DWORD dwSize;
ReadFile(hFile,(void *)&bfh,sizeof(BITMAPFILEHEADER),&dwSize,0);
printf("bfType:%x\n",bfh.bfType);
printf("bfSize:%d\n",bfh.bfSize);
printf("bfReserved1:%d\n",bfh.bfReserved1);
printf("bfReserved2:%d\n",bfh.bfReserved2);
printf("bfOffbits:%d\n",bfh.bfOffBits);
return 0;
}
再來看一下BITMAPINFOHEADER這個結構以及它成員的意思和取值
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //本結構大小,為40
LONG biWidth; //位圖的寬度,以像素為單位
LONG biHeight; //位圖的高度,以像素為單位
WORD biPlanes; //目標設備的級別,必須是1
WORD biBitCount; //每個像素所占的位數,24表示真彩位圖
DWORD biCompression; //位圖壓縮類型,一般為BI_RGB(未經壓縮)
DWORD biSizeImage; //實際位圖數據這部分的所占用的字節數
LONG biXPelsPerMeter; //指定目標設備水平分辨率,單位像素/米,為0
LONG biYPelsPerMeter; //指定目標垂直分辨率真,單位像素/米,為0
DWORD biClrUsed; //指定目標設備實際用到的顏色數,如果該值為0,則用到的顏色數為2的biBitCount方
DWORD biClrImportant; //表示圖像中重要的顏色數,如果為0,則所有顏色都是重要的。
} BITMAPINFOHEADER;
調色板(LOGPALLETE)由於大部分都是針對真彩位圖操作,此部分略過
GetDIBits函數定義:
int GetDIBits(
HDC hdc, //位圖兼容的DC
HBITMAP hbmp, //位圖句柄
UINT uStartScan, //從哪行開始掃描
UINT cScanLines, //掃描多少行數據
LPVOID lpvBits, //接收數據的緩存區
LPBITMAPINFO lpbi, //真彩位圖,此處填文件信息頭就行了
UINT uUsage //真彩位圖填DIB_RGB_COLORS,表示由R,G,B三色直接構成
);
例子:截屏,並把屏幕圖片保存成位圖
#include<windows.h>
void ScreenSnap(HBITMAP hBitmap,char *bmpPath,HDC dc);
int main()
{
HWND DeskWnd=::GetDesktopWindow(); //獲取桌面窗口句柄
RECT DeskRC;
::GetClientRect(DeskWnd,&DeskRC); //獲取窗口大小
HDC DeskDC=GetDC(DeskWnd); //獲取窗口DC
HBITMAP DeskBmp=::CreateCompatibleBitmap(DeskDC,DeskRC.right,DeskRC.bottom); //兼容位圖
HDC memDC=::CreateCompatibleDC(DeskDC); //兼容DC
SelectObject(memDC,DeskBmp); //把兼容位圖選入兼容DC中
BitBlt(memDC,0,0,DeskRC.right,DeskRC.bottom,DeskDC,0,0,SRCCOPY); //拷貝DC
ScreenSnap(DeskBmp,"d:\\Screen.bmp",DeskDC);
return 0;
}
void ScreenSnap(HBITMAP hBitmap,char *bmpPath,HDC dc)
{
BITMAP bmInfo;
DWORD bmDataSize;
char *bmData; //位圖數據
GetObject(hBitmap,sizeof(BITMAP),&bmInfo); //根據位圖句柄,獲取位圖信息
bmDataSize=bmInfo.bmWidthBytes*bmInfo.bmHeight; //計算位圖數據大小
bmData=new char[bmDataSize]; //分配數據
BITMAPFILEHEADER bfh; //位圖文件頭
bfh.bfType=0x4d42;
bfh.bfSize=bmDataSize+54;
bfh.bfReserved1=0;
bfh.bfReserved2=0;
bfh.bfOffBits=54;
BITMAPINFOHEADER bih; //位圖信息頭
bih.biSize=40;
bih.biWidth=bmInfo.bmWidth;
bih.biHeight=bmInfo.bmHeight;
bih.biPlanes=1;
bih.biBitCount=24;
bih.biCompression=BI_RGB;
bih.biSizeImage=bmDataSize;
bih.biXPelsPerMeter=0;
bih.biYPelsPerMeter=0;
bih.biClrUsed=0;
bih.biClrImportant=0;
::GetDIBits(dc,hBitmap,0,bmInfo.bmHeight,bmData,(BITMAPINFO *)&bih,DIB_RGB_COLORS);//獲取位圖數據部分
HANDLE hFile=CreateFile(bmpPath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,0); //創建文件
DWORD dwSize;
WriteFile(hFile,(void *)&bfh,sizeof(BITMAPFILEHEADER),&dwSize,0); //寫入位圖文件頭
WriteFile(hFile,(void *)&bih,sizeof(BITMAPINFOHEADER),&dwSize,0); //寫入位圖信息頭
WriteFile(hFile,(void *)bmData,bmDataSize,&dwSize,0); //寫入位圖數據
::CloseHandle(hFile); //關閉文件句柄
}
原文地址: http://www.cnblogs.com/yiciyun/archive/2012/02/24/2365984.html