Запись данных в Delphi TStringGrid из внешнего приложения

У меня есть устаревшее приложение, написанное на Delphi, и мне нужно создать механизм для

  1. чтение и
  2. письмо

данные из/в TStringGrid.

У меня нет исходного кода приложения, нет интерфейса автоматизации и маловероятно, что производитель его предоставит.

Поэтому я создал

  1. DLL C++, которая внедряет
  2. DLL Delphi (написанная мной) в
  3. адресное пространство устаревшего приложения.

DLL 2 получает доступ к экземпляру 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 без проблем с нарушением прав доступа?


person Mentiflectax    schedule 17.09.2012    source источник
comment
Удалось ли вам синхронизировать деревья объектов и отображение памяти? в DLL и EXE есть два разных класса TObject, две разные функции GetMem.   -  person Arioch 'The    schedule 17.09.2012
comment
Это вряд ли сработает. У вас есть два отдельных экземпляра VCL. Один в целевом приложении и один в вашей DLL. Это на один VCL слишком много. Вы можете заставить его немного работать, если вам случится скомпилировать свою DLL с той же версией VCL (то есть с той же версией Delphi), что и целевое приложение. Я верю, что вы убедились, что сделали это. Вы? Но даже тогда я ожидаю, что ваш подход потерпит неудачу в какой-то момент.   -  person David Heffernan    schedule 17.09.2012
comment
Я создал простое приложение Delphi, которое содержит только сетку. Я провожу свои эксперименты с этим простым приложением. И это приложение, и библиотека DLL Delphi скомпилированы с использованием одной и той же версии Delphi (2009 г.).   -  person Mentiflectax    schedule 17.09.2012
comment
Некоторые люди утверждают, что можно установить значения ячеек с помощью метода setCell. Чтобы получить к нему доступ, надо что-то делать с VMT.   -  person Mentiflectax    schedule 17.09.2012
comment
Вам будет достаточно вызывать виртуальные методы, поскольку они используют VMT. Это статические методы, которые доставляют вам проблемы, потому что они в конечном итоге выполняются в неправильном VCL. Если свойство Cells не работает, почему вызов SetCells должен быть другим? Предположительно, вы можете прочитать SetCells с помощником класса, чтобы взломать частную видимость. VMT не поможет, потому что SetCells не виртуальный.   -  person David Heffernan    schedule 17.09.2012
comment
До сих пор мне известны варианты записи в TStringGrid, которые стоит изучить: 1) Использование помощников класса 2) Получить координаты целевой ячейки, затем программно дважды щелкнуть ячейку, программно ввести текст и нажать Enter. 3) Отправьте сообщение Win32 (WM_SETTEXT?), которое должно изменить текст в ячейке. Что-нибудь еще?   -  person Mentiflectax    schedule 17.09.2012
comment
@David Где я могу найти подпись (список параметров и возвращаемое значение) метода SetCells?   -  person Mentiflectax    schedule 17.09.2012
comment
@David, но есть функция GetEditText(ACol, ARow: Longint): string; переопределить; процедура SetEditText(ACol, ARow: Longint; const Value: string); переопределить;   -  person Arioch 'The    schedule 17.09.2012
comment
@Dmitry в исходниках VCL, а именно в модуле VCL.Grids. docwiki.embarcadero.com/Libraries/en/Vcl.Grids.TStringGrid   -  person Arioch 'The    schedule 17.09.2012
comment
Использование помощников классов - ваша проблема в том, что EXE и DLL имеют разные классы TObject (и TStringGrid). Расширение класса DLL.tstringGird устранит проблемы в классе EXE.TStringGrid. Как? /// Отправляем некоторое Win32-сообщение (WM_SETTEXT?), которое должно модифицировать текст в ячейке. Нет ячейки на уровне Windows. TDrawGrid просто рисует все эти строки и буквы на холсте Windows.   -  person Arioch 'The    schedule 17.09.2012
comment
Думаю, если бы я был на вашем месте, я бы, вероятно, не стал заморачиваться с инъекцией и фальшивым вводом с клавиатуры. Я бы использовал AutoHotKey, по крайней мере, чтобы оценить осуществимость.   -  person David Heffernan    schedule 17.09.2012
comment
Интересно, может ли он взять в руки EXE RTTI (Jedi CodeLib должен был управлять им иногда при интеграции VMT), а затем использовать RTTI для установки TMethod для установщика свойств .Cells, а затем вызвать его.   -  person Arioch 'The    schedule 17.09.2012
comment
Могу ли я еще раз спросить о менеджерах памяти кучи? будет вести себя realGrid.Cells[1,1] := 'Test'; иначе, чем var s: string; с:= 'Тест'; realGrid.Cells[1,1] := s; с:= с + '1'; с:= '';   -  person Arioch 'The    schedule 17.09.2012
comment
@Arioch'The я подозреваю, что вы попали в точку. Присвоение Cells[1,1] удалит предыдущее значение. Но освободите его в неправильной куче.   -  person David Heffernan    schedule 17.09.2012
comment
@Дэйвид. Надеюсь, что так. Но не уверен. Не знаю, как компилятор работает с литеральными константами. Делает ли он внутренне временную 0-ссылку StringRec ? Или, возможно, использование внешней DLL FastMM также было бы достаточным, если бы это была единственная проблема, если бы он мог заставить хост-exe попасть в нее? Мы надеемся, что использование SetEditText через VMT облегчит еще один.   -  person Arioch 'The    schedule 17.09.2012
comment
@Arioch'The Это строка ссылки -1. Но помните, что в ячейке уже есть содержимое. И их нужно будет освободить, чтобы освободить место для нового содержимого. И старое содержимое было размещено в куче приложения. Но этот код будет освобожден в куче DLL. БУМ!   -  person David Heffernan    schedule 17.09.2012
comment
@Arioch'The Если используется точно такая же версия Delphi, то вызовы статических методов будут работать нормально. Настоящая проблема — это менеджеры кучи. Так что ShareMem сделает свою работу. Наверное!   -  person David Heffernan    schedule 17.09.2012
comment
Grid не имеет двумерного массива строк, как можно было бы ожидать. Здесь замешано черное вуду. экземпляр TStringSparseList для каждой строки создается/возвращается (TStringGrid.EnsureDataRow), а затем приводится к TStringGridStrings в TStringGrid.SetCells, но из-за VMT TStringSparseList.Put вызывается... очень странно. Жаль, что я никогда не вникал в эти кровавые подробности.   -  person Arioch 'The    schedule 17.09.2012
comment
@David - точно такой же Delphi, точно такие же параметры компилятора, точно такие же части кода, которые были отброшены SmartLinker ... Что ж, если бы у топикстартера был доступ к исходным источникам, он бы просто внес изменения (добавил IPC) и перекомпилировал. Без исходников и возможности пересборки полагаться на одну и ту же среду — значит обманывать себя.   -  person Arioch 'The    schedule 17.09.2012
comment
@Arioch'Я знаю, я знаю. Я бы использовал AHK и покончил с этим.   -  person David Heffernan    schedule 17.09.2012
comment
@David, поэтому, чтобы попробовать полагаться на VMT, следует использовать TStringGrid.SetEditText(ACol, ARow: Longint; const Value: string), а строки должны быть уверены, что они являются буквальными константами или постоянными переменными, явно очищенными вызывающей стороной. Вроде должно хватить...   -  person Arioch 'The    schedule 17.09.2012
comment
Забавно, на русском форуме (RSDN.ru) топикстартеру предложили заглянуть в ObjectFromHWnd (Controls.pas)   -  person Arioch 'The    schedule 17.09.2012
comment
@Arioch Да, я действительно получил этот совет, но понятия не имею, как он поможет мне прочитать данные из сетки.   -  person Mentiflectax    schedule 18.09.2012


