pyscripter / python4delphi

Free components that wrap up Python into Delphi and Lazarus (FPC)
MIT License
895 stars 307 forks source link

using python4delphi via compiled DLL, avoids reading variables from python to delphi #472

Closed mp1609 closed 2 months ago

mp1609 commented 2 months ago

Hi there,

I got a strange issue when using python4delphi as a DLL. I have a running demo (compiled as "exe" file) which is working:

FYI using the current latest code of this repo.

program ProjectWorking;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Classes,
  System.Variants,
  System.Diagnostics,
  WinApi.Windows,
  IOUtils,
  PythonEngine,
  VarPyth;

{$R *.res}

const
  coPythonFolder = 'python-3.8.3-embed-win32';

type
  TArrayOfDouble = TArray<double>;

var
  PythonEngine : TPythonEngine;
  cmd          : TStringList;

procedure CreatePythonEnvironment;
begin
end;

procedure CreatePyEngine;
begin
  PythonEngine := TPythonEngine.Create(nil);
  PythonEngine.Name := 'PythonEngine';

  with PythonEngine do
  begin
    APIVersion          := 1013;
    AutoLoad            := False;
    AutoUnload          := true;
    AutoFinalize        := true;
    UseLastKnownVersion := false;
    DllName := 'python38.dll';
    DllPath := GetCurrentDir + '\' + coPythonFolder;

    PyFlags := [];
  end;

  PythonEngine.LoadDll;
end;

procedure DestroyEngine;
begin
  PythonEngine.Free;
end;

procedure demo;  stdcall;
var
  foo   : Variant;
var
  taps,
  freq,
  gain : TArrayOfDouble;
begin
  freq := [0, 0.5, 1];
  gain := [0,   1, 0];

  try
    cmd.Clear;
    cmd.Add('N = [0,1,2,3,4,5,6,7,8,9,10]');

    PythonEngine.ExecStrings(cmd);
    foo             := MainModule.N;     // foo = OK, I see data of "N"

  finally

  end;
end;

exports
  demo;

var
  taps : Variant;
begin
  CreatePyEngine;
  cmd := TStringList.Create;
  demo;
end.

So, when running the code, my variable "foo" is readable: grafik

Now I copy that same code and compile it as DLL:

library ProjectDll;

uses
  System.SysUtils,
  System.Classes,
  System.Zip,
  System.Variants,
  System.Diagnostics,
  WinApi.Windows,
  IOUtils,
  PythonEngine,
  VarPyth;

{$R *.res}

const
  coPythonFolder = 'python-3.8.3-embed-win32';

type
  TArrayOfDouble = TArray<double>;

var
  PythonEngine : TPythonEngine;
  cmd          : TStringList;

procedure CreatePythonEnvironment;
begin
end;

procedure CreatePyEngine;
begin
  PythonEngine := TPythonEngine.Create(nil);
  PythonEngine.Name := 'PythonEngine';

  with PythonEngine do
  begin
    APIVersion          := 1013;
    AutoLoad            := False;
    AutoUnload          := true;
    AutoFinalize        := true;
    UseLastKnownVersion := false;
    DllName := 'python38.dll';
    DllPath := GetCurrentDir + '\' + coPythonFolder;

    PyFlags := [];
  end;

  PythonEngine.LoadDll;
end;

procedure DestroyEngine;
begin
  PythonEngine.Free;
end;

procedure demo;  stdcall;
var
  foo   : Variant;
var
  taps,
  freq,
  gain : TArrayOfDouble;
begin
  freq := [0, 0.5, 1];
  gain := [0,   1, 0];

  try
    cmd.Clear;
    cmd.Add('N = [0,1,2,3,4,5,6,7,8,9,10]');

    PythonEngine.ExecStrings(cmd);
    foo             := MainModule.N;          // foo = is NOT OK - I can not see data of "N"

  finally

  end;
end;

exports
  demo;

var
  taps : Variant;
begin
  CreatePyEngine;
  cmd := TStringList.Create;
end.

To demonstrate how I call the DLL, here is my "DLL caller":

unit UnitDllCaller;

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)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    dll_demo : procedure;  stdcall;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
  FDLLHandle : THandle;
implementation

{$R *.dfm}

procedure loadDLLFunction(var APointer : Pointer; AFnName : String; const ARequired : boolean = true);
var
  fnName : PAnsiChar;
begin
  fnName := PAnsiChar(AnsiString(AFnName));

  APointer := GetProcAddress(FDLLHandle, fnName);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin

  FDLLHandle := LoadLibrary(PWchar('ProjectDll.dll'));

  if(FDLLHandle > 0) then
  begin
    loadDLLFunction(@dll_demo , 'demo');
    dll_demo();
  end;
end;

end.

So running DLL caller, is working, but my "foo" variable causes an exception: grafik

Here is also the Delphi project to reproduce this: demo.zip

Anyone got an idea? The python code was always executed without errors. I tried to debug a little bit more:

    cmd.Clear;
    cmd.Add('N = [0,1,2,3,4,5,6,7,8,9,10]');
    cmd.Add('f = open("demofile2.txt", "w")');
    cmd.Add('f.write(str(N))');
    cmd.Add('f.close()');

The written demofile2.txt looks as expected, so it seems that reading the variable back causes an error?!

pyscripter commented 2 months ago

Actually it does work fine despite inspection of foo not working. See below: image