ctypes и построение метаклассов: TypeError: экземпляр *class* вместо экземпляра LP_*class*

История: я использую ctypes для общения с python на C и наоборот. Я также создаю общую библиотеку C, которую тоже пытаюсь подключить. На данном этапе разработки это всего лишь простая библиотека для проверки всех концепций перед погружением в код. Библиотека написана на C++, выставляя функции с внешним "C", ничего особенного. Я тестировал функции с примитивными параметрами/возвратными типами, указателями и обратными вызовами функций.

Теперь я хочу передать структуры. Поскольку я ленивый программист, я планирую передать структуры C++ в унифицированное представление C (т.е. комбинацию простого словаря и списков) и обработать это в python, который преобразует это в реальный объект python (т.е. комбинация словаря и списков Python).

Проблема: Чтобы добиться этого, сначала я определил шаблонный словарь на C++, его реализация, просто для тестирования, представляет собой связанный список пар ключ-значение, где словарь владеет корнем. Затем для каждой функции, которой нужна специализация, typedef этой специализации используется как структура C.

Код выглядит так (не фактический код):

#include <cstdlib>

template <typename key_t, typename value_t>
struct DictNode
{
    key_t key;
    value_t value;
};

template <typename key_t, typename value_t>
struct Dict
{
    typedef DictNode<key_t, value_t> node_t;
    node_t root;
};

typedef Dict<int, char> Dict_int_char;

extern "C" Dict_int_char* example_new()
{
    Dict_int_char* result;
    result = (Dict_int_char*)malloc(sizeof(Dict_int_char));
    return result;
}

extern "C" void example_delete(Dict_int_char* value)
{
    free(value);
}

Теперь в python, чтобы не создавать класс для каждой специализации, я придерживаюсь того же подхода. Метод создаст для меня специализированный класс с учетом типов ключ-значение.

Код выглядит так (фактический код):

import types
import ctypes

# This is to provide some hiding of the module internals
# Suggestions on a more pythonic way are gladly accepted
class __Internals:
  """
  Creates class to interface with a C structure comming from a
  typedef'd C++ class template specialization. This method recieves
  the types of the template class, creates the ctypes classes to
  interface with the specialized class (which has been typedef'd)
  and returns them for usage with ctypes.
  """
  @staticmethod
  def __DictClassCreate__(key_t, value_t):
    # Foward declare the classes
    class InterfaceListNode(ctypes.Structure):
      pass;
    class InterfaceList(ctypes.Structure):
      pass;

    #### NODE
    # Node class
    nodeType = InterfaceListNode;
    # The pointer-to-node class 
    nodeTypePointerType = ctypes.POINTER(nodeType);
    # Fields of the node class (next, key, value)
    nodeType._fields_ = [("next", nodeTypePointerType),
                         ("key", key_t),
                         ("value", value_t) ];

    # Function to create a node pointer
    def nodeTypePointerCreate(cls, value=None):
      if(value is None):
        return nodeTypePointerType();
      else:
        return nodeTypePointerType(value);

    # Bind the function to the node class
    nodeType.pointer = types.MethodType(nodeTypePointerCreate, nodeType);

    #### DICT
    # Dict class
    dictType = InterfaceList;
    # The pointer-to-dict class 
    dictTypePointerType = ctypes.POINTER(dictType);
    # Useful for dict to know the types of it's nodes
    dictType._nodeType = nodeType;
    # Fields of the dict class (root)
    dictType._fields_ = [("root", ctypes.POINTER(nodeType))];

    # Function to create a dict pointer
    def dictTypePointerCreate(cls, value=None):
      if(value is None):
        return dictTypePointerType();
      else:
        return dictTypePointerType(value);

    # Bind the function to the dict class
    dictType.pointer = types.MethodType(dictTypePointerCreate, dictType);    

    # For debugging
    print 'Inside metaclass generator'
    print hex(id(nodeType));
    print hex(id(dictType));

    # Return just the dict class since it knows about it's node class.
    return dictType;

# Create a new specialized dict<c_uint, c_char>
dictType_1 = __Internals.__DictClassCreate__(ctypes.c_uint, ctypes.c_char);
# Obtain the node type of this dict
nodeType_1 = dictType_1._nodeType;

# For debugging
print 'In Script'
print hex(id(nodeType_1));
print hex(id(dictType_1));

# Try to instance this dictionary with 1 element
#(not NULL root, NULL root.next)
dict_1 = dictType_1(nodeType_1(nodeType_1.pointer(), 0, 'a'));

Когда этот код запускается, отображается следующий вывод:

python SciCamAPI.py
Inside metaclass generator
0x249c1d8L
0x249c588L
In Script
0x249c1d8L
0x249c588L
Traceback (most recent call last):
  File "SciCamAPI.py", line 107, in <module>
    dict_1 = dictType_1(nodeType_1(nodeType_1.pointer(), 0, 'a'));
TypeError: incompatible types, InterfaceListNode instance instead of LP_InterfaceListNode instance

Из печатного вывода я вижу, что использую те же метаклассы для экземпляра простого словаря и его узлов, что и сгенерированный в методе.

Я гуглил, что может быть LP_ добавленным к ошибке, но поиск LP_ python возвращает только линейные решатели задач и этот ответ. Из того, что понимает ответ, ctypes создает указатель в стиле C из nodeType_1.pointer() (последняя строка), но это то, что должно быть получено, когда node.next был объявлен как [("next", nodeTypePointerType) ,...] (в nodeType.полях=...). Так что я довольно потерян.


person jabozzo    schedule 24.02.2016    source источник
comment
dictType_1.root — это поле LP_InterfaceListNode, то есть POINTER(InterfaceListNode), но вы инициализируете его InterfaceListNode вместо указателя на единицу. LP предназначен для длинного указателя, существовавшего во времена сегментированных архитектур, которые имели ближние (внутри сегмента) и дальние/длинные указатели. Типы Windows сохраняют этот префикс, даже если он больше не имеет смысла, например LPVOID и LPWSTR. ctypes изначально был пакетом только для Windows.   -  person Eryk Sun    schedule 24.02.2016
comment
Спасибо, вот оно! Замена последней строки на: rootNode = nodeType_1(nodeType_1.pointer(), 0, 'a'); dict_1 = dictType_1(nodeType_1.pointer(rootNode)); делает это. Должны ли вы опубликовать ответ, чтобы принять его, или я цитирую вас?   -  person jabozzo    schedule 24.02.2016


Ответы (1)


Цитируя комментарий eryksun:

dictType_1.root — это поле LP_InterfaceListNode, то есть POINTER(InterfaceListNode), но вы инициализируете его InterfaceListNode вместо указателя на единицу. «LP» означает «длинный указатель» еще во времена сегментированных архитектур, которые имели ближние (внутри сегмента) и дальние/длинные указатели. Типы Windows сохраняют этот префикс, даже если он больше не имеет смысла, например LPVOID и LPWSTR. ctypes изначально был пакетом только для Windows

Поэтому я пропустил преобразование dictType_1.root в указатель. Изменение последней строки на:

rootNode = nodeType_1(nodeType_1.pointer(), 0, 'a');
dict_1 = dictType_1(nodeType_1.pointer(rootNode));

Решает проблему.

person jabozzo    schedule 26.02.2016