GetDesktopWindow: HWND; {无参数; 返回桌面窗口的句柄}
var h: HWND; begin h := GetDesktopWindow; ShowMessage(IntToStr(h)); end; Application.MainForm句柄
--------------------------------------------------------------------------------
声明:
GetCurrentThread: THandle; {返回当前线程的虚拟句柄} GetCurrentThreadId: DWORD; {返回当前线程 ID} GetCurrentProcess: THandle; {返回当前进程的虚拟句柄} GetCurrentProcessId: DWORD; {返回当前进程 ID}
提示:
ID 是系统唯一的标识.
所谓虚拟句柄, 就是该句柄只在调用进程的进程中有效, 也不能被继承;
如果用于其他进程需要用 DuplicateHandle 复制句柄;
GetCurrentProcess 返回的虚拟句柄,可以通过 OpenProcess 创建一个真实的句柄.
在系统中,对象分两类:核心对象和用户对象.如进程对象,线程对象,文件映射对象等就是核心对象;而像窗口,菜单等都是用户对象. 两者是有差别的,用于标示用户对象的句柄是系统唯一的,也就是说,一个进程完全可以对另外一个进程中的用户对象进行操作,比如两个进程间通信的方法之一, 就是发送消息.正是由于窗口是用户对象,所以句柄是系统唯一,通过FindWindow(), 得到另外一个进程的窗口句柄,然后用SendMessage(),让hWnd的窗口过程来处理消息,实现了进程间的通信.因此,对于用户对象,你根本不用DuplicateHandle(),直接把句柄拿来用就行了. 而核心对象则不一样.核心对象是为了加强系统的稳定性,因此,核心对象句柄是进程相关的,在每一个进程中都有一个核心对象表,每一个对象的索引(不完全是)作为内核对象的句柄,从而实现进程相关.同一个对象在不同的进程中可能有不同的索引,即句柄.对核心对象进行操作时,系统还要进行安全检验,看一下你是否有权来操作这个对象.因此你不能同用户对象一样,直接把句柄拿过来用.比方说,你想操作另一个进程中的文件映射对象,这个文件映射对象句柄在那个进程中假设是0x000001,但在你的进程中,很有可能0x00000001时表示另一个核心对象,此时的操作就永远不会成功,甚至会产生灾难性的后果.此时,就有必要用DuplicateHandle().
核心对象(内核对象)详解:
1:什么是内核对象?
内核对象是一组可以被内核创建、识别和操作的数据结构的总称。我的理解是操作系统为了管理资源而定义和实现的一组内部数据,这些数据只能被内核创建和修改。而用户层是无法看到他们的真正内容或者对他们直接进行操作的。我们的用户程序只能通过windows提供的一些api来获得这些内核对象的句柄,从而通过windows本身提供的其他api来间接访问和修改内核对象本身。这就好比windows为内核对象的操作提供了一个类似c++的封装。而一个内核对象的句柄与一个内核对象之间存在着一种间接的引用关系。
内核对象的生命周期是由内核来管理的,用户层的程序只能通过增删引用计数来提醒内核,某个内核对象的使用数为0或者其他,内核一旦发现某个内核对象的使用计数为0时才真正删除这个对象。
内核对象有一个重要的特性是安全访问机制,在创建一个内核对象的时候,可以指定该对象的访问权限。凡是在创建的时候可以指定一个SECURITY_ATTRIBUTES的对象都是一个内核对象。这可以用来区分普通的gdi对象。
如:
HANDLE CreateFileMapping(
HANDLE hFile, //物理文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, //安全设置-----------------------------表明文件映射对象是内核对象
DWORD flProtect, //保护设置
DWORD dwMaximumSizeHigh, //高位文件大小
DWORD dwMaximumSizeLow, //低位文件大小
LPCTSTR lpName //共享内存名称
);
2:内核对象的使用
每个进程都有可能创建或者打开若干内核对象,但是前面说过,用户无法获得真正的内核对象的内容,只能通过windows提供的api来创建和访问内核对象。具体表现为一个内核对象句柄(HANDLE)。windows为每个进程都创建一个内核对象句柄表。不同的进程的内核对象句柄表是互相独立的。windows提供的操作内核对象的api需要根据这个句柄表来进一步完成其任务。一般的内核对象句柄的值为真正内核对象在这个句柄表中的索引。windows的api根据句柄表和索引可以找到指向真正的内核对象的指针,及其访问权限和继承标志等信息。而这一切对于用户都是不可见的。不过将来句柄的值也许就不是索引,而是另外一种方式了,这里windows做了一个抽象,只要记住一个内核对象句柄与其真正的内核对象之间存在着某种引用关系。
在每个进程中都一个内核句柄表,这也就说明同一个内核对象其在不同的进程中其内核句柄值可能是不一样的。但是内核对象的作用很大程度上就在于能够在进程之间共同访问。这就需要提供一种跨越进程间的共享机制。目前有3种方法可以在进程间共享内核对象:
1:利用对象句柄的继承性;
前面说过每个进程有一个独立的句柄表,当该进程创建子进程的时候,可以指定子进程是否能继承其句柄表中的可继承句柄。如果子进程继承了父进程的对象句柄表,那么意味着同样的句柄值在两个进程中都是可以访问的。关于句柄是否可继承的需要在创建内核对象的时候指定其可继承性。如果将一个不可继承的句柄值传递给子进程使用,子进程的句柄表中并没有这个句柄值的索引,就会发生错误。
2:利用对象的可命名性;
在创建内核对象的时候可以给其指定一个全局的名字。其他进程中则可以通过这个名字获得这个内核对象的句柄。
3:利用windows提供的转换机制;
windows本身提供了将一个进程的内核对象句柄表中一个句柄拷贝到另一个进程的内核对象句柄表中的机制,利用他可以在进程间共享内核对象。
众所周知,Windows编程最基础的还是对资源的利用、控制操作,资源是一个比较具体的东西,比如CPU、内存等等。那么怎么来对资源进行操作呢,我们通常是通过“对象”与“资源”去打交道(这是本人现在的理解,可能有误)。而对象又可以分为“用户对象”、“GDI对象”和“内核对象”。
显然被称作“内核对象”,他肯定是只能被内核访问的创建于内存中的“数据结构”,也就是说,内核对象只是一块存储空间,这个空间中保存了有关这个“对象”的“数据结构”,简而言之,就是一个数据结构,只能被操作系统内核访问。对于各种不同类型的内核对象,只能被内核访问,那我们又怎么来操作这些存在内存中的内核结构呢?
微软为我们提供了“句柄”,就可以使我们很方便的来通过句柄操作内核对象了。因此在每一个内核对象被创建的时候,我们都可以得到一个唯一标识该内核对象的句柄值。也就是通过这个句柄,然后调用一些API函数,对这个句柄所标识的内核对象进行访问和修改,这个句柄称为“内核对象句柄”,相当于内核对象的“身份证”。
讲了这么多,还没有提到内核对象到底是怎么样一个数据结构呢。微软并没有给出明确的数据结构,但是一个内核结构他肯定具有两个很重要的属性:1、内核对象使用计数器,每当一个内核对象被创建时,系统都会创建这个使用计数,并且初始为1,当这个使用计数减为0的时候,那么这个内核对象就被从内存中释放掉,也就不复存在了。2、内核对象安全性描述,关于这个描述,微软为我们提供了一个SECURITY_ATTRIBUTE结构,专门在创建内核对象时设置该内核对象的安全性描述。这里我们可以提供一条用来判断一个对象是否为内核对象的依据,即如果在创建对象句柄的函数参数中包含了SECURITY_ATTRIBUTE结构的参数,那么该对象肯定是内核对象。
内核对象是在“进程”中创建的,所以在一个进程中都有一份与内核对象有关的“进程句柄表”,所有在这个进程中创建或打开的内核对象的句柄都会出现在这个进程句柄表中,一旦这些句柄被关闭,其引用计数为0的时候它所标识的内核对象被操作系统释放,也就是删除,这个句柄也由操作系统自动将从进程句柄表中删除,释放内存空间。
也正是由于这个原因,同一个进程中的所有“线程”都可以访问该进程中的所有“内核对象句柄”(当然被关闭了的句柄也就不被称为内核对象句柄了)。
“进程句柄表”“可能”有几个重要的属性:1、一个指向对象内存块的指针(一个地址);2、访问标志;3、对象属性标志。
以上我们提到了,内核对象句柄是进程的句柄,那么有很多情况下我们需要在不同进程运行的线程能够共享一些内核对象,就很难得到了实现了。微软也考虑了这种情况,为此为我们提供了3种方法可以实现跨进程边界的共享内核对象,也可以说是三种情况下可以进行共享内核对象的可能操作吧。
第一种方法,就是当父进程在创建子进程前,将那些需要被子进程继承的内核对象句柄的标志位设置为1(通过创建内核对象时将SECURITY_ATTRIBUTE结构中的bInheritHandle置为TRUE就可以了),然后在创建子进程的函数中把bInheritHandle同样设置为TRUE,就实现了需要继承的内核对象被子进程继承的功能(同时子进程得到一份继承句柄的拷贝项加入自己的内核对象句柄表,同时这些内核对象的使用计数都加1)。这样在子进程中就可以使用父进程中的内核对象句柄了。
注意:在这里我们只是说在创建内核句柄的时候设置内核对象句柄的标志位,其实我们也可以调用SetHandlInformation()函数来重新设置内核对象的句柄位。
第二种方法,通过给对象命名的方法来实现,即我们在创建内核对象时给它一个字符串作为唯一标识该内核对象,因为非终端服务器类型的系统,其名字空间只有一个,所以在一个进程中用以上方法创建了一个内核对象,如果在其它进程再创建同一个名字的内核对象(需要前一进程创建的内核对象还未被内存释放),则在前后两个进程中就可以实现对该内核对象的共享了。以上说的是Greate函数方法,其实我们也可以采用Open函数方法。实质跟fileOpen函数具有类似的功效,不过一个对于文件来说,而前者则是对于内核对象来说而已。
第三种方法,通过DuplicateHandle()函数来实现,至于这个函数的参数较多,并且他的功能更为强大,所以书本上给我们罗列了三个例子,来详细说明了三种情况的应用。在这里笔者觉得看书上例子就可以了。
自然了,以上创建和共享了内核对象的句柄,本来就是用来使用这些句柄的,来对具体的对象进行操作的,在这里不作展开了。一旦一个内核对象在一个进程中用完之后,我们必须记得关闭该句柄,以使得内核对象的引用计数减1,从而实现该内核对象不再被访问时能够顺利的释放内存,我们可以使用传递内核对象的句柄给CloseHandle(HANDLE hHandle)函数来关闭一个内核对象。不必等到所有程序都关闭时才释放,这是对资源的一种浪费,同时还有可能造成资源的泄漏。
关于内核对象,就介绍到这里。
DuplicateHandle(),要涉及到三个进程,(一般很少有这种情况,最多的是两个进程). 第一个是你调用DuplicateHandle()函数的线程.第二个是你的源进程,第三个是目的进程. hSourceHandle,是你要复制的核心对象句柄.调用这个函数后,系统将检索对象名空间, 找到内核对象,增加计数,然后在你的目的进程中的核心对象表中,为这个对象分配一个索引,这个索引作为目的进程中该内核对象的句柄.然后你还要通知(发消息)目的进程说"喂,你现在可以用这个核心对象了",目的进程才可以操作该对象.
--------------------------------------------------------------------------------
声明:
GetModuleHandle( lpModuleName: PChar {模块名; 只能是映射到当前进程的模块} ): HMODULE; {返回模块句柄; 0 表示失败}
举例:
//获取当前模块的句柄 var s: string; h: Cardinal; begin {先取得模块名} s := Application.ExeName; s := ExtractFileName(s); {获取参数只要模块名就够了; 不需要路径(测试中有路径也可以)} h := GetModuleHandle(PChar(s)); ShowMessage(IntToStr(h)); {4194304} end; //用 nil 做参数即可获取当前模块的句柄 var h: Cardinal; begin h := GetModuleHandle(nil); ShowMessage(IntToStr(h)); {4194304} end; //获取系统骨干模块 Gdi32.dll 的句柄 var h: Cardinal; begin h := GetModuleHandle('Gdi32.dll'); ShowMessage(IntToStr(h)); {2012151808} end; //能不能获取记事本的句柄? var h: Cardinal; begin h := GetModuleHandle('notepad.exe'); ShowMessage(IntToStr(h)); {0; 失败} {因为当前进程和记事本的进程是相互独立的;} {如果用能够冲破进程、面向全局的 dll 应该可以.} end;
--------------------------------------------------------------------------------
GetWindowThreadProcessId - 获取指定窗口的进程 ID 或线程 ID
//声明:
GetWindowThreadProcessId( hWnd: HWND; {指定窗口句柄} lpdwProcessId: Pointer = nil {返回进程 ID 的指针} ): DWORD; {返回线程 ID}
举例:
var c: Cardinal; begin GetWindowThreadProcessId(Handle, @c); ShowMessage(IntToStr(c)); {2792; 随机的} {在本例中相同于 GetCurrentProcessID 的结果} c := GetCurrentProcessID; ShowMessage(IntToStr(c)); {2792} c := GetWindowThreadProcessId(Handle, nil); ShowMessage(IntToStr(c)); {2748} {在本例中相同于 GetCurrentThreadID 的结果} c := GetCurrentThreadID; ShowMessage(IntToStr(c)); {2748} end;
--------------------------------------------------------------------------------
函数功能描述:
GetProcAddress函数检索指定的动态链接库(DLL)中的输出库函数地址。
函数原型:
FARPROC GetProcAddress( HMODULE hModule, // DLL模块句柄 LPCSTR lpProcName // 函数名 );
参数:
hModule
[in] 包含此函数的DLL模块的句柄。LoadLibrary或者GetModuleHandle函数可以返回此句柄。
lpProcName
[in] 包含函数名的以NULL结尾的字符串,或者指定函数的序数值。
如果此参数是一个序数值,它必须在一个字的底字节,高字节必须为0。
返回值:
如果函数调用成功,返回值是DLL中的输出函数地址。
如果函数调用失败,返回值是NULL。得到进一步的错误信息,调用函数GetLastError。
注释:
GetProcAddress函数被用来检索在DLL中的输出函数地址。
--------------------------------------------------------------------------------
{返回进程的句柄} OpenProcess( dwDesiredAccess: DWORD; {访问选项} bInheritHandle: BOOL; {能否继承; True 表示能用 CreateProcess 继承句柄创建新进程} dwProcessId: DWORD {指定进程 ID} ): THandle; {成功会返回进程句柄; 失败返回 0} {获取指定进程的退出码} GetExitCodeProcess( hProcess: THandle; {进程句柄} var lpExitCode: DWORD {接收退出码} ): BOOL; {强制结束(其他)进程} TerminateProcess( hProcess: THandle; {进程句柄} uExitCode: UINT {退出码} ): BOOL; //提示: 关闭其他程序一般应该是向其主窗口发送WM_CLOSE 消息, 不行再用这个, 因为它不能关闭其关联的 DLL.
举例:
var id: Cardinal; wh: HWND; ph: THandle; ExitCode: DWORD; begin wh := FindWindow('#32770', nil); id := GetWindowThreadProcessId(wh, nil); ph := OpenProcess(PROCESS_TERMINATE, False, id); GetExitCodeProcess(ph, ExitCode); TerminateProcess(ph, ExitCode); end;
--------------------------------------------------------------------------------
读取内存流程
1.获得程序的句柄---->findwindow
2.获得进程ID---->GetWindowThreadProcessId
3.获得进程句柄---->OpenProcess
4.内存操作(读取写放)---->ReadProcessMemory
--------------------------------------------------------------------------------
用来读取指定进程的空间的数据,此空间必须是可以访问的,否则读取操作会失败!
函数原型:
BOOL ReadProcessMemory( HANDLE hProcess, {目标进程句柄} LPCVOID lpBaseAddress, {读取数据的起始地址} LPVOID lpBuffer, {存放数据的缓存区地址} DWORD nSize, {要读取的字节数} LPDWORD lpNumberOfBytesRead{实际读取数存放地址} );
参数
hProcess
目标进程的句柄,该句柄必须对目标进程具有PROCESS_VM_READ 的访问权限。
lpBaseAddress
从目标进程中读取数据的起始地址。在读取数据前,系统将先检验该地址的数据是否可读,如果不可读,函数将调用失败。
lpBuffer
用来接收数据的缓存区地址。
nSize
从目标进程读取数据的字节数。
lpNumberOfBytesRead
实际被读取数据大小的存放地址。如果被指定为NULL,那么将忽略此参数。
返回值
如果函数执行成功,返回值非零。
如果函数执行失败,返回值为零。调用 GetLastError 函数可以获取该函数执行错误的信息。
如果要读取一个进程中不可访问空间的数据,该函数就会失败。
备注
ReadProcessMemory 函数从目标进程复制指定大小的数据到自己进程的缓存区,任何拥有PROCESS_VM_READ 权限句柄的进程都可以调用该函数,目标进程的地址空间很显然要是可读的,但也并不是必须的,如果目标进程处于被调试状态的话。
举例:
var pid: DWORD; // 定义变量类型 hw: hwnd; pHandle, pAddr: DWORD; // 类型很重要 mNum, tmpNum: DWORD; // 类型很重要 begin hw := findwindow(nil, '扫雷'); // 获得扫雷程序的句柄 pid := GetWindowThreadProcessId(hw, nil); // 获得进程ID pHandle := OpenProcess(PROCESS_ALL_ACCESS, False, pid); // 获得进程句柄 pAddr := $01005194; // 查找的内存地址 ReadProcessMemory(pHandle, Pointer(pAddr), Pointer(@mNum), sizeof(mNum), tmpNum); // 记得格式 CloseHandle(pHandle); // 关闭句柄释放内存 end; //当雷少于8时提示 if mNum < 8 then begin button1.Caption := '成功'; end
--------------------------------------------------------------------------------
var hProcess: THandle; NewAddr: TNtJmpCode; OldAddr: array [0 .. 7] of Byte; ReadOK: Boolean; BaseAddr: Pointer; DllModule: HMODULE; dwReserved: DWORD; begin DllModule := GetModuleHandle(PChar('kernel32.dll')); // 获取模块句柄 if DllModule = 0 then DllModule := LoadLibrary(PChar('kernel32.dll')); // 如果得不到说明未被加载 // 得到模块入口地址(基址) BaseAddr := Pointer(GetProcAddress(DllModule, PChar('OpenProcess'))); // 获取当前进程句柄 hProcess := GetCurrentProcess; // 指向新地址的指针 NewAddr.MovEax := $B8; NewAddr.Addr := DWORD(NewOpenProcess); NewAddr.JmpCode := $E0FF; // 保存原始地址 ReadOK := ReadProcessMemory(hProcess, BaseAddr, @OldAddr, 8, dwReserved); //从目标进程复制指定大小的数据到自己进程的缓存区 if (ReadOK = False) then Exit; // 写入新的地址 WriteProcessMemory(hProcess, BaseAddr, @NewAddr, 8, dwReserved); if (ReadOK = False) then Exit; // 恢复地址 WriteProcessMemory(hProcess, BaseAddr, @OldAddr, 8, dwReserved); end;
--------------------------------------------------------------------------------
调用外部 DLL 中的函数
1. 早绑定
{在interface 区,声明函数MB:} function MB(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall; {在 implementation 区,说明函数的来源:} function MB; external 'user32.dll' name 'MessageBoxA';
2. 晚绑定
//晚绑定,也就是动态调用外部函数主要用以下三个命令: LoadLibrary:获取 DLL GetProcAddress:获取函数 FreeLibrary:释放 {定义一个过程类型,参数要和需要的函数一致} type TMB = function(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall; {声明函数 MB} MB: TMB; var inst: LongWord; {声明一个变量来记录要使用的 DLL 句柄} begin inst := LoadLibrary('user32.dll'); if inst <> 0 then MB := GetProcAddress(inst, 'MessageBoxW');//按函数名找,大小写敏感。如果你知道自动化对象的本质就清楚了。 //找到函数入口指针就调用 MB(...); end; FreeLibrary(inst); {记得释放}
--------------------------------------------------------------------------------
内存映像文件
type PShareMem = ^TShareMem; TShareMem = record ClientFormHandle: THandle; end; var MapHandle: Thandle; PShare: PShareMem; cdds: TCopyDataStruct; begin MapHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, Pchar('Map Name')); if MapHandle = 0 then MapHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, 4, Pchar('Map Name')); if MapHandle <> 0 then PShare := PShareMem(MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0)); if PShare <> nil then begin outputdebugstring(Pchar('两个程序之间用消息传递数据')); cdds.lpData := Pchar(FIE.LocationURL + #0); // 要发送的字符串, #0 表示 PChar 结束 cdds.dwData := WM_COPYDATA; // 指定消息类型 cdds.cbData := ByteLength(FIE.LocationURL) + 2; // 指定要发送的数据的大小,多出的两个字节用于后面的 #0 sendmessage(PShare^.ClientFormHandle, WM_COPYDATA, 0, Integer(@cdds)); // 发送 end; CloseHandle(MapHandle); end; begin { 创建一个文件映射内核对象 } HMapping := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, SizeOf(TShareMem), pchar('Map Name')); if (HMapping = 0) then begin ShowMessage('不能创建内存映射文件'); Application.Terminate; exit; end; { 将文件数据映射到进程的地址空间 } PShare := PShareMem(MapViewOfFile(HMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0)); if PShare = nil then begin CloseHandle(HMapping); ShowMessage('Can''t View Memory Map'); Application.Terminate; exit; end; { 既然我们通过pvFile得到了映象视图的起始地址,那么可以对视图做一些操作了 } end; { 当创建了一个文件映射对象之后,仍然必须让系统为文件的数据保留一个地址空间区域, 并将文件的数据作为映射到该区域的物理存储器进行提交。 } PShare^.ClientFormHandle := self.Handle;
const HOOK_MEM_FILENAME = 'tmp.hkt'; // 文件映射对象名 type PShareURL = ^TShareURL; TShareURL = record LastURL: array [0 .. 265] of AnsiChar; end; var hMappingFile: Thandle; // 内存映射 pLastUrl: PShareURL; procedure SetMapMem(LocalURL: String); begin hMappingFile := OpenFileMapping(FILE_MAP_WRITE, False, HOOK_MEM_FILENAME); if hMappingFile = 0 then begin hMappingFile := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, 4, HOOK_MEM_FILENAME); end; pLastUrl := MapViewOfFile(hMappingFile, FILE_MAP_WRITE or FILE_MAP_READ, 0, 0, 0); if pLastUrl = nil then begin CloseHandle(hMappingFile); Exception.create('Can not create ShareMemory'); Exit; end; StrPCopy(pLastUrl^.LastURL, LocalURL); end; procedure GetMapMemValue(var LocalURL: String); begin hMappingFile := OpenFileMapping(FILE_MAP_WRITE, False, HOOK_MEM_FILENAME); pLastUrl := MapViewOfFile(hMappingFile, FILE_MAP_WRITE or FILE_MAP_READ, 0, 0, 0); LocalURL := (pLastUrl^).LastURL; end;
--------------------------------------------------------------------------------
程序运行时自动注册OCX或DLL
工程文件中添加:
uses Forms, SysUtils, Dialogs, ActiveX, Windows, Unit1 in 'Unit1.pas' {Frm_Main}; {$R *.res} var OCXHand:THandle; RegFunc:TDLLRegisterServer; sfile:string; begin Application.Initialize; //运行程序前先注册OCX文件 sfile:=ExtractFilePath(Application.ExeName)+myocx.ocx'; if not FileExists(sfile) then begin ShowMessage(myocx.ocx文件丢失,请重新安装程序!'); Application.Terminate; end; try OCXHand:=LoadLibrary(PChar(sfile)); RegFunc:=GetProcAddress(OCXHand, 'DllRegisterServer'); if RegFunc <> 0 then begin ShowMessage('myocx.ocx注册失败!'); Application.Terminate; end; finally FreeLibrary(OCXHand); end; Application.CreateForm(TFrm_Main, Frm_Main); Application.Run; end.