Ответы (2)


Такой подход просто не сработает. У вас есть два экземпляра VCL в целевом исполняемом файле. Один принадлежит целевому приложению, а другой — DLL. Это слишком много для одного экземпляра VCL. Это может сойти с рук, если для создания как целевого приложения, так и вашей DLL используется одна и та же версия Delphi.

Однако у вас по-прежнему будет два менеджера кучи. И ваш код передает память, выделенную кучей, между вашими различными экземплярами VCL. Вы будете выделять в одной куче и освобождать в другой. Это не работает и приведет к нарушениям прав доступа.

Вы передаете строку, выделенную в куче DLL, объекту сетки строк, который использует кучу целевого приложения. Это просто не может работать.

Я думаю, что нарушение прав доступа произойдет в тот момент, когда код DLL попытается освободить предыдущее значение Cells[i,j], которое было выделено диспетчером кучи целевого приложения.

В основном то, что вы пытаетесь, не сработает. Вы можете узнать адрес реализации целевого приложения TStringGrid.SetCell и подделать вызов к нему. Но вам также необходимо найти реализацию GetMem, FreeMem и т. д. в целевом приложении и убедиться, что вся динамическая память, перешедшая из вашей DLL в целевое приложение, была выделена и освобождена кучей целевого приложения. У вас будет дьявольская работа, чтобы сделать эту работу. Конечно, если и целевое приложение, и DLL используют диспетчер общей памяти, то вы можете легко реализовать этот подход.

