4

I have a legacy application written in Delphi, and need to build a mechanism for

  1. reading and
  2. writing

data from/to a TStringGrid.

I don't have the source code of the application, there is no automation interface and it is very unlikely that the vendor will provide one.

Therefore I've created

  1. a C++ DLL, which injects
  2. a Delphi DLL (written by me) into
  3. the address space of the legacy application.

DLL 2 gets access to the TStringGrid instance inside the legacy application, reads cell values and writes them into the debug log.

Reading works fine. But, when I try to write data into a grid cell using a call like

realGrid.Cells[1,1] := 'Test';

an access violation occurs.

Here's the code:

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*)

How can I write data into a TStringGrid without getting access violation problem?

Glory to Russia
  • 17,289
  • 56
  • 182
  • 325
  • Did you manage to sync object trees and memory mapping ? in DLL and EXE there are two different TObject classes, two different GetMem functions. – Arioch 'The Sep 17 '12 at 08:05
  • This is not likely to work. You've got two separate instance of the VCL. One in the target app, and one in your DLL. That's one VCL too many. You might get it sort of working, a bit, if you happen to compile your DLL with the exact same version of VCL (i.e. same Delphi version) as the target app. I trust you made sure that you did that. Did you? But even then I would expect your approach to fail at some point. – David Heffernan Sep 17 '12 at 08:05
  • I've created a simple Delphi application, which contains only the grid. I'm running my experiments on this simple application. Both this application and the Delphi DLL are compiled with exactly the same version of Delphi (2009). – Glory to Russia Sep 17 '12 at 09:15
  • Some people argue that it IS possible to set cell values using method setCell. To get access to it, it is necessary to do something with VMT. – Glory to Russia Sep 17 '12 at 09:16
  • 1
    You'll be fine calling virtual methods since they use the VMT. It's static methods that give you problems because they end up executing in the wrong VCL. If the `Cells` property fails, why would calling `SetCells` be different? You can presumably read `SetCells` with class helper to crack the private visibility. VMT won't help because `SetCells` is not virtual. – David Heffernan Sep 17 '12 at 09:27
  • Up to now I'm aware of options for writing into TStringGrid, which are worth investigating: 1) Using class helpers 2) Obtain coordinates of the target cell, then programmatically double click the cell, programmatically type in the text and press Enter. 3) Send some Win32 message (WM_SETTEXT?), which should modify the text in the cell. Anything else? – Glory to Russia Sep 17 '12 at 09:38
  • @David Where can I find the signature (parameter list and return value) of the SetCells method? – Glory to Russia Sep 17 '12 at 09:40
  • 1
    @David yet there are function GetEditText(ACol, ARow: Longint): string; override; procedure SetEditText(ACol, ARow: Longint; const Value: string); override; – Arioch 'The Sep 17 '12 at 09:41
  • 1
    @Dmitry in VCL sources, namely in VCL.Grids unit. http://docwiki.embarcadero.com/Libraries/en/Vcl.Grids.TStringGrid – Arioch 'The Sep 17 '12 at 09:42
  • "Using class helpers" - your problem is that EXE and DLL have different TObject (and TStringGrid) classes. Extending DLL.tstringGird class would fix problems in EXE.TStringGrid class how ? /// "Send some Win32 message (WM_SETTEXT?), which should modify the text in the cell" There is no Cell on Windows level. TDrawGrid just pictures all those lines and letters on Windows Canvas – Arioch 'The Sep 17 '12 at 09:46
  • I think if I were you I'd probably not bother with injection and fake keyboard input. I'd use AutoHotKey, at least to scope out the feasibility. – David Heffernan Sep 17 '12 at 09:46
  • I wonder if he can put hands on EXE RTTI ( Jedi CodeLib should have managed it sometimes when integratign VMTs) then using RTTI to settle TMethod for .Cells property setter, then invoke it. – Arioch 'The Sep 17 '12 at 09:48
  • May i ask again about Heap memory managers ? would *realGrid.Cells[1,1] := 'Test';* behave distinctly from *var s: string; s:= 'Test'; realGrid.Cells[1,1] := s; s:= s +'1'; s:= '';* – Arioch 'The Sep 17 '12 at 09:50
  • @Arioch'The I suspect you have hit the nail on the head. Assigning to `Cells[1,1]` will deallocate the previous value. But deallocate it on the wrong heap. – David Heffernan Sep 17 '12 at 09:52
  • @David. Hope so. But ain't sure. Dunno how compiler works with literal constants. Does it internally make temporary 0-reference StringRec ? Or perhaps using external FastMM DLL would also suffice if that was the only problem, if he can force host exe to fall into it ? Using SetEditText via VMT would hopefully ease another one. – Arioch 'The Sep 17 '12 at 10:49
  • @Arioch'The It's a -1 ref string. But remember that the cell already has contents. And they will need to be deallocated to make room for the new contents. And the old contents were allocated on the app's heap. But this code will deallocate on the DLL's heap. KABOOM! – David Heffernan Sep 17 '12 at 10:51
  • @Arioch'The If exact same Delphi version used, then static method calls will work fine. The real issue is heap managers. So ShareMem would get the job done. Probably! – David Heffernan Sep 17 '12 at 10:52
  • 1
    Grid does not have 2D array of strings, as one might expect. There is a black voodoo involved. per-row instance of TStringSparseList is created/returned (TStringGrid.EnsureDataRow) then casted to TStringGridStrings in TStringGrid.SetCells, but due to VMT TStringSparseList.Put gets called... very weird. Wish i never looked into those gory details. – Arioch 'The Sep 17 '12 at 11:03
  • @David - exact same Delphi, exact same compiler options, exact same parts of code been discarded by SmartLinker... Well, if topicstarter had access to original sources - he'd just make changes (add IPC) and recompile. Without the sources andability to rebuild, to rely on "same environment" is just to fool yourself. – Arioch 'The Sep 17 '12 at 11:05
  • @Arioch'The I know, I know. I'd use AHK and be done with it. – David Heffernan Sep 17 '12 at 11:09
  • @David so to tryo rely on VMT, TStringGrid.SetEditText(ACol, ARow: Longint; const Value: string) should be used and strings to be assured to be literal constants or persistent variables explicitly cleaned by caller. Looks like that should be enough... – Arioch 'The Sep 17 '12 at 11:15
  • Funny, on russian forum (RSDN.ru) topicstarter was suggested to look into ObjectFromHWnd (Controls.pas) – Arioch 'The Sep 17 '12 at 11:18
  • @Arioch Yes, I actually got this advice, but have no idea, how it would help me to read the data from the grid. – Glory to Russia Sep 18 '12 at 08:14

