我有一个用Delphi编写的遗留应用程序,需要为构建一个机制
- 阅读和
- 书写
TStringGrid中的数据。
我没有应用程序的源代码,也没有自动化接口,供应商也不太可能提供。
因此我创建了
- 一个C++DLL,它注入
- Delphi DLL(由我编写)
- 遗留应用程序的地址空间
DLL2访问遗留应用程序内的TStringGrid实例,读取单元格值并将其写入调试日志。
阅读效果很好。但是,当我尝试使用之类的调用将数据写入网格单元时
realGrid.Cells[1,1] := 'Test';
发生访问违规。
这是代码:
procedure DllMain(reason: integer) ;
type
PForm = ^TForm;
PClass = ^TClass;
PStringGrid = ^TStringGrid;
var
[...]
begin
if reason = DLL_PROCESS_ATTACH then
begin
handle := FindWindow('TForm1', 'FORMSSSSS');
formPtr := PForm(GetVCLObjectAddr(handle) + 4);
if (not Assigned(formPtr)) then
begin
OutputDebugString(PChar('Not assigned'));
Exit;
end;
form := formPtr^;
// Find the grid component and assign it to variable realGrid
[...]
// Iterate over all cells of the grid and write their values into the debug log
for I := 0 to realGrid.RowCount - 1 do
begin
for J := 0 to realGrid.ColCount - 1 do
begin
OutputDebugString(PChar('Grid[' + IntToStr(I) + '][' + IntToStr(J) + ']=' + realGrid.Cells[J,I]));
// This works fine
end;
end;
// Now we'll try to write data into the grid
realGrid.Cells[1,1] := 'Test'; // Crash - access violation
end;
end; (*DllMain*)
如何在不出现访问冲突问题的情况下将数据写入TStringGrid?
这种方法根本不起作用。目标可执行文件中有两个VCL实例。一个由目标应用程序拥有,一个由DLL拥有。这是一个太多的VCL实例。如果使用完全相同版本的Delphi来构建目标应用程序和DLL,您可能可以逃脱惩罚。
但是,您仍将有两个堆管理器在运行。您的代码在不同的VCL实例之间传递堆分配的内存。您将在一个堆中进行分配,在另一个堆进行释放。这不起作用,会导致访问违规。
您正在将DLL堆中分配的字符串传递给使用目标应用程序堆的字符串网格对象。这是行不通的。
我认为访问冲突将发生在DLL代码试图释放Cells[i,j]
的前一个值时,该值是由目标应用程序的堆管理器分配的。
基本上,你所尝试的是行不通的。您可以找到目标应用程序实现TStringGrid.SetCell
的地址,并伪造对该地址的调用。但是,您还需要找到目标应用程序的GetMem
、FreeMem
等实现,并确保从DLL到目标应用程序之间的所有动态内存都已由目标应用程序堆分配和释放。做这件事你会有一份艰巨的工作。当然,如果目标应用程序和DLL都使用了共享内存管理器,那么您可能就可以实现这种方法了。
更简单的做法是伪造键盘输入。我个人会考虑使用AutoHotKey的可行性。
与堆使用相关的一切都面临着非常大的风险。您可以尝试使用Jedi CodeLib来合并对象树,并确保EXE和DLL中的单个堆相同,但这将是一个非常脆弱的解决方案。
希望VMT调用或多或少是安全的,并偏执地试图阻止编译器释放字符串,草图如下:
type TSGCracker = class(Grids.TStringGrid); // access to protected functions.
....
var s: string;
function dummy(s: string); // not-inline! pretend we need and use the value!
begin Result := s + '2'; end;
begin
...
s := 'Test';
TSGCracker(realGrid).SetEditText(1,1, s);
dummy( s+'1');
...
end;
但如果主机EXE使用它,它可能会调用TStringGrid.OSetEditText。