Как передать ссылки объявленных переменных в функцию C в python, используя ctypes

Я хотел бы вызвать функцию C из python. Эта функция C недействительна, поэтому «возвращаемые параметры» (данные, которые я хочу изменить) определяются как указатели в определении функции C.

Функция на C выглядит так (примечание: я не могу и не буду ее менять, так как она генерируется автоматически MATLAB codegen):

void func(int   *input_var,    // input
          float *output_var    //output
         ) {
    ... 
}

из python я вызываю свою функцию так

import ctypes as C

func = C.CDLL('path/to/lib.so').func

input_var = C.c_int(5)
output_var = C.POINTER(C.c_float) # basically want to just declare a pointer here
func(C.byref(input_var), C.byref(output_var))

Ошибка, которую я получаю,

TypeError: byref() argument must be a ctypes instance, not '_ctypes.PyCPointerType'

если я удалю bref() я получу

ctypes.ArgumentError: argument 2: <class 'TypeError'>: Don't know how to convert parameter 2

Я также пытался передать output_var как C.byref(output_var()); это приводит к Segmentation fault (core dumped)

POINTER(c_float) — это тип. Экземпляром будет POINTER(c_float)(), но на самом деле вам нужен просто экземпляр с плавающей запятой: float(). Пройди byref
Mark Tolonen 09.02.2023 08:24

создание экземпляра работает.

Brian Barry 09.02.2023 18:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

вы можете использовать .byref сразу после объявления переменной, не нужно делать указатель.

поэтому в вашем примере вы можете объявить вывод как тип переменной, который вам нужен, а затем передать его по ссылке на функцию.

 output_var = C.c_float()
 func(C.byref(input_var), C.byref(output_var)

это должно работать.

Нет, это не сработает. c_float — это тип. Вам нужен экземпляр: c_float()

Mark Tolonen 09.02.2023 08:20
Ответ принят как подходящий

Листинг [Python.Docs]: ctypes — внешняя библиотека функций для Python.

Делайте то же, что и в C (и вы уже делаете это для входного аргумента): объявите переменную с плавающей запятой и передайте ее адрес функции (через byref).

Одна важная вещь: проверьте [SO]: функция C, вызванная из Python через ctypes, возвращает неправильное значение (@ответ CristiFati) для распространенной ловушки при работе с CTypes (вызов функций).

Пример (самый простой сценарий: указатели оборачивают одиночные значения, если бы были массивы или функция выполняла некоторые выделения памяти, все было бы немного сложнее).

  • dll00.c:

    #include <stdio.h>
    
    #if defined(_WIN32)
    #  define DLL00_EXPORT_API __declspec(dllexport)
    #else
    #  define DLL00_EXPORT_API
    #endif
    
    
    #if defined(__cplusplus)
    extern "C" {
    #endif
    
    DLL00_EXPORT_API void dll00Func00(int *pIn, float *pOut);
    
    #if defined(__cplusplus)
    }
    #endif
    
    
    void dll00Func00(int *pIn, float *pOut)
    {
        if (pIn)
            printf("C - In: %d\n", *pIn);
        if (pOut) {
            float f = 3.141593 * (pIn ? *pIn : 1);
            printf("C - Setting out to: %.3f\n", f);
            *pOut = f;
        }
    }
    
  • code00.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import sys
    
    
    DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    
    def main(*argv):
        dll = cts.CDLL(DLL_NAME)
        func = dll.dll00Func00
        func.argtypes = (cts.POINTER(cts.c_int), cts.POINTER(cts.c_float))
        func.restype = None
    
        i = cts.c_int(2)
        f = cts.c_float(0)
    
        res = func(cts.byref(i), cts.byref(f))
        print("Values after calling function: {:d}, {:.3f}".format(i.value, f.value))
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

Выход:

(qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q075393602]> ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]> ls
code00.py  dll00.c
[064bit prompt]>
[064bit prompt]> gcc -shared -o dll00.so dll00.c
[064bit prompt]>
[064bit prompt]> python ./code00.py
Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] 064bit on linux

C - In: 2
C - Setting out to: 6.283
Values after calling function: 2, 6.283

Done.

Это имеет смысл, однако чтение вашего конкретного сценария было немного запутанным по сравнению с моей ситуацией, поскольку вы инициализируете свою выходную переменную f значением (0). Но в моем случае оказывается, что с помощью ctypes вы можете «объявить» переменную, создав ее экземпляр без аргумента/значения, т. Е. c_float(), как объяснил @Mark Tolonen

Brian Barry 09.02.2023 18:54

:) Это как в C: у вас может быть float f; или float f = 0;. Я всегда инициализирую переменные (у меня привычка не полагаться на значения компилятора по умолчанию). В любом случае в CTypes 2 эквивалентны, как если бы вы печатали «неинициализированный», он по-прежнему равен 0 (инициализированный по умолчанию). Также это хорошо в вашей ситуации, так как вы можете ясно видеть, что функция фактически изменила значение.

CristiFati 09.02.2023 19:21

Я понимаю; Я не знал об этом поведении компилятора по умолчанию!

Brian Barry 09.02.2023 19:24

На самом деле я неправильно прочитал комментарий. POINTER(c_float)() создает число с плавающей запятой* (которое равно NULL). Теперь я не знаю, что функция делает внутри (обрабатывает ли она этот случай или нет, выделяет ли она память и т. д.). Передача указателя NULL в мою функцию ничего с ним не делает, поэтому возвращаемого значения нет.

CristiFati 09.02.2023 19:29

Другие вопросы по теме