简体   繁体   中英

Passing python StringIO to C++ stringstream using swig

I've got some python code that generates a StringIO variable. I'd like to pass this variable to a C++ function using a stringstream parameter (on the assumption C++ stringstream is the closest match to a python StringIO). Is there a simple way using swig to do this translation?

I see in the swig modules there is a std_sstream.i which contains some stringstream code, but I can't find any references to it in the swig documentation, or any examples of its use on a web search.

To try and make it a little clearer, I'll include some very simple code below. Here's test.cpp which is fixed and I can't change:

#include <iostream>
#include <sstream>

#include "test.h"

using namespace std;

void StTest(stringstream &ss)
{
    cout << ss << endl;
}

Here's test.h which is also fixed:

#include <sstream>

using namespace std;

void StTest(stringstream &ss);

Here's the python code which I can edit and which calls the C++ StTest function (we'll call this test_test.py):

#!/usr/bin/env python

import StringIO
import test

ss = StringIO.StringIO()
ss.write("Hello")

# Maybe some thing here to convert ss from StringIO to stringstream

test.StTest(ss)

So I'm looking for something that can do the conversion from StringIO to stringstream in the commented line above.

Here's my initial dig at the test.i file for swig do its stuff:

/* test.i */
%module test

%{
#include "test.h"
%}

%include "std_sstream.i"
%include "test.h"

I've included "std_sstream.i" in test.i as this is what ought to be used, I assume. Do I need to add something to this file to get it to do something?

Currently the error report when running test_test.py is:

TypeError: in method 'StTest', argument 1 of type 'stringstream &'

Why not just call PyArg_ParseTuple with a string, which you get from calling StringIO.getvalue() ?

With something like this ?

const char* cstring;
if (!PyArg_ParseTuple(args,"s", &cstring)) //Note the &
        return NULL;
std::string stdstring = cstring;
std::stringstream buffer(stdstring);

Also see the C API for cStringIO here .

After the additional requirements, I attempted to discover a solution using SWIG within the fixed constraints.

The first possible solution was the inline or extends solution (extend StTest to take a string and then have the extension make a stringstream and call StTest), but as the provided example has only one method and no classes, this didn't meet the constraints.

The eventual interface file looked like this.

/* test.i */
%module test

%{
#include "test.h"
#include <sstream>
#include <iostream>
%}
using namespace std;
%include "std_sstream.i"
%include "test.h"

%include <typemaps.i>

%typemap(in) std::stringstream & (char *buf = 0, size_t size = 0, int alloc = 0, std::stringstream stream)
{
  if (SWIG_AsCharPtrAndSize($input, &buf, &size, &alloc) != SWIG_OK)
  {
    %argument_fail(SWIG_TypeError, "(TYPEMAP, SIZE)", $symname, $argnum);
  }

  stream.write(buf, size - 1);

  $1 = &stream;
}

Note that SWIG ignored the typemap in the interface file above, not sure why. It managed to to create stringstream objects in Python, but they wouldn't be passed to your StTest, giving the same type error because the type of the objects wasn't the type of the stringstream & (also the mapped swig type didn't match the types generated by std_sstream.i, probably because it doesn't contain deferencing typemaps) .

Next was the implementation of the approach I suggested overriding SWIG's wrapper, the resulting wrapped function was as follows. (Not going to include the entire wrapper file as it is over 18000 lines long)

SWIGINTERN PyObject *_wrap_StTest(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
  PyObject *resultobj = 0;
  void *argp1 = 0 ;
  int res1 = 0 ;
  PyObject * obj0 = 0 ;
  int argc;
  PyObject *argv[3];
  int ii;
  char *cstring;
  std::stringstream testbuffer;
  if (!PyTuple_Check(args)) SWIG_fail;
  argc = args ? (int)PyObject_Length(args) : 0;
  for (ii = 0; (ii < 2) && (ii < argc); ii++) {
    argv[ii] = PyTuple_GET_ITEM(args,ii);
  }
  if (argc == 1) {

  if (!PyArg_ParseTuple(args,"s",&cstring)) 
SWIG_fail;
  std::string teststring = cstring;
  testbuffer.str(cstring);
  std::cout << "the string in the stream: " << testbuffer.str() << std::endl;
  }  

  StTest(testbuffer);
  resultobj = SWIG_Py_Void();
  return resultobj;
fail:
  return NULL;
}   

The string in the stream line prints correctly, however passing the stringstream to StTest function prints the location in memory of the stringstream. This is expected because the stringstream isn't bound like in accepted answer of the following Stack Overflow Question .

I assume StTest(testbuffer); could be replaced with a version using boost's bind, and it would work as required, but have been able to verify.

References that were helpful included

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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