如何使用 boost.python 将预填充的"unsigned char*"缓冲区传递给C++方法?

How do I pass a pre-populated "unsigned char*" buffer to a C++ method using boost.python?

本文关键字:缓冲区 方法 C++ char unsigned python boost 何使用 填充      更新时间:2023-10-16

我有一个带有成员函数的c++类,它接受一个无符号char*缓冲区和一个无符号int长度作为参数,并对它们进行操作。我已经用Boost::Python包装了这个类,并希望通过Python脚本将预填充的缓冲区传递给该类。python端缓冲区是用struct.pack创建的。我不知道如何使参数类型匹配,并不断得到Boost.Python.ArgumentError。

包括/Example.h

#ifndef EXAMPLECLASS_H_
#define EXAMPLECLASS_H_
#include <cstdio>
class ExampleClass
{
public:
    ExampleClass() {}
    virtual ~ExampleClass() {}
    void printBuffer(unsigned char* buffer, unsigned int length)
    {
        for (unsigned int i = 0; i < length; ++i)
        {
            printf("%c", buffer[i]);
        }
        printf("n");
    }
};
#endif

src/example.cpp

#include "Example.h"
int main(int argc, char** argv)
{
    unsigned char buf[4];
    buf[0] = 0x41;
    buf[1] = 0x42;
    buf[2] = 0x43;
    buf[3] = 0x44;
    ExampleClass e;
    e.printBuffer(buf, 4);
    return 0;
}

src/Example_py.cpp

#include <boost/python.hpp>
#include "Example.h"
using namespace boost::python;
BOOST_PYTHON_MODULE(example_py)
{
    class_<ExampleClass>("ExampleClass")
    .def("printBuffer", &ExampleClass::printBuffer)
    ;
}

脚本/example.py

#!/usr/bin/env python
import example_py
import struct
import ctypes
buf = struct.pack('BBBB', 0x41, 0x42, 0x43, 0x44)
print 'python:'
print buf
e = example_py.ExampleClass()
print 'c++:'
print e.printBuffer(ctypes.cast(ctypes.c_char_p(buf), ctypes.POINTER(ctypes.c_ubyte)), len(buf))

CMakeLists.txt(不完全)

include_directories(
    include
    ${Boost_INCLUDE_DIRS}
    ${PYTHON_INCLUDE_DIRS}
)
add_library(example_py
    src/Example_py.cpp
)
target_link_libraries(example_py ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
set_target_properties(example_py PROPERTIES PREFIX "")
add_executable(example src/example.cpp)
target_link_libraries(example example_py)

输出
$ ./example
ABCD
$ ./scripts/example.py
python: ABCD
c++:
Traceback (most recent call last):
  File "/home/dustingooding/example/scripts/example.py", line 13, in <module>
    print 'c++:', e.printBuffer(ctypes.cast(ctypes.c_char_p(buf), ctypes.POINTER(ctypes.c_ubyte)), len(buf))
Boost.Python.ArgumentError: Python argument types in
    ExampleClass.printBuffer(ExampleClass, LP_c_ubyte, int)
did not match C++ signature:
    printBuffer(ExampleClass {lvalue}, unsigned char*, unsigned int)

我尝试了许多不同的方法(直接传递'buf',传递'buf'作为ctypes.c_char_p,创建一个ctypes。Ubyte数组并填充'但是'的内容并传递它),但似乎都不起作用。

我不明白为什么LP_c_ubyte和unsigned char*不匹配。

编辑

这是一个带有现成代码库的Github项目。请随意使用它。我添加了@Tanner的修正。https://github.com/dustingooding/boost_python_ucharp_example

可能值得考虑将Python辅助函数作为ExampleClass.printBuffer方法公开给Python,该方法委托给c-ish ExampleClass::printBuffer成员函数。例如,这将允许Python用户调用:

import example
import struct
buf = struct.pack('BBBB', 0x41, 0x42, 0x43, 0x44)
e.printBuffer(buf)

而不是要求用户执行正确的ctypes铸造和尺寸


struct.pack()方法在Python2中返回str对象,在Python3中返回bytes对象,因此辅助c++函数需要用strbytes中的元素填充连续的内存块。boost::python::stl_input_iterator可以提供一种方便的方法,从Python对象(如strbytes)构造c++容器(如std::vector<char>)。唯一奇怪的是stl_input_iterator期望Python类型支持iterable协议,而str不支持。但是,内置的iter() Python方法可用于创建可迭代对象。

/// @brief Auxiliary function used to allow a Python iterable object with char
///        elements to be passed to ExampleClass.printBuffer().
void example_class_print_buffer_wrap(
  ExampleClass& self,
  boost::python::object py_buffer)
{
  namespace python = boost::python;
  // `str` objects do not implement the iterator protcol (__iter__),
  // but do implement the sequence protocol (__getitem__).  Use the
  // `iter()` builtin to create an iterator for the buffer.
  // >>> __builtins__.iter(py_buffer)
  python::object locals(python::borrowed(PyEval_GetLocals()));
  python::object py_iter = locals["__builtins__"].attr("iter");
  python::stl_input_iterator<char> begin(
     py_iter(py_buffer)), end;
  // Copy the py_buffer into a local buffer with known continguous memory.
  std::vector<char> buffer(begin, end);
  // Cast and delegate to the printBuffer member function.
  self.printBuffer(
    reinterpret_cast<unsigned char*>(&buffer[0]),
    buffer.size());
}
创建辅助函数后,只需要将其公开为ExampleClass.printBuffer方法:
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<ExampleClass>("ExampleClass")
    .def("printBuffer", &example_class_print_buffer_wrap)
    ;
}

