Search code examples
delphidelphi-xe

Delphi - modify variable from DLL


I want to make simple program that sets Edit1.Text to "6" (for example, but with usage of DLL - thats important). Here's the code: Unit:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  a:integer;

implementation
procedure test; external 'lib.dll' name 'test';

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
test;

Edit1.Text:=Inttostr(a);

end;

end.

And the DLL file:

library lib;

uses
Winapi.Windows, System.SysUtils;

var
a:integer;
procedure test;
begin
  a:=6;
end;

exports
test;
{$R *.res}

begin
end.

The problem is, that Edit1.Text is still 0. Can you help me, please?


Solution

  • Building on your original code and adding a few more buttons, here's a demonstration of how you can use procedures (or functions if you prefer) and have them play nicely with a DLL.

    Note that the name option is not required unless you wish to change the function's name or use overloading - so I've commented it out.

    implementation
    procedure test(var a : integer); external 'lib.dll' {name 'test'};
    procedure test2(ptr_a : pinteger); external 'lib.dll';
    procedure test3(ptr_a : pinteger); external 'lib.dll';
    procedure test4(ptr_a : pinteger = nil); external 'lib.dll';
    
    {$R *.dfm}
    
    procedure TForm14.Button1Click(Sender: TObject);
    begin
      test(a);
    
      Edit1.Text:=Inttostr(a);
    
    end;
    
    procedure TForm14.Button2Click(Sender: TObject);
    begin
      test2(@a);
    
      Edit1.Text:=Inttostr(a);
    
    
    end;
    
    procedure TForm14.Button3Click(Sender: TObject);
    begin
      test3(@a);
    
      Edit1.Text:=Inttostr(a);
    
    end;
    
    procedure TForm14.Button4Click(Sender: TObject);
    begin
      test4(@a);
      test4;
    
      Edit1.Text:=Inttostr(a);
    
    end;
    
    end.
    

    ... and the library body ...

    var
      local_a:integer;
      local_Ptr_a:pinteger;
    procedure test(var a : integer);
    begin
      a:=6;
    end;
    
    procedure test2(ptr_a : pinteger);
    begin
      inc(ptr_a^);
    end;
    
    procedure test3(ptr_a : pinteger);
    begin
      inc(local_a);
      ptr_a^:=local_a;
    end;
    
    procedure test4(ptr_a : pinteger = nil);
    begin
      if ptr_a = nil then
        inc(local_ptr_a^)
      else
        local_ptr_a := ptr_a;
    end;
    
    exports test;
    exports test2;
    exports test3;
    exports test4;
    {$R *.res}
    
    begin
      local_a := 4;
    end.
    

    So - to a little explanation.

    First test : using a var-parameter to return a value from a procedure. No problem there.

    Second test : pass the address of the receiving variable as a pointer. I've added a little curl here - incrementing the value for entertainment er,...value.

    Third test : This is showing how local values owned by the DLL can be used. The local value is initialised by the assignment of 4 at the end. The procedure itself uses the same mechanism as the second test to return the value from the DLL's local variables to the main routine's variables.

    Note that test3 assigns to the program's variable (1 + the value stored in the DLL's memory, ) hence pressing button3 will actually changes a; so pressing buttons 1-2-2-3-2 will use the value from 3 for the last change, not the prior-value-from-2.

    Final test: this where we can get a little more clever. It uses the optional-parameters mechanism to vary the detailed operation.

    First you eecute the procedure with a parameter, being the address of (or pointer to) a variable of the appropriate type. The procedure stores that address in the DLL's memory area.

    Next you can execute the procedure with no parameters and it will increment the integer to which the stored pointer is pointing. Purely for convenience, I've established the variable's address on each button-push, but once the address has been stored, it doesn't matter whether that address was set a few microseconds ago or a week, test4; will increment the integer value at that address - whatever it is. Set the address using test4(@b); then test4; will increment b - whichever integer was last pointed to when the procedure was executed with a parameter.

    executing test4; without having at sometime prior executed a test4(@something) or where something is now out-of-scope (like perhaps a local variable in a procedure) is very likely to cause an access violation.