简体   繁体   English

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

[英]How do I pass a pre-populated “unsigned char*” buffer to a C++ method using boost.python?

I have a C++ class with a member function that takes an unsigned char* buffer and an unsigned int length as arguments and operates on them. 我有一个带有成员函数的C ++类,它使用unsigned char * buffer和unsigned int length作为参数并对它们进行操作。 I've wrapped this class with Boost::Python and would like to pass a pre-populated buffer to the class from a Python script. 我用Boost :: Python包装了这个类,并希望从Python脚本中将预先填充的缓冲区传递给该类。 The Python-side buffer is created with struct.pack. Python端缓冲区是用struct.pack创建的。 I can't figure out how to make the argument type match and keep getting Boost.Python.ArgumentError. 我无法弄清楚如何使参数类型匹配并继续获取Boost.Python.ArgumentError。

include/Example.h 包括/ 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 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 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)
    ;
}

scripts/example.py 脚本/ 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 (incomplete) 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)

Output 产量

$ ./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)

I've tried a number of different approaches (passing 'buf' directly, passing 'buf' as a ctypes.c_char_p, creating a ctypes.ubyte array and populating it with the contents of 'buf' and passing it), but none seem to work. 我已经尝试了许多不同的方法(直接传递'buf',将'buf'作为ctypes.c_char_p传递,创建一个ctypes.ubyte数组并用'buf'的内容填充它并传递它),但似乎没有上班。

I don't understand why 'LP_c_ubyte' and 'unsigned char*' don't match. 我不明白为什么'LP_c_ubyte'和'unsigned char *'不匹配。

EDIT 编辑

Here's a Github project with a ready-to-go codebase. 这是一个带有现成代码库的Github项目。 Feel free to use this. 随意使用它。 I've added @Tanner's fix. 我添加了@ Tanner的修复程序。 https://github.com/dustingooding/boost_python_ucharp_example https://github.com/dustingooding/boost_python_ucharp_example

It may be worth considering exposing a Pythonic auxiliary function as the ExampleClass.printBuffer method to Python, that delegates to the c-ish ExampleClass::printBuffer member function. 可能值得考虑将Pythonic辅助函数作为ExampleClass.printBuffer方法暴露给Python,它委托给c-ish ExampleClass::printBuffer成员函数。 For instance, this would allow the Python users to invoke: 例如,这将允许Python用户调用:

import example
import struct

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

Rather than requiring the user to perform the correct ctypes cast and sizing. 而不是要求用户执行正确的ctypes演员和大小调整。


The struct.pack() method returns a str object in Python2 and a bytes object in Python3, so the auxiliary C++ function would need to populate a continuous block of memory with the elements of from either str or bytes . struct.pack()方法返回struct.pack()str对象和Python3中的bytes对象,因此辅助C ++函数需要使用strbytes的元素填充连续的内存块。 The boost::python::stl_input_iterator can provide a convenient way to construct C++ containers, such as std::vector<char> , from a Python object, such as str or bytes . boost::python::stl_input_iterator可以提供一种从Python对象(如strbytes构造C ++容器(如std::vector<char>的便捷方法。 The only oddity is that stl_input_iterator expects the Python type to support the iterable protocol, which str does not do. 唯一奇怪的是stl_input_iterator期望Python类型支持可迭代协议,而str不支持。 However, the builtin iter() Python method can be used to create an iterable object. 但是,builtin 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());
}

With the auxiliary function created, one just needs to expose it as the ExampleClass.printBuffer method: 创建辅助函数后,只需将其公开为ExampleClass.printBuffer方法:

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<ExampleClass>("ExampleClass")
    .def("printBuffer", &example_class_print_buffer_wrap)
    ;
}

Here is a complete example demonstrating this approach: 以下是演示此方法的完整示例:

#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)
    ;
}

Interactive usage: 互动用法:

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

The python documentation lists the following in the chapter Fundamental Data Types : python文档基本数据类型一章中列出了以下内容:

class ctypes. ctypes。 c_char_p c_char_p

Represents the C char * datatype when it points to a zero-terminated string. 当它指向以零结尾的字符串时表示C char *数据类型。 For a general character pointer that may also point to binary data, POINTER(c_char) must be used. 对于也可能指向二进制数据的通用字符指针,必须使用POINTER(c_char) The constructor accepts an integer address, or a string. 构造函数接受整数地址或字符串。

shows that you should probably use a c_char_p type. 表明您应该使用c_char_p类型。 If you use the POINTER() functions this will be a LP_c_char_p . 如果使用POINTER()函数,则这将是LP_c_char_p

The type 类型

LP_c_ubyte   /* corresponds to */  unsigned char;

you should probably use 你可能应该使用

LP_c_char_p    /* which corresponds to */    char *;

Update : I've corrected the types above. 更新 :我已经纠正了上面的类型。 Also: I'm not a python expert, so I might have it wrong. 另外:我不是python专家,所以我可能错了。 There is also this answer . 还有这个答案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM