5

Is it possible (without the use of Runtime Packages or the Shared Memory DLL) to pass a Record type between the host application and a DLL module where the Record type contains Functions/Procedures (Delphi 2006 and above)?

Let's presume for the sake of simplicty that our Record type doesn't contain any String fields (as this of course requires the Sharemem DLL), and here's an example:

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

So, to state this simply: can I pass an "instance" of TMyRecord between the host application and a DLL (in either direction), without the use of Runtime Packages or Shared Memory DLL, and execute the DoSomething function from both the Host EXE and the DLL?

LaKraven
  • 5,804
  • 2
  • 23
  • 49
  • 2
    I suspect that you want to pass both data and code to the DLL. The correct way to do this is with an interface. Your accepted answer does not pass code, only data. – David Heffernan Dec 08 '11 at 07:23
  • David: Both the DLL and EXE share a common unit containing the Record types.... in the test-case done by Dorin, the Record passed from the host executable to the DLL allows the DLL to execute the "DoSomething" method, so his solution is perfectly viable for my needs. Ordinarily I would agree that Interfaces were the way to go, but since this relates specifically to the "Smart Types" I'm working on, I need to be able to pass the record itself between host and DLL (now I can). Thanks :) – LaKraven Dec 08 '11 at 13:37
  • 1
    Just so that you know that the method executed is the one in the DLL, even when the record is created in the EXE. – David Heffernan Dec 08 '11 at 13:51
  • Yup... that's good to know, but in this instance that's the desired behaviour :) – LaKraven Dec 08 '11 at 14:00

2 Answers2

7

I would not suggest that, whether it works or not. If you need the DLL to operate on TMyRecord instances, the safest option is to have the DLL export plain functions instead, eg:

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.

App:

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; 
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Sadly that's not possible in this case, as the record MUST contain both the methods and the class operators to function as intended. Aside from that, normally this is what I would suggest (and do myself). – LaKraven Dec 08 '11 at 02:15
  • So I've +1'd it for common-sense :) – LaKraven Dec 08 '11 at 02:15
  • 1
    +1 I also don't suggest that, because in the future we won't know how well will it be supported, but the question is if it's possible... also, I don't know how the "record methods" are handled, but I assume it's just compiler magic, meaning, when you call the "record method", it just calls a method in which it passes the record as first/last argument and the rest are the method's arguments... I could be wrong of course –  Dec 08 '11 at 02:37
  • 1
    Another downside is it requires the same method code to reside in both DLL and EXE. You can't implement the method in the EXE and have the DLL call it directly, and vice versa. You need a VMT for that to work, which records do not have AFAIK. If the code ever changes, both EXE and DLL have to be recompiled to stay in sync. You could pass around a second record that contains function pointers that you pass your main record to when needed. That would allow you to centralize the code so only the EXE or the DLL is the one accessing the methods directly and acts as a proxy for the other module. – Remy Lebeau Dec 08 '11 at 05:33
  • 2
    "the record MUST contain both the methods and the class operators to function as intended." Note that the answer you accepted does not meet this criterion. When you send the record to a different module you send the data but not the code. – David Heffernan Dec 08 '11 at 07:15
  • Never thought of this before, but when passing records between an exe and a dll, how do compiler alignment settings affect this? In the given example it won't be a problem (certainly not on 32 bit platform), but what about when there is a char[1] or something like that after the double and that is followed by another integer? Would you run into problems if exe and dll are compiled with different alignment settings? Is it advisable to use packed to avoid those? – Marjan Venema Dec 08 '11 at 07:50
  • @marjan Yes, alignment must match. Packing is one option but not necessarily the best. You just need to ensure that the alignment matches, effect this with $ALIGN – David Heffernan Dec 08 '11 at 07:54
  • @DavidHeffernan "Note that the answer you accepted does not meet this criterion." yes and no, from what I understand(can be wrong of course), the dll and exe are both written in Delphi, and in my comment, I specify to use the "common" unit in which the type is defined so, if you can pass it from app to dll, I'm pretty sure you can do the reverse. Of course, if you have a better approach, I would love to see your answer and learn from it as I did from your previous answers on different questions. –  Dec 08 '11 at 08:12
  • 2
    @dorin If the dll and exe are updated in synch then it doesn't matter. And it's not clear precisely what the requirement is. That's why I added clarification to your answer. Personally I'd use an interface. – David Heffernan Dec 08 '11 at 08:28
  • @DorinDuminica: that is assuming that both EXE and DLL are compiled to use the same revision of the common unit at all times. If you start mixing different versions, you might run into problems. Davis is right that when you pass a record around, you are only passing around its data, not its code. Code is local to each module, so runtime behavior depends on what code is compiled into each module. – Remy Lebeau Dec 08 '11 at 18:57
4

If I understood your question correctly, then you can do it, here's one way to do it:

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 <- used by both application and dll, unit where your fancy record is defined

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.

and finally a test application, file -> new -> vcl forms application, drop a button on the form, include uCommon.pas in the uses clause, add reference to external method

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

and in the button's on click event, add

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

DISCLAIMER:

  • works in D2010;
  • compiles on my machine!

enjoy!


David Heffernan' edit

Just to be 100% clear, the DoSomething method that is executed is the method defined in the DLL. The DoSomething method defined in the EXE is never executed in this code.


  • 1
    Thanks for testing this for me :) +1 and marking as correct answer! – LaKraven Dec 08 '11 at 02:46
  • Using a pointer to the record makes no difference to behaviour. You could equally use a plain value paramter, const parameter etc. – David Heffernan Dec 08 '11 at 07:21
  • @DavidHeffernan yes, thank you, but there's more than one way to skin a cat! (: –  Dec 08 '11 at 07:26
  • 1
    My point is that your answer implies there is something special about using a pointer to the record. There is not. – David Heffernan Dec 08 '11 at 07:31
  • well, yes, didn't knew up until 5 minutes later that you can also use "const param" and the compiler does the magic. –  Dec 08 '11 at 08:08
  • But what magic? It doesn't matter how you pass the parameter in terms of what information is transferred. Your answer is very misleading. – David Heffernan Dec 08 '11 at 08:16