下面是演示这种方法的完整示例:

#include <cstdio>
#include <vector>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
// Mocks...
/// @brief Legacy class that cannot be changed.
class ExampleClass
{
public:
  void printBuffer(unsigned char* buffer, unsigned int length)
  {
    for (unsigned int i = 0; i < length; ++i)
    {
      printf("%c", buffer[i]);
    }
    printf("n");
  }
};
/// @brief Auxiliary function used to allow a Python iterable object with char
///        elements to be passed to ExampleClass.printBuffer().
void example_class_print_buffer_wrap(
  ExampleClass& self,
  boost::python::object py_buffer)
{
  namespace python = boost::python;
  // `str` objects do not implement the iterator protcol (__iter__),
  // but do implement the sequence protocol (__getitem__).  Use the
  // `iter()` builtin to create an iterator for the buffer.
  // >>> __builtins__.iter(py_buffer)
  python::object locals(python::borrowed(PyEval_GetLocals()));
  python::object py_iter = locals["__builtins__"].attr("iter");
  python::stl_input_iterator<char> begin(
     py_iter(py_buffer)), end;
  // Copy the py_buffer into a local buffer with known continguous memory.
  std::vector<char> buffer(begin, end);
  // Cast and delegate to the printBuffer member function.
  self.printBuffer(
    reinterpret_cast<unsigned char*>(&buffer[0]),
    buffer.size());
}
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<ExampleClass>("ExampleClass")
    .def("printBuffer", &example_class_print_buffer_wrap)
    ;
}
互动用法:

>>> import example
>>> import struct
>>> buf = struct.pack('BBBB', 0x41, 0x42, 0x43, 0x44)
>>> print 'python:', buf
python: ABCD
>>> e = example.ExampleClass()
>>> e.printBuffer(buf)
ABCD

python文档在基本数据类型一章中列出了以下内容:

类ctypes。 c_char_p

指向以零结尾的字符串时,表示C char * 数据类型。用于一般字符指针也可以指向二进制数据,必须使用POINTER(c_char)。的构造函数接受整型地址或字符串。

显示您可能应该使用c_char_p类型。如果你使用POINTER()函数,这将是一个LP_c_char_p

类型

LP_c_ubyte   /* corresponds to */  unsigned char;
你应该用
LP_c_char_p    /* which corresponds to */    char *;

:我已经更正了上面的类型。另外:我不是python专家,所以我可能弄错了。