I am using SWIG version 4.0.2 in a Windows Subsystem for Linux (WSL) Ubuntu distribution. The C++ class I want to wrap contains an array of bytes (i.e., each item in the array is of type uint8_t or unsigned char). The C++ class has a method whose output parameter is a double pointer, so that the caller can obtain a pointer to that array of bytes.
The simple C++ class to wrap is represented by the following header file.
example6.h
#pragma once
class Message
{
private:
uint8_t * m_data;
int m_size;
public:
Message( void )
: m_data( NULL )
, m_size( 0 )
{
m_data = new uint8_t[3]();
m_data[0] = 'A';
m_data[1] = 'B';
m_data[2] = 'C';
m_size = 3;
}
virtual ~Message( void )
{
delete[] m_data;
}
int package( uint8_t** buf )
{
*buf = NULL;
int size = 0;
if ( m_data )
{
*buf = m_data;
size = m_size;
}
return size;
}
};
The SWIG interface file I use to wrap the C++ code is the following.
example6.swg
%module example6
%{
#define SWIG_FILE_WITH_INIT
#include "example6.h"
%}
%typemap( in, numinputs=0 ) ( uint8_t** buf )
{
uint8_t** temp = 0x0;
$1 = temp;
}
%typemap( argout ) ( uint8_t** buf )
{
if ( *$1 )
{
$result = PyBytes_FromString( (char *)( *$1 ) );
free( *$1 );
}
}
%include "example6.h"
In the SWIG interface file, I am attempting to use appropriate %typemap(in) and %typemap(argout) directives, based on breadcrumbs from several examples. I have several concerns about them and do not know yet if they will work. However, those are not the first obstacle.
I am not certain what type of Python variable to provide as the argument for the Message.package() method when I call it. I think I want the variable to be of type bytes. Or, maybe I need additional %typemap directives to correctly convert the Python bytes variable to a C++ uint8_t** variable?
The following is a demonstration of the problem.
finch@laptop:~/work/swig_example$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example6
>>> m = example6.Message()
>>> b = bytes()
>>> b
b''
>>> m.package( b )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/finch/work/swig_example/example6.py", line 73, in package
return _example6.Message_package(self, buf)
TypeError: in method 'Message_package', argument 2 of type 'uint8_t **'
The TypeError exception is raised in the SWIG-generated example6_wrap.cxx code. The exception happens well before it gets a chance to execute the C++ snippet that the %typemap(argout) directive above injects into it.
example6_wrap.cxx
SWIGINTERN PyObject *_wrap_Message_package(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
Message *arg1 = (Message *) 0 ;
uint8_t **arg2 = (uint8_t **) 0 ;
void *argp1 = 0 ;
int res1 = 0 ;
void *argp2 = 0 ;
int res2 = 0 ;
PyObject *swig_obj[2] ;
int result;
if (!SWIG_Python_UnpackTuple(args, "Message_package", 2, 2, swig_obj)) SWIG_fail;
res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_Message, 0 | 0 );
if (!SWIG_IsOK(res1)) {
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Message_package" "', argument " "1"" of type '" "Message *""'");
}
arg1 = reinterpret_cast< Message * >(argp1);
res2 = SWIG_ConvertPtr(swig_obj[1], &argp2,SWIGTYPE_p_p_uint8_t, 0 | 0 );
if (!SWIG_IsOK(res2)) {
//*** Finch temp: This is where the TypeError exception will be raised.
SWIG_exception_fail(SWIG_ArgError(res2), "in method '" "Message_package" "', argument " "2"" of type '" "uint8_t **""'");
}
arg2 = reinterpret_cast< uint8_t ** >(argp2);
result = (int)(arg1)->package(arg2);
resultobj = SWIG_From_int(static_cast< int >(result));
//*** Finch temp: This is the snippet that the %typemap(argout) directive injects.
{
if ( *arg2 )
{
resultobj = PyBytes_FromString( (char *)( *arg2 ) );
free( *arg2 );
}
}
//***
return resultobj;
fail:
return NULL;
}
Problem 1
What typemap will help me convert a Python bytes variable to a C++ uint8_t** variable.
Problem 2
PyBytes_FromString() expects to be given a char const * variable. But the underlying message data in the real C++ library I am wrapping is uint8_t; I am stuck with that. I worry that blindly recasting will cause some byte values to be wrong. Is there another PyBytes_something() function I have not seen?
If not, is it appropriate for the %typemap(argout) directive to have a lot more involved code in it, such as iterating the byte buffer and manually copying it to ... something else?
I have been searching for and piecing together examples from several places, including:
- How to apply a SWIG typemap for a double pointer struct argument
- SWIG Python - wrapping a function that expects a double pointer to a struct
- SWIG return bytes instead of string with typemap
- swig typemap for python: input and output arrays
- How do I apply a SWIG typemap to _only_ a specific function?
- SWIG 'cstring.i' Python return byte type instead of string type
- https://docs.python.org/3.10/c-api/bytearray.html
- https://docs.python.org/3.10/c-api/bytes.html
PyBytes_FromStringAndSizeinstead, asPyBtes_FromStringstops at the first NUL (0) byte. I would not worry too much about mismatches between char and uint8_t.