Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SWIG: "out" typemap for structure field needs to access another field of the same structure

Tags:

c

swig

I am attempting to wrap a C library (which I did not write, and whose interfaces cannot be changed) using SWIG. It's mostly straightforward, but there's one field of one struct that's giving me trouble. The relevant struct definition looks like this:

struct Token {
    const char *buffer;
    const char *word;
    unsigned short wordlen;
    // ... other fields ...
};

buffer is a normal C string and should be exposed normally (but immutably). word is the problem field. It is a pointer to somewhere within the buffer string, and is meant to be understood as a string of length wordlen. I want to expose this to high-level languages as a normal read-only string, so they don't always have to be taking a slice.

I think the way to handle this is with an "out" typemap specifically for Token::word, something like this:

struct Token {
    %typemap (out) const char *word {
        $result = SWIG_FromCharPtrAndSize($1, ?wordlen?);
    }
}

and this is where I got stuck: How do I access the wordlen field of the parent structure from this typemap?

Or if there's a better way to handle this entire issue, please tell me about that instead.

like image 955
zwol Avatar asked Dec 24 '18 16:12

zwol


1 Answers

Unfortunately, it looks like SWIG has no support for mapping multiple structure members at the same time. Inspecting the generated output we learn that (arg1) points to the input structure. Thus, we have to:

  1. Make the word immutable, so that the set wrapper is not generated.
  2. Import the SWIG_FromCharPtrAndSize fragment - it's not available by default.
  3. Map word using SWIG_FromCharPtrAndSize as you wished, referring to (arg1)->wordlen.
  4. Skip wordlen, so that it's not mapped (either by %ignore-ing it, or not providing it in the struct visible to SWIG).

The following is a complete example. First, the header:

// main.h
#pragma once

struct Token {
  const char *word;
  unsigned short wordlen;
};

struct Token *make_token(void);
extern char *word_check;

And the SWIG module - note that we use the header verbatim, only overriding the definition of struct Token:

// token_mod.i
%module token_mod
%{#include "main.h"%}
%ignore Token;
%include "main.h"
%rename("%s") Token;

struct Token {
  %immutable word;
  %typemap (out, fragment="SWIG_FromCharPtrAndSize") const char *word {
    $result = SWIG_FromCharPtrAndSize($1, (arg1)->wordlen);
  }
  const char *word;
  %typemap (out) const char *word;
};

The demo code that uses Python to check that things work:

// https://github.com/KubaO/stackoverflown/tree/master/questions/swig-pair-53915787
#include <assert.h>
#include <stdlib.h>
#include <Python.h>
#include "main.h"

struct Token *make_token(void) {
  struct Token *r = malloc(sizeof(struct Token));
  r->word = "1234";
  r->wordlen = 2;
  return r;
}

char *word_check;

#if PY_VERSION_HEX >= 0x03000000
#  define SWIG_init    PyInit__token_mod
PyObject*
#else
#  define SWIG_init    init_token_mod
void
#endif
SWIG_init(void);

int main()
{
   PyImport_AppendInittab("_token_mod", SWIG_init);
   Py_Initialize();
   PyRun_SimpleString(
            "import sys\n"
            "sys.path.append('.')\n"
            "import token_mod\n"
            "from token_mod import *\n"
            "token = make_token()\n"
            "cvar.word_check = token.word\n");
   assert(word_check && strcmp(word_check, "12") == 0);
   Py_Finalize();
   return 0;
}

Finally, the CMakeLists.txt that makes the demo - it can be used with Python 2.7 or 3.x. Note: To switch Python versions, the build directory must be wiped (or at least the cmake caches in it have to be wiped).

cmake_minimum_required(VERSION 3.2)

set(Python_ADDITIONAL_VERSIONS 3.6)
project(swig-pair)
find_package(SWIG 3.0 REQUIRED)
find_package(PythonLibs 3.6 REQUIRED)
include(UseSwig)

SWIG_MODULE_INITIALIZE(${PROJECT_NAME} python)
SWIG_ADD_SOURCE_TO_MODULE(${PROJECT_NAME} swig_generated_sources "token_mod.i")
add_executable(${PROJECT_NAME} "main.c" ${swig_generated_sources})
target_include_directories(${PROJECT_NAME} PRIVATE ${PYTHON_INCLUDE_DIRS} ".")
target_link_libraries(${PROJECT_NAME} PRIVATE ${PYTHON_LIBRARIES})
like image 52
Kuba hasn't forgotten Monica Avatar answered Nov 06 '22 18:11

Kuba hasn't forgotten Monica