Я создаю интерфейс для библиотеки C в Python, используя ctypes, и у меня есть функция C, которая обычно возвращает char ** (массив C-строк), но возвращает NULL при ошибке. Я не знаю длину возвращаемого массива, последняя запись будет NULL. Я пробовал несколько типов для restype:
restype = POINTER(c_char_p)
restype = POINTER(POINTER(c_char))
Оба они работают нормально, когда функция C успешно возвращается (с немного другой обработкой впоследствии). Но у обоих есть проблемы, когда функция C имеет ошибку и возвращает NULL. Я ожидал, что возвращаемое значение будет None, но вместо этого получил указатель на байтовую строку трассировки стека, которая заканчивается «ValueError: доступ к указателю NULL».
Каким должен быть retype для функции C, которая преобразует char** в массив строк в случае успеха, но преобразует NULL в None в случае ошибки?






Пара вариантов:
c_void_p. Он будет принудительно заменен на None, если будет возвращен NULL, в противном случае он будет приведен к POINTER(c_char_p) для извлечения строк.POINTER(c_char_p) для извлечения строк. Заверните в try/except и верните None на ValueError.Либо работает. Я предпочитаю использовать оболочку, чтобы заставить функцию вести себя так, как нужно, и она гарантирует, что, если строки были динамически выделены, они освобождаются после извлечения как строки Python.
Пример:
тест.с
#if defined(_WIN32)
# define API __declspec(dllexport)
#else
# define API
#endif
#include <stdlib.h>
#include <string.h>
API char** get_strings(int fail) {
if (fail)
return NULL;
char** p = malloc(sizeof(char*) * 3);
p[0] = _strdup("test1");
p[1] = _strdup("test2");
p[2] = NULL;
return p;
}
API void free_strings(char** p) {
if (p) {
char** tmp = p;
while(*p)
free(*p++);
free(tmp);
}
}
test.py
from ctypes import *
dll = CDLL('./test')
dll.get_strings.argtypes = c_int,
dll.get_strings.restype = POINTER(c_char_p)
dll.free_strings.argtypes = POINTER(c_char_p),
dll.free_strings.restype = None
def get_strings(fail):
p = dll.get_strings(fail)
result = []
try:
for s in p:
if s is None: break
result.append(s)
return result
except ValueError:
return None
finally:
dll.free_strings(p)
print(get_strings(0))
print(get_strings(1))
Выход:
[b'test1', b'test2']
None
Я пошел с переключением restype на c_void_p, а затем приведением к POINTER(POINTER(c_char)), потому что это в основном соответствовало тому, что я делал раньше. И это сработало. Я почти хочу подать отчет об ошибке по этому поводу, я думаю, что NULL должен быть принудительно равен None для обоих исходных типов возврата, которые у меня были. Я проверил None, это не сработало, потому что там было какое-то значение.