Передача записи, содержащей метод, между ведущим приложением и DLL

Возможно ли (без использования пакетов времени выполнения или DLL с общей памятью) передать тип записи между основным приложением и модулем DLL, где тип записи содержит функции / процедуры (Delphi 2006 и более поздние версии)?

Предположим для простоты, что наш тип записи не содержит каких-либо полей String (поскольку для этого, конечно, требуется библиотека Sharemem DLL), и вот пример:

TMyRecord = record
  Field1: Integer;
  Field2: Double;
  function DoSomething(AValue1: Integer; AValue2: Double): Boolean;
end;

Итак, чтобы сформулировать это просто: могу ли я передать «экземпляр» TMyRecord между хост-приложением и DLL (в любом направлении) без использования пакетов времени выполнения или DLL с общей памятью и выполнить функцию DoSomething как из Host EXE а DLL?


person LaKraven    schedule 08.12.2011    source источник
comment
Я подозреваю, что вы хотите передать в DLL и данные, и код. Правильный способ сделать это - использовать интерфейс. Ваш принятый ответ не передает код, только данные.   -  person David Heffernan    schedule 08.12.2011
comment
Дэвид: И DLL, и EXE имеют общий блок, содержащий типы записей ... в тестовом примере, выполненном Дорином, запись, переданная из исполняемого файла хоста в DLL, позволяет DLL выполнять метод DoSomething, поэтому его решение идеально подходит для моих нужд. Обычно я бы согласился с тем, что лучше всего использовать интерфейсы, но поскольку это относится конкретно к интеллектуальным типам, над которыми я работаю, мне нужно иметь возможность передавать саму запись между хостом и DLL (теперь я могу). Спасибо :)   -  person LaKraven    schedule 08.12.2011
comment
Просто чтобы вы знали, что выполняемый метод находится в DLL, даже если запись создается в EXE.   -  person David Heffernan    schedule 08.12.2011
comment
Да ... это хорошо, но в данном случае это желаемое поведение :)   -  person LaKraven    schedule 08.12.2011


Ответы (2)


Если я правильно понял ваш вопрос, то вы можете это сделать, вот один из способов:

testdll.dll

library TestDll;

uses
  SysUtils,
  Classes,
  uCommon in 'uCommon.pas';

{$R *.res}

procedure TakeMyFancyRecord(AMyFancyRecord: PMyFancyRecord); stdcall;
begin
  AMyFancyRecord^.DoSomething;
end;

exports
  TakeMyFancyRecord name 'TakeMyFancyRecord';

begin
end.

uCommon.pas ‹- используется как приложением, так и dll, модулем, в котором определяется ваша необычная запись

unit uCommon;

interface

type
  PMyFancyRecord = ^TMyFancyRecord;
  TMyFancyRecord = record
    Field1: Integer;
    Field2: Double;
    procedure DoSomething;
  end;

implementation

uses
  Dialogs;

{ TMyFancyRecord }

