16 июля 2020

Python. Вызов функции DLL со строковыми параметрами

    Вот я добрался и до Python. Так сказать – первые пробы пера. Но куда я в Python без Delphi? Сегодня столкнулся с тем, что в Python нужно было вызвать функцию из DLL, которая была написана много лет тому назад на Delphi.
    Немного общей теории. Для вызова функций из сторонних библиотек в Python есть специальный модуль - ctypes. Этот модуль предоставляет разработчику C-совместимые типы данных и позволяет вызывать функции под различными операционными системами. Для загрузки динамических библиотек модуль ctypes предоставляет три метода:
  • cdll - загружает библиотеки, которые экспортируют функции используя соглашение о вызовах cdecl;
  • windll - загружает библиотеки, которые экспортируют функции используя соглашение о вызовах stdcall;
  • oledll - загружает библиотеки, которые экспортируют функции используя соглашение о вызовах stdcall и предполагает, что результат функции имеет тип HRESULT (как это требуют COM/OLE).
Итак, у нас есть DLL работающая со строками:
library xyz;

function TestW(const pText1, pText2: PChar): PChar; stdcall;
begin
  Result := ...;
end;

function TestA(const pText1, pText2: PAnsiChar): PAnsiChar; stdcall;
begin
  Result := ...;
end;

exports
  TestA, TestW;
begin
end.
Как вы видите, DLL экспортируют функции используя соглашение о вызовах stdcall, поэтому загружаем ее с помощью метода windll:
import ctypes

text1 = 'Привет'
text2 = 'Delphi'

# загружаем DLL
lib  = ctypes.WinDLL('xyz.dll')
# получаем функцию
funcTest = lib.TestW
# задаем типы параметров функции
funcTest.argtypes = ctypes.c_wchar_p, ctypes.c_wchar_p
# задаем тип результата функции
funcTest.restype = ctypes.c_wchar_p
# вызываем функцию и выводим результат ее выполнения
print(funcTest(text1, text2))
    Вызов функции с параметрами типа PAnsiChar немного сложнее:
import ctypes

text1 = 'Привет'
text2 = 'Delphi'

lib  = ctypes.WinDLL('xyz.dll')
funcTest = lib.TestA
funcTest.restype = ctypes.c_char_p
funcTest.argtypes = ctypes.c_char_p, ctypes.c_char_p
print(funcTest(text1.encode(), text2.encode()).decode())
Как вы видите, кроме изменения типа параметров, в коде появились вызовы методов encode() и decode(). Метод encode() кодирует параметры в байтовые строки. Если это не сделать, то во время выполнения программы мы получим ошибку:
ctypes.ArgumentError: argument 1: : wrong type
Метод decode() выполняет обратную операцию – он декодирует результат выполнения функции из байтовой строки. Если это не сделать, то print, при выводе результата функции, вместо русских букв выведет байты. Например, строка "Привет Python" превратится в "b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82 Python'".
    Даже для меня, человека, который плохо знает Python, вызвать функцию из DLL оказалось очень легко.

3 комментария:

  1. ну ты герой, для меня питон это дичь конченная, одни отступы чего стоят...

    ОтветитьУдалить
  2. Ну не знаю... Все вокруг как раз копья ломают по "begin-end" или "{-}", а тут просто идеальное решение.

    ОтветитьУдалить
  3. Какой шикарный код.

    function TestW(const pText1, pText2: PChar): PChar; stdcall;

    Особенно в случаях когда строковый буфер будет разрушен после финализации функции и снаружи будет происходить работа с указателем на освобожденные данные :)))

    ОтветитьУдалить