我对这个问题的首选解决方案是让 Python 开发人员看到的界面尽可能“Pythonic”。在这种情况下,将接受 python file 对象作为您的 ostream 和 istream 参数。
为了实现这一点,我们必须编写一个类型映射来设置每个映射。
我编写了以下头文件来演示这一点:
#ifndef TEST_HH
#define TEST_HH
#include <iosfwd>
void readFrom(std::istream& istr);
void writeTo(std::ostream& ostr);
#endif
我为测试编写了一个虚拟实现:
#include <iostream>
#include <cassert>
#include "test.hh"
void readFrom(std::istream& istr) {
assert(istr.good());
std::cout << istr.rdbuf() << "\n";
}
void writeTo(std::ostream& ostr) {
assert(ostr.good());
ostr << "Hello" << std::endl;
assert(ostr.good());
}
有了它,我就可以使用以下方法成功包装它:
%module test
%{
#include <stdio.h>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
namespace io = boost::iostreams;
typedef io::stream_buffer<io::file_descriptor_sink> boost_ofdstream;
typedef io::stream_buffer<io::file_descriptor_source> boost_ifdstream;
%}
%typemap(in) std::ostream& (boost_ofdstream *stream=NULL) {
int fd = -1;
#if PY_VERSION_HEX >= 0x03000000
fd = PyObject_AsFileDescriptor($input);
#else
FILE *f=PyFile_AsFile($input); // Verify the semantics of this
if (f) fd = fileno(f);
#endif
if (fd < 0) {
SWIG_Error(SWIG_TypeError, "File object expected.");
SWIG_fail;
}
else {
// If threaded incrment the use count
stream = new boost_ofdstream(fd, io::never_close_handle);
$1 = new std::ostream(stream);
}
}
%typemap(in) std::istream& (boost_ifdstream *stream=NULL) {
int fd = -1;
#if PY_VERSION_HEX >= 0x03000000
fd = PyObject_AsFileDescriptor($input);
#else
FILE *f=PyFile_AsFile($input); // Verify the semantics of this
if (f) fd = fileno(f);
#endif
if (fd < 0) {
SWIG_Error(SWIG_TypeError, "File object expected.");
SWIG_fail;
}
else {
stream = new boost_ifdstream(fd, io::never_close_handle);
$1 = new std::istream(stream);
}
}
%typemap(freearg) std::ostream& {
delete $1;
delete stream$argnum;
}
%typemap(freearg) std::istream& {
delete $1;
delete stream$argnum;
}
%{
#include "test.hh"
%}
%include "test.hh"
它的核心部分基本上是调用PyFile_AsFile() 从Python 的file 对象中获取FILE*。有了它,我们就可以构建一个 boost 对象,它使用文件描述符作为适当的源/接收器。
剩下的唯一事情就是在调用发生后清理我们创建的对象(或者如果错误阻止调用发生)。
有了它,我们就可以在 Python 中按预期使用它了:
import test
outf=open("out.txt", "w")
inf=open("in.txt", "r")
outf.write("Python\n");
test.writeTo(outf)
test.readFrom(inf)
outf.close()
inf.close()
请注意,缓冲语义可能不会产生您预期的结果,例如在 out.txt 我得到:
你好
蟒蛇
这是调用的相反顺序。我们也可以通过在构造 C++ 流之前强制调用我们类型映射中的 Python file 对象上的 file.flush() 来解决这个问题:
%typemap(in) std::ostream& (boost_ofdstream *stream=NULL) {
PyObject_CallMethod($input, "flush", NULL);
FILE *f=PyFile_AsFile($input); // Verify the semantics of this
if (!f) {
SWIG_Error(SWIG_TypeError, "File object expected.");
SWIG_fail;
}
else {
// If threaded incrment the use count
stream = new boost_ofdstream(fileno(f), io::never_close_handle);
$1 = new std::ostream(stream);
}
}
哪个具有所需的行为。
其他说明:
- 如果您有多线程代码并且在没有 GIL 的情况下进行 C++ 调用,您需要分别在 in 和 freearg 类型映射中调用
PyFile_IncUseCount 和 PyFile_DecUseCount 以确保在您执行时没有任何东西可以关闭文件'仍在使用它。
- 我假设
PyFile_AsFile 返回 NULL 如果它给出的对象不是 file - 文档似乎没有指定任何一种方式,所以你可以使用 PyFile_Check 确定.
- 如果您想要超级灵活,您可以接受来自 Python 的字符串,并根据需要使用
PyString_Check/PyFile_Check 构造一个 std::ifstream 来决定在类型映射中采取哪些操作。
- 一些 C++ 标准库提供了一个
ifstream/ofstream 构造函数,它采用 FILE* 作为扩展。如果您有其中之一,则可以使用它而不是依赖 boost。