2 Answers2

1

This approach simply is not going to work. You have two VCL instances in the target executable. One owned by the target app and one owned by the DLL. That's one VCL instance too many. You might be able to get away with that if the exact same version of Delphi is used to build both the target app and your DLL.

However, you will still have two heap managers in play. And your code passes heap allocated memory between your different VCL instances. You will be allocating in one heap and deallocating in the other. That doesn't work and will result in access violations.

You are passing a string allocated in the DLL's heap to the string grid object that uses the target app's heap. That just cannot work.

I think the access violation will occur at the point at which the DLL code attempts to deallocate the previous value of Cells[i,j], that was allocated by the target app's heap manager.

Basically what you are attempting is not going to work. You could find out the address of the target app's implementation of TStringGrid.SetCell and fake a call to that. But you'd also need to find the target app's implementation of GetMem, FreeMem etc. and make sure that all dynamic memory that crossed from your DLL to the target app was allocated and deallocated by the target app's heap. You are going to have a devil of a job making this work. Of course, if both target app and the DLL used the shared memory manager, then you might just be able to make this approach fly.

Much simpler would be to fake keyboard input. I personally would scope out the feasibility of that using AutoHotKey.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
1

Everything related to Heap usage is under very heavy risk there. You can try Jedi CodeLib to merge object trees and ensure same single heap in EXE and DLL, but that would be utterly fragile solution.

Hoping the VMT calls are more or less safe and paranoically trying to prevent compiler from freeing string, the sketch to be like that:

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;

But that might call TStringGrid.OnSetEditText, if host EXE uses it.

Arioch 'The
  • 15,799
  • 35
  • 62
  • How can you merge two heaps? I find it hard to believe that is possible. – David Heffernan Sep 17 '12 at 11:30
  • 1
    @DavidHefferman not "merge" technically. Switch both DLL and EXE to the same manager, and do it early enough so no memory allocated before switch wold be deallocated during run. "Having common heap" would be more correct statement. – Arioch 'The Sep 17 '12 at 11:44
  • 1
    That's actually plausible. The injected DLL would have to install the new memory manager as the first thing it did, but it's quite feasible to do that without using the heap. Would be rather hacky though. – David Heffernan Sep 17 '12 at 11:47
  • @Arioch 'The I've implemented writing to grid cells by a) reading the coordinates of the cell and b) clicking to the cell and typing the text using Microsoft UI Automation. Now I want to improve the performance of the whole thing, especially item b). MS UI Automation types the text letter by letter, which is slower than my customer wants. Where can I read more about memory managers and "single heap" for EXE and DLL? – Glory to Russia Sep 25 '12 at 07:33
  • can MS UI Automation use copy/paste instead ? can you use UI automation only to select the sell, then use Win32 API PostMessage to press F2 (enter cell edit mode), then paste letters, then press Enter ? Maybe that would be faster ? – Arioch 'The Sep 28 '12 at 14:21
  • about single HeapMM - i have heavy doubts... Since you have no control about EXE and it probably does not use BPLs, you probably can not switch it. If it uses FastMM then maybe (only maybe) putting full-blown FastMM4 DLL near exe would cause it loaded - but only maybe. You may try to find if u have exactly same RTL and compiler and settings, then u can try to find in EXE http://docwiki.embarcadero.com/Libraries/en/System.GetMemoryManager and call it and Set that manager in DLL very early in DLL init. – Arioch 'The Sep 28 '12 at 14:34