Гораздо проще было бы подделать ввод с клавиатуры. Лично я бы оценил возможность этого с помощью AutoHotKey.

person David Heffernan    schedule 17.09.2012

Все, что связано с использованием кучи, подвергается очень большому риску. Вы можете попробовать 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;

Но это может вызвать TStringGrid.OnSetEditText, если хост-EXE использует его.

person Arioch 'The    schedule 17.09.2012
comment
Как можно объединить две кучи? Мне трудно поверить, что это возможно. - person David Heffernan; 17.09.2012
comment
@DavidHefferman технически не объединяется. Переключите и DLL, и EXE на один и тот же менеджер, и сделайте это достаточно рано, чтобы память, выделенная до переключения, не освобождалась во время выполнения. Наличие общей кучи было бы более правильным утверждением. - person Arioch 'The; 17.09.2012
comment
Это на самом деле правдоподобно. Внедренная DLL должна была бы установить новый диспетчер памяти в первую очередь, но вполне возможно сделать это без использования кучи. Хотя было бы довольно хакерским. - person David Heffernan; 17.09.2012
comment
@Arioch «Я реализовал запись в ячейки сетки, а) прочитав координаты ячейки и б) щелкнув ячейку и набрав текст с помощью Microsoft UI Automation. Теперь хочу улучшить производительность всего этого дела, особенно п.б). MS UI Automation набирает текст буква за буквой, что медленнее, чем хочет мой клиент. Где я могу узнать больше о менеджерах памяти и одиночной куче для EXE и DLL? - person Mentiflectax; 25.09.2012
comment
может ли MS UI Automation вместо этого использовать копирование/вставку? Вы можете использовать автоматизацию пользовательского интерфейса только для выбора продажи, затем используйте Win32 API PostMessage, чтобы нажать F2 (войдите в режим редактирования ячейки), затем вставьте буквы, затем нажмите Enter? Может так будет быстрее? - person Arioch 'The; 28.09.2012
comment
насчет одиночного HeapMM - у меня большие сомнения... Поскольку у вас нет контроля над EXE и он, вероятно, не использует BPL, вы, вероятно, не сможете его переключить. Если он использует FastMM, то, возможно (только возможно), размещение полноценной FastMM4 DLL рядом с exe приведет к его загрузке - но только возможно. Вы можете попытаться найти, если у вас точно такие же RTL, компилятор и настройки, тогда вы можете попытаться найти в EXE docwiki.embarcadero.com/Libraries/en/System.GetMemoryManager, вызовите его и установите этот менеджер в DLL очень рано при инициализации DLL. - person Arioch 'The; 28.09.2012