关于跨进程使用回调函数的研究:以跨进程获取Richedit中RTF流为例(在Delphi 初始化每一个TWinControl 对象时,将会在窗体 的属性(PropData)中加入一些标志,DLL的HInstance的值与HOST 进程的HInstance并不一致)
建议先参考我上次写的博文跨进程获取Richedit中Text:
获得QQ聊天输入框中的内容
拿到这个问题,我习惯性地会从VCL内核开始分析。找到TRichEdit声明的单元,分析TRichEdit保存为RTF流的代码。(分析VCL内核代码方便了解Windows标准API的封装和使用) 打开声明TRichEdit的ComCtrls.pas单元。搜索"TRichEditStrings"(保存流使用TRichEdit.Lines.SaveToStream方法,TRichEditStrings为TRichEdit.Line的类型)
private
RichEdit: TCustomRichEdit;
FPlainText: Boolean;
FConverter: TConversion;
procedure EnableChange(const Value: Boolean);
protected
function Get(Index: Integer): string; override;
function GetCount: Integer; override;
procedure Put(Index: Integer; const S: string); override;
procedure SetUpdateState(Updating: Boolean); override;
procedure SetTextStr(const Value: string); override;
public
destructor Destroy; override;
procedure Clear; override;
procedure AddStrings(Strings: TStrings); override;
procedure Delete(Index: Integer); override;
procedure Insert(Index: Integer; const S: string); override;
procedure LoadFromFile(const FileName: string); override;
procedure LoadFromStream(Stream: TStream); override;
procedure SaveToFile(const FileName: string); override;
procedure SaveToStream(Stream: TStream); override;
property PlainText: Boolean read FPlainText write FPlainText;
end; 寻找到SaveToStream的方法 procedure TRichEditStrings.SaveToStream(Stream: TStream); var EditStream: TEditStream;
TextType: Longint; StreamInfo: TRichEditStreamInfo; Converter: TConversion; begin if FConverter <> nil then Converter := FConverter else Converter := RichEdit.DefaultConverter.Create; StreamInfo.Stream := Stream; StreamInfo.Converter := Converter; try with EditStream do begin dwCookie := LongInt(Pointer(@StreamInfo)); pfnCallBack := @StreamSave; dwError := 0; end; if PlainText then TextType := SF_TEXT else TextType := SF_RTF; SendMessage(RichEdit.Handle, EM_STREAMOUT, TextType, Longint(@EditStream)); if EditStream.dwError <> 0 then raise EOutOfResources.Create(sRichEditSaveFail); finally if FConverter = nil then Converter.Free; end; end;
看关键的一句:“SendMessage(RichEdit.Handle, EM_STREAMOUT, TextType, Longint(@EditStream));” 这下明白了,获取RTF关键的是向Richedit发送EM_STREAMOUT消息。 关于EM_STREAMOUT消息想了解更多可以查阅MSDN: EM_STREAMOUT wParam = (WPARAM) (UINT) uFormat; lParam = (LPARAM) (EDITSTREAM FAR *) lpStream; 进程间的内存地址是相对的。 A进程$00450000内存地址值为34,那么B进程$00450000内存地址就不一定是34了。 在发送EM_STREAMOUT消息时,lParam参数表示的地址就是相对于目标进程的。 跨进程访问内存主要用到如下API函数: GetWindowThreadProcessId -- 根据窗体句柄获得其所在的线程、进程ID OpenProcess -- 打开进程并返回访问句柄 VirtualAllocEx -- 分配进程虚拟内存空间,返回所分配的内存地址。 VirtualFreeEx -- 释放进程虚拟内存空间 ReadProcessMemory、WriteProcessMemory -- 读写进程内存数据。 和以往不一样,这个消息用到了回调函数“pfnCallBack := @StreamSave;” 函数也是存放在内存中的数据(一些机器指令),访问函数同样会碰到进程间不能直接访问内存的问题。 也就是说:需要将函数数据写入到目标进程中,才能被正常调用。 【如何获得函数的数据?】
function MyFunction(A, B: Integer): Integer;
begin
Result := A + B;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text := IntToStr(Integer(@MyFunction));
end; 函数地址容易得到。调试如上代码,点击按钮获得函数地址,打开CPU查看器(Ctrl+Alt+C),定位函数地址。 这样将看到如图:
7105 --------jon +$05
E81f43FBFF --call @IntOver
C3 --------- ret 前面就是十六进制数据,后面就是该数据表示的机器指令。 这些就是函数数据,将它写入到目标进程就可以调用了!!! 两个进程载入相同的DLL那么DLL的函数地址则是相同的,也就是说API函数SendMessage在A、B两个进程的地址一致。有了这点,利用系统API函数SendMessage发送WM_COPYDATA消息就可以交互数据了。 当然,如果指令里有相对地址的访问也得克隆才成,比如上面的"E81f43FBFF --call @IntOver"就用到了相对地址。囧 可以将编译条件中“溢出、范围、IO"检查都关掉,减少相对地址的访问。 跨进程之前,先在本进程实验通过再说: 【第一个实验:正常调用回调函数】 function MySendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
Result := SendMessage(hWnd, Msg, wParam, lParam);
end;
type
TMySendMessage = function (hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var vMySendMessage: TMySendMessage = MySendMessage;
function EditStreamCallBack(dwCookie: Longint; pbBuff: PByte;
cb: Longint; var pcb: Longint): Longint; stdcall;
var
vCopyDataStruct: TCopyDataStruct;
begin
pcb := cb;
vCopyDataStruct.dwData := 0;
vCopyDataStruct.cbData := cb;
vCopyDataStruct.lpData := pbBuff;
vMySendMessage(dwCookie, WM_COPYDATA, 0, Integer(@vCopyDataStruct));
Result := ERROR_SUCCESS;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
vEditStream: TEditStream;
begin
vEditStream.dwCookie := Handle;
vEditStream.dwError := 0;
vEditStream.pfnCallback := EditStreamCallBack;
FMemoryStream := TMemoryStream.Create;
SendMessage(RichEdit1.Handle, EM_STREAMOUT, SF_RTF, Integer(@vEditStream));
FMemoryStream.Position := 0;
Memo1.Lines.LoadFromStream(FMemoryStream);
FMemoryStream.Free;
end; “EditStreamCallBack“就是要复制到目标进程的函数数据。 调试通过后,获得其十六进制数据。
{ 55 } PUSH EBP { 8BEC } MOV EBP,ESP
{ 83C4F4 } ADD ESP,$F4
{ 8B4510 } MOV EAX,DWORD PTR [EBP+$10]
{ 8B5514 } MOV EDX,DWORD PTR [EBP+$14]
{ 8902 } MOV DWORD PTR [EDX],EAX
{ 33D2 } XOR EDX,EDX
{ 8955F4 } MOV DWORD PTR [EBP-$0C],EDX
{ 8945F8 } MOV DWORD PTR [EBP-$08],EAX
{ 8B450C } MOV EAX,DWORD PTR [EBP+$0C]
{ 8945FC } MOV DWORD PTR [EBP-$04],EAX
{ 8D45F4 } LEA EAX,DWORD PTR [EBP-$0C]
{ 50 } PUSH EAX
{ 6A00 } PUSH $00
{ 6A4A } PUSH $4A
{ 8B4508 } MOV EAX,DWORD PTR [EBP+$08]
{ 50 } PUSH EAX
{ FF15B88C4500} CALL DWORD PTR [$00458CB8]
{ 33C0 } XOR EAX,EAX
{ 8BE5 } MOV ESP,EBP
{ 5D } POP EBP
{ C21000 } RET $0010 其中{ FF15B88C4500} CALL DWORD PTR [$00458CB8]里的[$00458CB8] 里是相对地址,不同的调试环境会不一样。 我们这里[$00458CB8]里存放的就是系统API函数SendMessage的地址。 【第二个实验:拼装函数数据】 const
EditStreamCallBackBytes =
#$55 + // PUSH EBP
#$8B#$EC + // MOV EBP,ESP
#$83#$C4#$F4 + // ADD ESP,$F4
#$8B#$45#$10 + // MOV EAX,DWORD PTR [EBP+$10]
#$8B#$55#$14 + // MOV EDX,DWORD PTR [EBP+$14]
#$89#$02 + // MOV DWORD PTR [EDX],EAX
#$33#$D2 + // XOR EDX,EDX
#$89#$55#$F4 + // MOV DWORD PTR [EBP-$0C],EDX
#$89#$45#$F8 + // MOV DWORD PTR [EBP-$08],EAX
#$8B#$45#$0C + // MOV EAX,DWORD PTR [EBP+$0C]
#$89#$45#$FC + // MOV DWORD PTR [EBP-$04],EAX
#$8D#$45#$F4 + // LEA EAX,DWORD PTR [EBP-$0C]
#$50 + // PUSH EAX
#$6A#$00 + // PUSH $00
#$6A#$4A + // PUSH $4A
#$8B#$45#$08 + // MOV EAX,DWORD PTR [EBP+$08]
#$50 + // PUSH EAX
#$FF#$15#$00#$00#$00#$00 + // CALL DWORD PTR [H] -- String Index:43
#$33#$C0 + // XOR EAX,EAX
#$8B#$E5 + // MOV ESP,EBP
#$5D + // POP EBP
#$C2#$10#$00 + // RET $0010
#$00#$00#$00#$00 + // Api Address -- String Index:55
#$00#$00#$00#$00 + // _editstream : dwCookie -- String Index:59
#$00#$00#$00#$00 + // _editstream : dwError
#$00#$00#$00#$00; // _editstream : pfnCallback
type
TVclApi = packed record //JMP DWORD PTR [$HHHHHHHH]
rJmp: Word; // FF 25
rAddress: PInteger; // API实际地址
end;
PVclApi = ^TVclApi;
procedure TForm1.Button2Click(Sender: TObject);
type
PEditStream = ^TEditStream;
var
vEditStreamCallBack: string;
begin
vEditStreamCallBack := EditStreamCallBackBytes;
PInteger(@vEditStreamCallBack[43])^ := Integer(@vEditStreamCallBack[55]);
PInteger(@vEditStreamCallBack[55])^ := PVclApi(@SendMessage)^.rAddress^;
PEditStream(@vEditStreamCallBack[59])^.dwCookie := Handle;
PEditStream(@vEditStreamCallBack[59])^.pfnCallback := @vEditStreamCallBack[1];
FMemoryStream := TMemoryStream.Create;
SendMessage(RichEdit1.Handle, EM_STREAMOUT, SF_RTF, Integer(@vEditStreamCallBack[59]));
FMemoryStream.Position := 0;
Memo1.Lines.LoadFromStream(FMemoryStream);
FMemoryStream.Free;
end; VCL中调用API时使用JMP指令。 ”PVclApi(@SendMessage)^.rAddress^”就是获得SendMessage实际地址。 好了,本进程的实验做完后,我们就要拿另一个进程开刀了。 封装一下,最终代码如下:
uses RichEdit;
{$WARN SYMBOL_DEPRECATED OFF}
type
TRichEditStreamReader = class
private
FStream: TStream;
FHandle: THandle;
protected
procedure WndProc(var Message: TMessage); virtual;
public
constructor Create(AStream: TStream);
destructor Destroy; override;
property Handle: THandle read FHandle;
end;
{ TRichEditStreamReader }
constructor TRichEditStreamReader.Create(AStream: TStream);
begin
FStream := AStream;
FHandle := AllocateHWnd(WndProc);
end;
destructor TRichEditStreamReader.Destroy;
begin
DeallocateHWnd(FHandle);
inherited;
end;
procedure TRichEditStreamReader.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_COPYDATA:
begin
if not Assigned(FStream) then Exit;
FStream.Write(PCopyDataStruct(Message.LParam)^.lpData^,
PCopyDataStruct(Message.LParam)^.cbData);
end;
end;
end;
function Process_ReadRichEditStream(
AHandle: THandle; AStream: TStream; AFormat: Longword): Boolean;
type
TVclApi = packed record //JMP DWORD PTR [$HHHHHHHH]
rJmp: Word; // FF 25
rAddress: PInteger; // API实际地址
end;
PVclApi = ^TVclApi;
const
EditStreamCallBackBytes =
#$55 + // PUSH EBP
#$8B#$EC + // MOV EBP,ESP
#$83#$C4#$F4 + // ADD ESP,$F4
#$8B#$45#$10 + // MOV EAX,DWORD PTR [EBP+$10]
#$8B#$55#$14 + // MOV EDX,DWORD PTR [EBP+$14]
#$89#$02 + // MOV DWORD PTR [EDX],EAX
#$33#$D2 + // XOR EDX,EDX
#$89#$55#$F4 + // MOV DWORD PTR [EBP-$0C],EDX
#$89#$45#$F8 + // MOV DWORD PTR [EBP-$08],EAX
#$8B#$45#$0C + // MOV EAX,DWORD PTR [EBP+$0C]
#$89#$45#$FC + // MOV DWORD PTR [EBP-$04],EAX
#$8D#$45#$F4 + // LEA EAX,DWORD PTR [EBP-$0C]
#$50 + // PUSH EAX
#$6A#$00 + // PUSH $00
#$6A#$4A + // PUSH $4A
#$8B#$45#$08 + // MOV EAX,DWORD PTR [EBP+$08]
#$50 + // PUSH EAX
#$FF#$15#$00#$00#$00#$00 + // CALL DWORD PTR [H] -- String Index:43
#$33#$C0 + // XOR EAX,EAX
#$8B#$E5 + // MOV ESP,EBP
#$5D + // POP EBP
#$C2#$10#$00 + // RET $0010
#$00#$00#$00#$00 + // Api Address -- String Index:55
#$00#$00#$00#$00 + // _editstream : dwCookie -- String Index:59
#$00#$00#$00#$00 + // _editstream : dwError
#$00#$00#$00#$00; // _editstream : pfnCallback
type
PEditStream = ^TEditStream;
var
vEditStreamCallBack: string;
vProcessId: DWORD;
vProcess: THandle;
vPointer: Pointer;
vNumberOfBytesRead: Cardinal;
vRichEditStreamReader: TRichEditStreamReader;
begin
Result := False;
if not Assigned(AStream) then Exit;
if not IsWindow(AHandle) then Exit;
GetWindowThreadProcessId(AHandle, @vProcessId);
vProcess := OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_READ or
PROCESS_VM_WRITE, False, vProcessId);
try
vPointer := VirtualAllocEx(vProcess, nil, 4096, MEM_RESERVE or MEM_COMMIT,
PAGE_READWRITE);
vRichEditStreamReader := TRichEditStreamReader.Create(AStream);
try
vEditStreamCallBack := EditStreamCallBackBytes;
PInteger(@vEditStreamCallBack[43])^ := Integer(vPointer) + 55 - 1;
PInteger(@vEditStreamCallBack[55])^ := PVclApi(@SendMessage)^.rAddress^;
PEditStream(@vEditStreamCallBack[59])^.dwCookie := vRichEditStreamReader.Handle;
PEditStream(@vEditStreamCallBack[59])^.pfnCallback := vPointer;
WriteProcessMemory(vProcess, vPointer, @vEditStreamCallBack[1],
Length(vEditStreamCallBack), vNumberOfBytesRead);
SendMessage(AHandle, EM_STREAMOUT, AFormat, Integer(Integer(vPointer) + 59 - 1));
finally
vRichEditStreamReader.Free;
VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE);
end;
finally
CloseHandle(vProcess);
end;
end; { Process_ReadRichEditStream }
procedure TForm1.Button1Click(Sender: TObject);
var
vHandle: THandle;
vMemoryStream: TMemoryStream;
begin
vHandle := FindWindow('WordPadClass', nil);
if vHandle = 0 then Exit;
vHandle := FindWindowEx(vHandle, 0, 'RICHEDIT50W', nil);
if vHandle = 0 then Exit;
vMemoryStream := TMemoryStream.Create;
try
Process_ReadRichEditStream(vHandle, vMemoryStream, SF_RTF);
vMemoryStream.Position := 0;
RichEdit1.PlainText := False;
RichEdit1.Lines.LoadFromStream(vMemoryStream);
finally
vMemoryStream.Free;
end;
end;
下面一段是某个文章的摘抄:
------------------------------------------------------------
分析RTL 源码发现,在Delphi 初始化每一个TWinControl 对象时,将会在窗体
的属性(PropData)中加入一些标志,其中的一个标志是用于存放该对象的内存地址
的。而FindControl 就是通过查看该属性来获取对象在内存中的起始地址。——即内
存实例地址。
这个“标志”,其固定形式为:
ControlAtomString := Format('ControlOfs%.8X%.8X',
[HInstance, GetCurrentThreadID]);
由于在DLL 中,HInstance 的值与HOST 进程的HInstance 并不一致,所以,在
DLL 中的ControlAtomString 也就与HOST 进程不一致。那么,通过B 标志去查A
标志的属性,自然什么也得不到了。
通过 Win32API GetWindowLong(),可以获得一个窗体所在的(真实的)实例句柄。
这样,可以在DLL 中重新构造针对于任何一个窗体句柄的ControlAtomString。
关于跨进程使用回调函数的研究:以跨进程获取Richedit中RTF流为例(在Delphi 初始化每一个TWinControl 对象时,将会在窗体 的属性(PropData)中加入一些标志,DLL的HInstance的值与HOST 进程的HInstance并不一致)的更多相关文章
- 对ajax回调函数的研究
1.1开发中遇到的问题 最近开发中我和同事都碰到这样的问题,我们使用jQuery的ajax方法做服务端的校验,在success方法里将验证结果存储到一个js的公共变量或者是页面里的隐藏域,接下来的代码 ...
- python网络编程--管道,信号量,Event,进程池,回调函数
1.管道 加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行任务修改,即串行修改,速度慢了,但牺牲了速度却保证了数据安全. 文件共享数据实现进程间的通信,但问题是: 1.效率低(共享 ...
- Python并发编程-进程池回调函数
回调函数不能传参数 回调函数是在主进程中执行的 from multiprocessing import Pool import os def func1(n): print('in func1', o ...
- array_map 等php回调函数使用问题(关联数组下标获取)
前言:我自己用此类回调函数,来替代 foreach 纯粹是用为代码的简洁性,让代码更好看.(我有点代码小洁癖~) 1.array_reduce 当迭代处理一个一维索引数组时,在回调函数内是无法获取到当 ...
- Spring 中初始化一个Bean对象时依赖其他Bean对象空指针异常
1. Bean依赖关系 一个配置类的Bean,一个实例Bean: 实例Bean初始化时需要依赖配置类的Bean: 1.1 配置类Bean @ConfigurationProperties(prefix ...
- JSONP跨域后回调函数中的参数使用
有关于跨域的解决方案网上的资源十分丰富,我是参考这个博主的:https://blog.csdn.net/u014607184/article/details/52027879: 这里的response ...
- java 中,new一个新对象时,是先给成员变量赋上初值后 再来调用类中的构造函数的。
今天学习时法现一个问题,我们定义了一个Test类,在主类中new了一个他的对象,发现:在新建对象中所有的成员变量是先给定了默认初值的:0,null或者false, 之后再调用的构造函数.(如果变量是由 ...
- 使用进程池模拟多进程爬取url获取数据,使用进程绑定的回调函数去处理数据
1 # 使用requests请求网页,爬取网页的内容 2 3 # 模拟使用进程池模拟多进程爬取网页获取数据,使用进程绑定的回调函数去处理数据 4 5 import requests 6 from mu ...
- 回调函数及数组中sort()方法实现排序的原理
1.回调函数:把一个方法A当一个参数值传递到另外一个函数B中,在B执行的过程当中我们随时根据需求让A方法执行: 什么是回调 :它是异步编程基本的方法,需要异步处理的时候一般采用后续传递的方式,将后 ...
随机推荐
- 调制:调幅(AM)与调频(FM)
AM:amplitude modulation,幅度调制: FM:Frequency Modulation,频率调制: 1. 为什么要调制 MW:Medium Wave,中波,SW:Short Wav ...
- 配置ANDROID_HOME
原文:配置ANDROID_HOME 1.在环境变量中设置一个名为ANDROID_HOME,变量值为SDK路径 2.添加至Path中 备注:ANDROID_HOME的变量值仅允许一个
- [MFC]SDI在图片背景上实现文本跟随鼠标移动
SDI是单文档接口应用程序的简称.本文要实现的是在视图区域显示一张图片,然后在图片表层显示文字,并且文字跟随鼠标移动.思考一下,可以判断这个问题一共分为以下几个部分:1.显示图片:2.找到鼠标的位置: ...
- Apache POI Word基本使用
Apache POI Word 1.什么是Apache POI? Apache POI是一个流行的API,使用Java程序创建,修改和显示MS-Office文件. 它是由Apache Software ...
- delphi 10.2 创建并使用资源文件(一共22种格式,RCDATA是自定义格式)
windows支持以下资源格式: 1 2 //下面是 Windows 支持的资源格式: RT_CURSOR = MakeIntResource(1); RT_BITMAP = MakeIntResou ...
- cordova使用cordova-plugin-baidumaplocation插件获取定位
原文:cordova使用cordova-plugin-baidumaplocation插件获取定位 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/m ...
- url操作等
取得url ?及以前: baseUrl = url.substr(0,url.indexOf('?')+1) searchParam = searchParam.slice(0, -1);//去掉最后 ...
- std::string 简单入门
string的定义原型 typedef basic_string<char, char_traits<char>, allocator<char> > string ...
- WPF 圆角textbox
原文:WPF 圆角textbox 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/a771948524/article/details/9245965 ...
- Matlab Tricks(二十四)—— 将一副图像逆时针旋转 180°
function I2 = rot180(I) I2 = I(end:-1:1, end:-1:1); % 上下颠倒,左右颠倒: