加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

DELPHI 跨进程获取其他程序的DBGrid内容

发布时间:2020-12-15 10:03:20 所属栏目:大数据 来源:网络整理
导读:一、思考与启发? ?1.对于Windows的Standard Controls,基本上大家应该是都会的: ? GetWindowText,WM_GETTEXT可以获取EDIT的文本内容; ? LB_GETTEXT可以获取LISTBOX列表项的文本内容; ? CB_GETLBTEXT可以获取COMBOBOX下拉列表项的文本内容; ? 这里我就不
一、思考与启发?
?1.对于Windows的Standard Controls,基本上大家应该是都会的:
? GetWindowText,WM_GETTEXT可以获取EDIT的文本内容;
? LB_GETTEXT可以获取LISTBOX列表项的文本内容;
? CB_GETLBTEXT可以获取COMBOBOX下拉列表项的文本内容;
? 这里我就不多说了。
? 对于Windows的Common Controls,如LISTVIEW、TREEVIEW等,在本进程自身中获取的话可以直接用LVM_GETITEMTEXT,TVM_GETITEM消息,跨进程的话,还需要另外用到一些API函数,有兴趣的可以看看这个贴子:
?? http://www.delphibbs.com/delphibbs/dispq.asp?lid=3224504
? DBGrid是Delphi的自写控件,不是Windows控件,没有什么消息可以利用来获取它所显示的数据记录内容。
2.运用鼠标屏幕取词的技术,可以获取到鼠标位置的显示内容,这个是采用的ApiHook技
? 术,截获屏幕输出函数,从而得到输出内容,其实现原理这里我不想多说,delphibbs
? 论坛上相关的贴子很多,大家搜索一下就能找到。不过这种方法的实用性不很强,因为
? 它需要在鼠标位置使输出重画,才能截获到输出内容的;而且如果DBGrid显示不完整有
? 滚动条的情况下,没有显示的字段、记录的内容就不能截获到。
3.在本进程自身中,是可以获得DBGrid显示的记录内容的,如
? DBGrid1.Columns[1].Field.DisplayText,不过前提是要得到对象实例DBGrid1,假如
? 我们只知道DBGrid1的句柄。这个当然也没有什么问题,FindControl函数可以完成这个
? 功能。
4.上面说到,只要能够得到DBGrid的对象实例,我们就能够获得它的内容啦。前面讲的是
? 在本进程可以获取到DBGrid内容,而我们现在要讲的是跨进程获取其他程序的DBGrid??? 内容,那么是不是真的能够得到其他进程的DBGrid对象实例呢?如果能够的话,又该怎么
? 实现呢?另外,还有一个问题,我们获取到了这个DBGrid的对象实例,但是这个地址是
? 其他进程地址空间中的一个虚拟地址,我们能够DBGrid1.Columns[1].Field.DisplayText
? 这样在自己的进程中访问获取其内容吗?
二、必备知识基础?
?1.进程地址空间?
? Win32系统,所有32位应用程序都有4GB的进程地址空间(32位地址最多可以映射4GB的内存)。应用程序可以访问2GB的进程地址空间,称为用户模式虚拟地址空间。应用程序拥有的所有线程都共享同一个用户模式虚拟地址空间。其余2GB为操作系统保留(也称为内
? 核模式地址空间)。?
? 而从Win2000 Server开始的所有操作系统版本,还有一个boot.ini
? 开关,可以为应用程序提供访问3GB的进程地址空间的权限,从而将内核模式地址空间
? 压缩为1GB。一般地,一个用户进程不可以直接访问另外一个用户进程的地址空间。进
? 程地址空间的描述请参考? http://msdn2.microsoft.com/zh-cn/library/ms189334.aspx
? 和《Windows核心编程》的第13章 Windows的内存结构。??
? 顺便说一句,《Windows核心编程》是一本相当经典的好书,呵呵,建议大家都应该购
? 买收藏一本,认认真真地把它看几遍。题外话,呵呵??
?2.DLL基础?
?1)先说说为什么要使用DLL?
?a.它可以动态装载,这样不必要在应用程序初始化就装载所有的代码,可以根据需要、操
? 作再装载DLL,这样启动速度比较快,也更节省内存;
b.便于项目管理,不同的开发人员、开发小组在不同的模块上工作;
c.有助于解决操作系统平台的差异,比如98/2000/XP等枚举进程可以用
? CreateToolhelp32Snapshot,Process32First,Process32Next,而NT上就不能用,而要
? 用psapi.dll中的EnumProcesses,EnumProcessModules,GetModuleFileNameEx等函数;
d.可以用多种编程语言编写,开发人员可以选用自己最擅长的语言;
e.有助于资源的共享和应用程序的本地化,DLL可以包含对象框模板、图标、字符串、位
? 图等资源;?
打错了,是对话框模板?
?f.可以实现一些特殊的目的,如系统范围的全局钩子,就要求写在DLL中才行。
2)EXE程序的全局变量不能被同一个EXE程序的多个运行实例所共享,DLL中的全局变量的处理方法也是一样的。也就是说,当一个进程将一个DLL映射到它的地址空间中去的时
?候,系统会同时创建全局变量的实例。?
也就是说不同进程间用的同一个DLL,全局变量的值可能是不一样的,不能共享?
?3)DLL和EXE之间的数据共享,这有很多技术,全局原子、内存映射、WM_COPYDATA消息等,
? 这个不是本文的重点,这里我们就不一一赘述。?
?4)Delphi写DLL要注意的问题:?
这一点我们需要注意??
?a.参数和返回值为string、动态数组类型时,DLL和EXE都要把ShareMem作为.dpr工程的第一个单元引用。当然最好是不要使用string、动态数组类型,可以改用PChar、数组指
? 针类型,如果是混合语言编程使用的话,就一定不能用string、动态数组类型。这样做
? 的原因是DLL和EXE的内存管理器(MemoryManager)不是一个,而string、动态数组类型是通过引用计数由Delphi自动进行内存管理的,它何时分配何时释放,我们不能显式的
? 知道的,DLL分配而EXE释放的话,这样就出问题了。用ShareMem就是为了让它们统一使用一个内存管理器进行内存分配释放。?
关于DLL和EXE内存管理器不同这一点,非常重要!!!?
?b.DLL和EXE的VCL类体系不是一个,它们各自有一套,因此,从EXE传递过去的对象,要在DLL中用is判断类型和as作类型转换,那都不能得到期望的结果。?
如果是bpl,就不会有这个问题??
?c.DLL中应用ADO、窗体(模态、非模态、MDI子窗体)、线程等的一些相关问题与今天的主题关系不大,就不作多讲了。参考
?? http://www.delphibbs.com/delphibbs/dispq.asp?LID=2977902
?? http://www.delphibbs.com/keylife/iblog_show.asp?xid=2438
?? http://www.delphibbs.com/keylife/iblog_show.asp?xid=11558?等?
关于DLL的详细论述请参考《Windows核心编程》第19、20章。?
?3.钩子(Hook)?
? Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递
? 来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其
? 他应用程序的消息,来完成普通应用程序难以实现的功能。钩子可以监视系统或进程中
? 的各种事件消息,截获发往目标窗口的消息并进行处理。这样,我们就可以在系统中安
? 装自定义的钩子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标
? 的输入,屏幕取词,日志监视等等。
? 按事件分类,有如下的几种常用类型的钩子:
? 1)键盘钩子可以监视各种键盘消息。
? 2)鼠标钩子可以监视各种鼠标消息。
? 3)外壳钩子可以监视各种Shell事件消息。
? 4)日志钩子可以记录从系统消息队列中取出的各种事件消息。
? 5)窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。?
? 安装钩子:SetWindowsHookEx
? 卸载钩子:UnhookWindowsHookEx
? 钩子回调函数形式:
function GetMsgProc(Code: UINT; lParam: LPARAM; wParam: WPARAM): LRESULT; stdcall;?
? 系统全局钩子必须在DLL中,因为它影响系统的所有应用程序,需要在消息发生时被系
? 统映射到其他进程的地址空间,从而调用DLL中的钩子回调函数。钩子所在的DLL被映射时,是整体映射被加载到被挂钩的进程的地址空间中,而不仅仅是钩子回调函数,这
? 样,被挂钩的进程就可以访问DLL中的变量和调用其他函数的。利用这个特点,在应用
? 中就可以做到很多特定的功能,比如屏幕取词、木马、三级跳隐藏进程等。
注意:安装了某类消息的系统全局钩子之后,在该类消息发生时钩子DLL会被系统映射到其他进程的地址空间,从而调用DLL中的钩子回调函数。?
还有一点要注意:当SetWindowsHookEx调用成功后,系统会自动映射这个DLL到被挂钩
? 的线程,但并不是立即映射。因为所有的Windows钩子都是基于消息的,直到一个适当
? 的事件发生后这个DLL才被映射。同理,UnhookWindowsHookEx调用之后,也是在某个适当的事件发生之后DLL才真正地从被挂钩线程卸载。?
?function GetMsgProc(Code: UINT; lParam: LPARAM; wParam: WPARAM): LRESULT; stdcall;?
能介绍这个回调函数什么用吗?
必备的知识基础就讲这么多,有什么问题待会儿我们大家再探讨?
?Code:钩子代码,通常为HA_ACTION时是用户要处理的?
?lParam,wParam:跟具体安装的钩子类型有关的封装了缴获到的消息结构的参数?
这个消息结构是怎样的??
比如GetMessage钩子,lParam是Removal flag移除标志,wParam是个TMsg结构的指针?
?typedef struct tagMSG {???? // msg??
??? HWND?? hwnd;??
??? UINT?? message;?
??? WPARAM wParam;?
??? LPARAM lParam;?
??? DWORD? time;?
??? POINT? pt;?
} MSG;?
?SDK中这么定义TMsg结构的?
等下会有例子源代码给大家看的,不要急?
继续...
三、实现
? 下面我们来讲如何解决一、4.中提到的问题。?
?1.在自己的进程中访问其他进程的对象实例
? 有了上面介绍的必备知识基础,那么现在这个问题对我们来说就不是很困难了,利用钩
? 子由系统将DLL注入目标进程,这时DLL就在目标进程的地址空间中了,这样,DLL中的访问目标进程的对象实例的代码就可以工作了。?
?2.得到其他进程的DBGrid对象实例?
? DLL注入目标进程之后,实际上DLL和目标进程就在一个进程中了,那么按理说我们用
? FindControl函数应该就可以由DBGrid句柄得到DBGrid对象实例的了,但实际并非如此!
? 实际写代码测试一下我们可以发现它返回的是nil。
我们来看看FindControl的源代码(Controls.pas中):?
?{ Find a TWinControl given a window handle }
{ The global atom table is trashed when the user logs off.? The extra test
? below protects UI interactive services after the user logs off.
? Added additional tests to enure that Handle is at least within the same
? process since otherwise a bogus result can occur due to problems with
? GlobalFindAtom in Windows.? }
function FindControl(Handle: HWnd): TWinControl;
var
? OwningProcess: DWORD;
begin
? Result := nil;
? if (Handle <> 0) and (GetWindowThreadProcessID(Handle,OwningProcess) <> 0) and
???? (OwningProcess = GetCurrentProcessId) then // 判断调用进程ID是否为Handle所在进程
? begin
??? if GlobalFindAtom(PChar(ControlAtomString)) = ControlAtom then
????? Result := Pointer(GetProp(Handle,MakeIntAtom(ControlAtom)))
??? else
????? Result := ObjectFromHWnd(Handle);
? end;
end;?
安徽-小李(297099102) 14:23:55
function ObjectFromHWnd(Handle: HWnd): TWinControl;
? if (GetWindowThreadProcessID(Handle,sans-serif!important"> ???? (OwningProcess = GetCurrentProcessID) then
??? Result := Pointer(SendMessage(Handle,RM_GetObjectInstance,0))
? else
??? Result := nil;
再看看其中使用到的ControlAtomString,ControlAtom,RM_GetObjectInstance的值是怎
样的(InitControls中):?
不是这个问题,我们的DLL已经注入目标进程了?
这是钩子帮我们完成的工作?
?procedure InitControls;
? UserHandle: HMODULE;
? WindowAtomString := Format('Delphi%.8X',[GetCurrentProcessID]);
? WindowAtom := GlobalAddAtom(PChar(WindowAtomString));
? ControlAtomString := Format('ControlOfs%.8X%.8X',[HInstance,GetCurrentThreadID]);
? ControlAtom := GlobalAddAtom(PChar(ControlAtomString));
? RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));
? ...
不知道大家发现了没有?
看到这里,我们可以发现问题之所在了。ControlAtomString是根据模块句柄(模块加载基
地址)和线程ID动态生成的,目标进程的模块基地址就是EXE基地址,一般是0x00400000,
但DLL的模块加载基地址就不是这个了,默认是0x10000000,而实际上可能因为这个地址
已经被占用(有其他DLL被加载到这个地址)而进行重定位,所以初始化时添加的
ControlAtom和目标进程的ControlAtom的值就不一样,RM_GetObjectInstance也同样是不
一样的,那FindControl当然就不能找到DBGrid对象实例啦。?
?OK,清楚了这一点,解决起来就简单了,我们自己写个FindControl函数,以目标进程基
地址来动态生成ControlAtomString,添加ControlAtom就可以啦。
在DLL中取EXE的基地址,用GetModuleHandle(nil)即可。?
这个在DLL中做?
?var
? ControlAtom: TAtom;
? ControlAtomString: string;
? RM_GetObjectInstance: DWORD;? // registered window message
???? (OwningProcess = GetCurrentProcessId) then
????? Result := Pointer(SendMessage(Handle,0));
end;

initialization
finalization
? GlobalDeleteAtom(ControlAtom);
? ControlAtomString := '';
end.
嗯,上面的FindControl我是把Controls.pas中FindControl和ObjectFromHWND合成一个函数了?
这样就可以根据句柄获取到对象实例了!
? 根据句柄得到对象实例,还有另外一种方法,采用MakeObjectInstance的实现机制,
? 和InitWndProc的实现代码,根据句柄得到ObjectInstance指针,从而得到实例对象,
? 本文就不说了,有兴趣的网友请自行研究MakeObjectInstance、InitWndProc的代码。?
? 上面两个主要的问题解决了,大体上我们就解决了跨进程获取其他程序的DBGrid内容
? 的问题了!?
?3.取DBGrid的内容?
句柄好找,FindWindow,FindWindowEx,关键是在于由句柄=>对象实例?
是的,是“绝对句柄”?
?// 获取目标进程中DBGrid的数据集的记录内容,保存到文件中
procedure ProcessDataSet(hCtrl: HWND);
? F: TextFile;
? FileName: string;
? Grid: TDBGrid;
? DataSet: TDataSet;
? I: Integer;
? Grid := TDBGrid(FindControl(hCtrl)); // 根据句柄取得对象实例
? if (Grid <> nil) and (Grid.DataSource <> nil) and (Grid.DataSource.DataSet <> nil) then
??? FileName := ExtractFilePath(ParamStr(0)) + 'DataSet.txt'; // 目标程序运行目录下
??? AssignFile(F,FileName);
??? if FileExists(FileName) then Append(F) else Rewrite(F);
??? try
????? DataSet := Grid.DataSource.DataSet;

????? Writeln(F,FormatDateTime('yyyy-MM-dd HH:mm:ss',Now),':');
????? DataSet.First;
????? while not DataSet.Eof do
????? begin
??????? for I := 0 to DataSet.FieldCount - 1 do
????????? Write(F,DataSet.Fields[I].AsString,',');
??????? Writeln(F);
??????? DataSet.Next;
????? end;
????? Writeln(F);
??? finally
????? CloseFile(F);
??? end;
大家觉得这个代码有没有问题????
这个代码,我们在实际测试时可以发现还是存在问题的,运行会出错,问题就在于
Write(F,DataSet.Fields[I].DisplayText,');这一句,如果将这一句改成
?null??
是要取界面显示的内容?
呵呵,不是这个原因?
?value<>displaytext?
?TField.AsString的属性声明:
??? property AsString: string read GetAsString write SetAsString;
??? property DisplayText: string read GetDisplayText;
????
??? function GetDisplayText: string;
??? procedure GetText(var Text: string; DisplayText: Boolean); virtual;?
?DisplayText属性声明?
?function TField.GetDisplayText: string;
? Result := '';
? if Assigned(FOnGetText) then
??? FOnGetText(Self,Result,True) else
??? GetText(Result,True);
end;
procedure TField.GetText(var Text: string; DisplayText: Boolean);
? Text := GetAsString;
?TField类型的GetText的实现是直接调用GetAsString,我们看看特定类型的字段的
GetText函数的实现代码,如TIntegerField.GetText:
procedure TIntegerField.GetText(var Text: string; DisplayText: Boolean);
? L: Longint;
? FmtStr: string;
? if GetValue(L) then
??? if DisplayText or (FEditFormat = '') then
????? FmtStr := FDisplayFormat else
????? FmtStr := FEditFormat;
??? if FmtStr = '' then Str(L,Text) else Text := FormatFloat(FmtStr,L);
? end else
??? Text := '';
这个函数也并没有什么特别的代码,那到底是怎么导致这个错误的呢?这个就需要我们
再仔细地想想了,想当初这个问题困扰了好几天而不得解!还问过刘麻子大虾的,呵呵?
我们再来看看,用Field.GetText字符串类型会出错,而用Field.Value,Variant类型就
没有问题,那么应该是跟字符串类型有关了,再联想到这是在DLL中,传递字符串参数和
返回值时都要引用ShareMem作为工程的第一个单元,嗯,是不是跟字符串的自动内存管
理有关系?EXE和DLL是各自有自己的一套VCL类体系的,我们FindControl获取到的DBGrid实际上是EXE中创建的对象,而Field.GetText是个虚拟方法,它在VMT中占据一项,实际的函数地址存放在VMT中,尽管我们是在DLL中调用DisplayText属性和GetText函数的,但由于DBGrid对象实例是EXE中的,它的VMT中的实际的GetText函数是EXE中的,也就是说它是通过EXE的内存管理器,给返回字符串分配内存的,而我们的可爱的
那么我们也把ShareMem作为工程的第一个引用单元,编译生成DLL,测试,问题是否搞定了呢?没有!嘿嘿,尽管DLL我们uses ShareMem了,但目标进程并没有,而一般说来我们要用这种方法去跨进程获取其他程序的DBGrid内容,原因就在于我们没有目标程序的
源代码,也不能打开目标数据库或目标数据库的内容被加密了,那么我们当然是没法改
目标进程让它也uses ShareMem的,那就只有另谋出路的啦。解决问题的方法也是很简单,
字符串通过引用计数来进行内存管理的,当引用计数=1而这个字符串不再被使用时,引
用计数-1=0,这时就会释放字符串的内存的。嘿嘿,你不是在引用计数=0时才释放字符串
内存么,我就偏不让你释放,我用代码手工修改字符串的引用计数,让它比1大,那么
Delphi自然就会认为还有其他地方要使用这个字符串,就不会多此一举,滥做好人,帮
我们释放字符串的内容了。?
但是这是解决了出错的问题,却又衍生一个新的问题:内存泄漏,不过这个倒问题不大,
反正进程结束的时候,系统总是会释放这个进程所占的所有内存资源的,没有好的解决
办法,那目前我们也就只能这么着了,哈哈!?
? S: string;
??????? begin
????????? S := DataSet.Fields[I].AsString;
????????? if S <> '' then
??????????? Inc(PInteger(PChar(S)-8)^); // 增加字符串引用计数,避免Delphi自
??????????????????????????????????????? // 动管理字符串内容的释放
??????? end;
完整的源代码:
http://lichengbin.iii-grp.com/Source/DBGrid.rar

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读