我对这个问题的首选解决方案是让 Python 开发人员看到的接口尽可能“Pythonic”。在这种情况下,将接受 pythonfile
对象作为您的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"
其核心部分基本上是调用从 Python对象PyFile_AsFile()
中获取 a 。有了它,我们就可以构建一个 boost 对象,它使用文件描述符作为适当的源/接收器。FILE*
file
唯一剩下的就是在调用发生后清理我们创建的对象(或者如果错误阻止调用发生)。
有了它,我们就可以在 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 我得到:
你好
Python
这是调用的相反顺序。我们也可以通过在构造 C++ 流之前强制调用我们类型映射file.flush()
中的 Python对象来解决这个问题:file
%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);
}
}
哪个具有所需的行为。
其他注意事项:
- 如果您有多线程代码并且 C++ 调用在没有 GIL 的情况下发生,您需要分别调用in
PyFile_IncUseCount
和PyFile_DecUseCount
freearg 类型映射,以确保在您仍在使用文件时没有任何东西可以关闭文件。
- 我假设如果给出的对象不是 a 则
PyFile_AsFile
返回- 文档似乎没有指定任何一种方式,所以你可以使用来确定。NULL
file
PyFile_Check
- 如果你想变得超级灵活,你可以接受来自 Python 的字符串,并使用/构造一个
std::ifstream
适当的值来决定在类型映射中采取哪些操作。PyString_Check
PyFile_Check
- 一些 C++ 标准库提供了一个
ifstream
/ofstream
构造函数,它将FILE*
, 作为扩展。如果你有其中之一,你可以使用它而不是依赖于 boost。