CPPYY / CTYPES передача массива строк как char * args [] - PullRequest
5 голосов
/ 25 мая 2020

Я только недавно начал использовать cppyy и ctypes, так что это может быть немного глупый вопрос. У меня есть следующая функция C ++:

float method(const char* args[]) {
    ...
}

и из Python я хочу передать args как список строк, то есть:

args = *magic*
x = cppyy.gbl.method(args)

Я уже нашел это , поэтому я использовал

def setParameters(strParamList):
    numParams    = len(strParamList)
    strArrayType = ct.c_char_p * numParams
    strArray     = strArrayType()
    for i, param in enumerate(strParamList):
        strArray[i] = param
    lib.SetParams(numParams, strArray)

, а from Python:

args = setParameters([b'hello', b'world'])

c_types.c_char_p ожидает массив байтов. Однако при вызове x = cppyy.gbl.method(args) я получаю

TypeError: could not convert argument 1 (could not convert argument to buffer or nullptr)

Я не совсем уверен, почему это было бы неправильно, поскольку args является объектом <__main__.c_char_p_Array_2>, который, как я считаю, должен быть преобразован в const char* args[].

Ответы [ 2 ]

3 голосов
/ 25 мая 2020

Для конкретного примера я буду использовать это как .cpp файл:

#include <cstdlib>

extern "C"
float method(const char* args[]) {
    float sum = 0.0f;
    const char **p = args;
    while(*p) {
        sum += std::atof(*p++);
    }
    return sum;
}

И я предполагаю, что он был скомпилирован с g++ method.cpp -fPIC -shared -o method.so. Учитывая эти предположения, вот пример того, как вы могли бы использовать его из Python:

#!/usr/bin/env python3

from ctypes import *

lib = CDLL("./method.so")
lib.method.restype = c_float
lib.method.argtypes = (POINTER(c_char_p),)

def method(args):
    return lib.method((c_char_p * (len(args) + 1))(*args))

print(method([b'1.23', b'45.6']))

Мы создаем массив C для хранения Python аргументов. len(args) + 1 проверяет, есть ли место для сигнального устройства нулевого указателя.

2 голосов
/ 26 мая 2020

ctypes не имеет API publi c, который можно использовать из C / C ++ для разработчиков расширений, поэтому обработка ctypes с помощью cppyy по необходимости несколько неудобна. Что не так, так это то, что сгенерированный массив ctypes const char* имеет тип const char*[2], а не const char*[], и поскольку cppyy выполняет прямое сопоставление типов для типов ctypes, это не удается.

Как есть, некоторые код где-то должен выполнить преобразование Python строк в C нижнего уровня и удерживать эту память на время вызова. Лично я бы использовал небольшую оболочку C ++, вместо того, чтобы все продумывать на стороне Python. Дело в том, что std::vector<std::string> может иметь дело с необходимыми преобразованиями (поэтому тип bytes не нужен, например, но, конечно, разрешен, если вы хотите), и он может хранить временную память.

Итак , если вам предоставлен какой-то сторонний интерфейс, подобный этому (вставив его в строку для cppyy только для примера):

import cppyy

cppyy.cppdef("""
    float method(const char* args[], int len) {
        for (int i = 0; i < len; ++i)
            std::cerr << args[i] << " ";
        std::cerr << std::endl;
        return 42.f;
    }
""")

Тогда я бы создал оболочку:

# write a C++ wrapper to hide C code
cppyy.cppdef("""
    namespace MyCppAPI {
       float method(const std::vector<std::string>& args) {
           std::vector<const char*> v;
           v.reserve(args.size());
           for (auto& s : args) v.push_back(s.c_str());
           return ::method(v.data(), v.size());
       }
    }
""")

Затем замените исходный C API версией C ++:

# replace C version with C++ one for all Python users
cppyy.gbl.method = cppyy.gbl.MyCppAPI.method

, и все будет так, как ожидалось для любого другого человека ниже по течению:

# now use it as expected
cppyy.gbl.method(["aap", "noot", "mies"])

Все, что сказано, очевидно, что нет причины, по которой cppyy не мог бы выполнить эту часть обертывания автоматически. Я создал эту проблему: https://bitbucket.org/wlav/cppyy/issues/235/automatically-convert-python-tuple-of

...