建议先参考我上次写的博文跨进程获取Richedit中Text: 获得QQ聊天输入框中的内容
拿到这个问题,我习惯性地会从VCL内核开始分析。找到TRichEdit声明的单元,分析TRichEdit保存为RTF流的代码。(分析VCL内核代码方便了解Windows标准API的封装和使用) 打开声明TRichEdit的ComCtrls.pas单元。搜索"TRichEditStrings"(保存流使用TRichEdit.Lines.SaveToStream方法,TRichEditStrings为TRichEdit.Line的类型) TRichEditStrings = class(TStrings)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),定位函数地址。 这样将看到如图: 03C2 --------add eax,edx
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;