procedure TMyFancyRecord.DoSomething;
begin
  ShowMessageFmt( 'Field1: %d'#$D#$A'Field2: %f', [ Field1, Field2 ] );
end;

end.

и, наконец, тестовое приложение, файл -> новое -> приложение форм vcl, перетащите кнопку в форму, включите uCommon.pas в предложение uses, добавьте ссылку на внешний метод

procedure TakeMyFancyRecord(AMyFancyRecord: PMyFancyRecord); stdcall;
  external 'testdll.dll' name 'TakeMyFancyRecord';

и в событии нажатия кнопки добавьте

procedure TForm1.Button1Click(Sender: TObject);
var
  LMyFancyRecord: TMyFancyRecord;
begin
  LMyFancyRecord.Field1 := 2012;
  LMyFancyRecord.Field2 := Pi;
  TakeMyFancyRecord( @LMyFancyRecord );
end;

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ:

  • работает в D2010;
  • компилируется на моей машине!

Наслаждайтесь!


Дэвид Хеффернан править

Чтобы быть на 100% ясным, выполняемый метод DoSomething - это метод, определенный в DLL. Метод DoSomething, определенный в EXE, никогда не выполняется в этом коде.


person Community    schedule 08.12.2011
comment
Спасибо за тестирование :) +1 и отметка как правильный ответ! - person LaKraven; 08.12.2011
comment
Использование указателя на запись не влияет на поведение. Вы также можете использовать параметр простого значения, параметр const и т. Д. - person David Heffernan; 08.12.2011
comment
@DavidHeffernan: да, спасибо, но есть несколько способов снять шкуру с кошки! (: - person ; 08.12.2011
comment
Я хочу сказать, что ваш ответ подразумевает, что в использовании указателя на запись есть что-то особенное. Нет. - person David Heffernan; 08.12.2011
comment
ну да, не знал до 5 минут спустя, что вы также можете использовать параметр const, и компилятор творит чудеса. - person ; 08.12.2011
comment
Но что за магия? Неважно, как вы передаете параметр с точки зрения передаваемой информации. Ваш ответ вводит в заблуждение. - person David Heffernan; 08.12.2011

Я бы не стал этого предлагать, независимо от того, работает это или нет. Если вам нужно, чтобы DLL работала с экземплярами TMyRecord, самый безопасный вариант - использовать вместо этого простые функции экспорта DLL, например:

DLL:

type
  TMyRecord = record 
    Field1: Integer; 
    Field2: Double; 
  end; 

function DoSomething(var ARec: TMyRecord; AValue1: Integer; AValue2: Double): Boolean; stdcall;
begin
  ...
end;

exports
  DoSomething;

end.

Приложение:

type 
  TMyRecord = record  
    Field1: Integer;  
    Field2: Double;  
  end;  

function DoSomething(var ARec: TMyRecord; AValue1: Integer; AValue2: Double): Boolean; stdcall; external 'My.dll';

procedure DoSomethingInDll;
var
  Rec: TMyRecord;
  //...
begin 
  //...
  if DoSomething(Rec, 123, 123.45) then
  begin
    //...
  end else
  begin
    //...
  end;
  //...
end; 
person Remy Lebeau    schedule 08.12.2011
comment
К сожалению, в данном случае это невозможно, поскольку запись ДОЛЖНА содержать как методы, так и операторы класса, чтобы функционировать должным образом. Помимо этого, обычно это то, что я предлагаю (и делаю сам). - person LaKraven; 08.12.2011
comment
Так что я поставил +1 для здравого смысла :) - person LaKraven; 08.12.2011
comment
+1 Я также не предлагаю этого, потому что в будущем мы не узнаем, насколько хорошо он будет поддерживаться, но вопрос в том, возможно ли это ... Кроме того, я не знаю, как обрабатываются методы записи, но я предполагаю, что это просто магия компилятора, то есть, когда вы вызываете метод записи, он просто вызывает метод, в котором он передает запись в качестве первого / последнего аргумента, а остальные - аргументы метода ... Я могу ошибаться, конечно - person ; 08.12.2011
comment
Другой недостаток - это требует, чтобы один и тот же код метода находился как в DLL, так и в EXE. Вы не можете реализовать метод в EXE и заставить DLL вызывать его напрямую, и наоборот. Для этого вам понадобится VMT, в записях которого нет AFAIK. Если код когда-либо изменится, необходимо перекомпилировать и EXE, и DLL, чтобы синхронизация оставалась неизменной. Вы можете передать вторую запись, содержащую указатели на функции, которым вы при необходимости передаете основную запись. Это позволит вам централизовать код, чтобы только EXE или DLL могли напрямую обращаться к методам и действовать как прокси для другого модуля. - person Remy Lebeau; 08.12.2011
comment
запись ДОЛЖНА содержать как методы, так и операторы класса, чтобы функционировать должным образом. Обратите внимание, что принятый вами ответ не соответствует этому критерию. Когда вы отправляете запись в другой модуль, вы отправляете данные, но не код. - person David Heffernan; 08.12.2011
comment
Никогда не думал об этом раньше, но как на это влияют настройки выравнивания компилятора при передаче записей между exe и dll? В данном примере это не будет проблемой (конечно, не на 32-битной платформе), но что делать, если после двойного числа стоит символ [1] или что-то подобное, а за ним следует другое целое число? У вас возникнут проблемы, если exe и dll скомпилированы с разными настройками выравнивания? Целесообразно ли использовать упаковку, чтобы избежать этого? - person Marjan Venema; 08.12.2011
comment
@marjan Да, выравнивание должно совпадать. Упаковка - это один из вариантов, но не обязательно лучший. Вам просто нужно убедиться, что выравнивание совпадает, выполните это с помощью $ ALIGN - person David Heffernan; 08.12.2011
comment
@DavidHeffernan Обратите внимание, что принятый вами ответ не соответствует этому критерию. да и нет, насколько я понимаю (может ошибаться, конечно), dll и exe написаны на Delphi, и в моем комментарии я указываю использовать общий блок, в котором определен тип, поэтому, если вы можете передать это из приложения в dll, я почти уверен, что вы можете сделать обратное. Конечно, если у вас есть лучший подход, я хотел бы увидеть ваш ответ и извлечь из него уроки, как я сделал из ваших предыдущих ответов на разные вопросы. - person ; 08.12.2011
comment
@dorin Если dll и exe обновляются синхронно, это не имеет значения. И непонятно, в чем именно состоит требование. Вот почему я добавил пояснение к вашему ответу. Лично я бы использовал интерфейс. - person David Heffernan; 08.12.2011
comment
@DorinDuminica: предполагается, что и EXE, и DLL скомпилированы для постоянного использования одной и той же версии общего модуля. Если вы начнете смешивать разные версии, у вас могут возникнуть проблемы. Дэвис прав в том, что, передавая запись, вы передаете только ее данные, а не ее код. Код является локальным для каждого модуля, поэтому поведение во время выполнения зависит от того, какой код скомпилирован в каждый модуль. - person Remy Lebeau; 08.12.2011