Search code examples
pythonc++pointersswig

Dealing with in/out string parameters in Swig Python


Maybe someone know, how to rewrite getVersion to get in Python version and legacyVersion as a result of function instead of passing them as in/out parameters

   class DeviceInterface {
   public:
      virtual uint32_t getVersion(uint8_t* version, uint8_t* legacyVersion ) = 0;
   };

For now I create additional function in SWIG inteface file, but current implementation returns only one parameter instead of two.

%ignore DeviceInterface::getVersion( uint8_t* version, uint8_t* legacyVersion );

%extend DeviceInterface {
    virtual char * getVersion() {
        static uint8_t _v[200];
        static uint8_t _lv[200];
        $self->getVersion(_v, _lv);
        return (char *)_lv;
    }
};

UPDATE

Now I use @ignore and @extend features. But I guess there is more elegant way. Here is my working SWIG interface snippet:

%extend DeviceInterface {
    virtual PyObject *getVersion() {
        static uint8_t _v[200];
        static uint8_t _lv[200];
        uint32_t libCode = $self->getVersion(_v, _lv);
        return Py_BuildValue("(l,s,s)", libCode, (char *)_v, (char *)_lv);
    }
};

Solution

  • You can define your own typemaps to handle the output parameters. In this case, the typemaps treat all uint32_t* parameters as 200-byte output buffers. This is a minimal example without error checking.

    %module test
    
    %include <stdint.i>
    
    // Require no input parameter.  Just allocate a fixed-sized buffer.
    %typemap(in,numinputs=0) uint8_t* %{
        $1 = new uint8_t[200];
    %}
    
    // Free the temporary buffer when done converting the argument
    %typemap(freearg) uint8_t* %{
        delete [] $1;
    %}
    
    // On output, convert the buffer to a Python byte string
    %typemap(argout) uint8_t* %{
        $result = SWIG_Python_AppendOutput($result, PyBytes_FromString(reinterpret_cast<char*>($1)));
    %}
    
    // Example implementation
    %inline %{
    #include <stdint.h>
    class DeviceInterface {
    public:
        virtual uint32_t getVersion(uint8_t* version, uint8_t* legacyVersion ) {
            strcpy(reinterpret_cast<char*>(version),"1.0.0");
            strcpy(reinterpret_cast<char*>(legacyVersion),"2.0.0");
            return 0;
        }
    };
    %}
    

    Output:

    >>> import test
    >>> d=test.DeviceInterface()
    >>> d.getVersion()
    [0, b'1.0.0', b'2.0.0']
    

    Note the output is a concatenation of the return value and two output parameters.