Remove out parameters from C# wrapper of C++ function in SWIG

Viewed 180

I am wrapping a C++ object and it has certain methods that return by value, and I would like the C# wrapper method to return a native type without having to allocate and de-allocate memory in C++-land. In this situation I think copying data into the return type is appropriate but I can't figure out how to do it in a nice way.

As a simple working example the following C++ code is a file parser. I only list the the std::pair<int,int> version() method but you can imagine other that return a small constant std::array, list, etc.

#include <utility>
#include <tuple>

class MyFile {
public:
  std::pair<int,int> version() const noexcept {
    return {version_major_, version_minor_};
  }
private:
 int version_major_;
 int version_minor_;
};

Using Out Arguments

I like this one the best, but I can't for the life of me figure out how to remove the out arguments for just the C# wrapper! C# is responsible for the memory and just passes it to C++ to fill in. This requires some C++ wrappers using %extend but that is ok.

I really want a final C# method with signature of public Tuple<int, int> version() 

SWIG code

%module parser
%include "typemaps.i"

%rename("$ignore", fullname=1) MyFile::version() const noexcept;
%extend MyFile {
  void version(int* major_out, int* minor_out) {
    std::tie(*major_out, *minor_out) = self->version();
  }
}

%typemap(ctype, out="void *") int* major_out, int* minor_out "int *"
%typemap(imtype, out="void") int* major_out, int* minor_out "out int"
%typemap(cstype) int* major_out, int* minor_out "out int"
%typemap(cstype) void MyFile::version "Tuple<int, int>"
%typemap(csin, pre="    int tmp_$csinput = 0;") int* major_out, int* minor_out "out tmp_$csinput"

// implment the C# function that returns the type specified above
%typemap(csout, noblock=1, excode=SWIGEXCODE) void MyFile::version
{
  $imcall;$excode
  major_out = tmp_major_out;
  minor_out = tmp_minor_out;
  return new Tuple<int, int>(tmp_major_out, tmp_minor_out);
}

Generated C++ wrapper

SWIGEXPORT void SWIGSTDCALL CSharp_parser_MyFile_version(void * jarg1, int * jarg2, int * jarg3) {
  MyFile *arg1 = (MyFile *) 0 ;
  int *arg2 = (int *) 0 ;
  int *arg3 = (int *) 0 ;
  
  arg1 = (MyFile *)jarg1; 
  arg2 = (int *)jarg2; 
  arg3 = (int *)jarg3; 
  MyFile_version(arg1,arg2,arg3);
}

Generated PInvoke Code

[global::System.Runtime.InteropServices.DllImport("parser", EntryPoint="CSharp_parser_MyFile_version")]
public static extern void MyFile_version(global::System.Runtime.InteropServices.HandleRef jarg1, out int jarg2, out int jarg3);

Generated C# Bindings

public Tuple<int, int> version(out int major_out, out int minor_out) {
  int tmp_major_out = 0;
  int tmp_minor_out = 0;
  parserPINVOKE.MyFile_version(swigCPtr, out tmp_major_out, out tmp_minor_out);
  major_out = tmp_major_out;
  minor_out = tmp_minor_out;
  return new Tuple<int, int>(tmp_major_out, tmp_minor_out);
}

Other methods

I have tried other methods but to keep the question short I won't post all the generated code here.

Returning pointer to a static array

this works, but requires static data for every method that I want to work this way. This doesn't scale. I could easily run into lifetime issues if I try to do this with member data that I assume will live long enough for C# to copy.

wrap the default generated code in a csout typemap

This also works but allocated and de-allocates a lot for a function just returning two ints! you end up with a C# wrapper something like:

public Tuple<int, int> version() {
  System.IntPtr cPtr = parserPINVOKE.MyFile_version(swigCPtr);
  pair_of_int tmp = new pair_of_int(cPtr, false);
  Tuple<int, int> ret = new Tuple<int, int>(tmp.first, tmp.second);
  tmp.Dispose();
  return ret;
}

Other SO Questions

a similar question is this one, but that deals with java. Java has JNI and Pyhton has a C interface that lets you create memory/object in the C code and return it... I don't see a way to do that in .NET withou C++/CLI (which SWIG isn't using).

0 Answers
Related