Delphi и Python embedded

·

2 min read

Есть у меня программка, которая предназначена для запуска скриптов Python из Delphi.

Использую я Python embedded, так что для этой программы нет необходимости установить Python в компьютере. Недавно я хотел свою программу на Delphi совершенствовать. Проблема была в том, что скрипты запускались в основном потоке, из-за чего UI программы зависал до окончания работы скрипта Python. А некоторые скрипты работали довольно долго. Не долго думая, решил использовать TTask. Вот фрагмент программы, запускающий скрипт Python-а в отдельный поток Delphi:

  task := TTask.Create(
       procedure
       begin
         GetPythonEngine.ExecFile(PyFileName);

         if task.Status = TTaskStatus.Canceled then
            //Если отменили, выходим из цикла.
            Exit;
       end);
  task.Start;

Запускаю программу, вроде все работает. Но в скриптах где импортируется библиотека numpy скрипт зависает. Эти же скрипты в основном потоке работали. Но тут никак. Ошибка не исчезла. Пришлось открыть запрос в гитхабе. Там мне автор подсказал статью https://github.com/pyscripter/python4delphi/wiki/PythonThreads, где довольно подробно объясняется запуск скриптов в отдельном потоке.

Первый вариант:

ThreadPythonExec(
    procedure
    begin
      GetPythonEngine.ExecFile(PyFileName);
    end);

Второй вариант:

Task := TTask.Create(
    procedure
    var
      Py: IPyEngineAndGIL;
    begin
      Py := SafePyEngine;
      Py.PythonEngine.ExecFile(PyFileName);
    end);
Task.Start;

Там есть еще и другие варианты, но сейчас не об этом. Но опять проблема не решена. И оказывается при создании формы следует вызывать следующий код:

TPythonThread.Py_Begin_Allow_Threads;

А при закрытии формы:

TPythonThread.Py_End_Allow_Threads;

И вроде все работает, проблема решена. Но не тут-то было.

У меня MDI-приложение. И мой PythonEngine был помещен в форме со списками скриптов. И когда я закрывал эту форму и заново вызывал форму, то скрипт с импортом numpy опять не работал и выдавал следующее сообщение:

C:\A\34\s/Objects/structsseq.c:398: bad argument to internal function

Тогда я решил при уничтожении формы делать следующее:

PyEngine.UnloadDll;
TPythonThread.Py_End_Allow_Threads; // Acquire the GIL
FreeAndNil(PyEngine);

Но ничего не изменилась. Обратился к автору. Ответ автора:

Почему вы освобождаете PythonEngine? Уничтожение и воссоздание PythonEngine - не самая лучшая идея.

Почему я не могу пересоздавать PythonEngine я так и не понял.

Решение проблемы.

Пришлось перенести PythonEngine и PythonInputOutput в DataModule. При создании DataModule:

// Prepare Python Engine
MaskFPUExceptions(True);
PyEngine.DllPath := glSetup.AppPath + 'DLLs';
PyEngine.DllName := 'python38.dll';
PyEngine.APIVersion := 1013;
PyEngine.AutoLoad := False;
PyEngine.LoadDll;
TPythonThread.Py_Begin_Allow_Threads; // Release the GIL

А в форме со списком скриптов

procedure TfrmScripts.PyInOutSendData(Sender: TObject; const Data: AnsiString);
begin
  PyConsole.Lines.Add(Data);
end;

procedure TfrmScripts.PyInOutSendUniData(Sender: TObject; const Data: string);
var S: String;
begin
  if IsUTF8String(Data) then
     S := UTF8Decode(Data)
  else
     S := Data;
  PyConsole.Lines.Add(S);
end;

PyConsole у меня это компонента TSynEdit. Все данные со скриптов выводятся в TSynEdit.

При создании формы со списками скриптов:

DMain.PyInOut.OnSendData := PyInOutSendData;
DMain.PyInOut.OnSendUniData := PyInOutSendUniData;

И вот, проблема решена. Все прекрасно работает.