0

I have a C++ data class which stores a pointer to some heap allocated memory; I have no problem instantiating and consuming it from C# using a SWIG-generated wrapper. My problem arises when I try to call a function in the wrapped library which wants an array of those data classes; I hit segmentation faults every time. I have tried two approaches, listed below.

In a nutshell, my situation is

class DataClass
{
   char* _data;
   ...
}
void printDataClassArray(DataClass *toPrint, std::size_t size)
{ ... }

The function printDataClassArray gets wrapped by default as void printDataClassArray(DataClass toPrint, int size), I have tried two different approaches to overcome this. I post the full code below to assist the good soul who will try to help me: example.h contains the C++ library I am trying to wrap, example.i contains the SWIG interface code and consumer.cs is a C# function consuming the exported function and classes.

I have tried researching this, in particular I have followed this thread: Wrong values passed as parameter to C library using SWIG but I found no answer. I don't know whether I am missing something obvious, but nevertheless I think this ought to be part of the examples in SWIG, as it's a fairly common situation.

I have tried two approaches, namely using CSHARP_ARRAYS and using array_class, but none of them worked. See the SWIG code just below and the complete listing further down.

Using _CSHARP_ARRAYS_

 CSHARP_ARRAYS(DataClass, DataClass);
 %apply DataClass INPUT[]{ DataClass *toprint }
 %inline %{
 static void PrintAllDataArray(DataClass *toprint, std::size_t size)
 {
     PrintAllData(toprint, size);
 }
 %}

Using _array_class_

 // Methodology 2
 %array_class(DataClass, DataClassArray);
 %inline %{
   static void PrintAllVariants(DataClassArray va, std::size_t size)
   {
     PrintAllData(DataClassArray_cast(&va), size);
   }
 %}

example.h

#include <cstring> // for memcpy
#include <string> // for std::string
#include <sstream>
#include <iostream>
#include "assert.h"


inline char* copyString(const char* value, std::size_t size) {
  char* newstring = new char[size + 1];
  memcpy(newstring, value, size);
  newstring[size] = 0;
  std::cout << "Allocated string of " << size << " characters at " << reinterpret_cast<const void*>(newstring) << std::endl;
  return newstring;
}

struct DataEncapsulator {
    struct StringValue {
      const char* ptr;
      int size;
    } svalue;
};

inline DataEncapsulator copyData(DataEncapsulator source) {
  DataEncapsulator copy = source;
    copy.svalue.ptr = copyString(source.svalue.ptr,
      source.svalue.size);
  return copy;
}

inline void deleteString(const char* string)
{
  std::printf("Deallocating string  at %p\n", reinterpret_cast<const void*>(string));
  delete[] string;
}

class DataClass {
  DataEncapsulator impl_;
public:
  DataClass()
  {
    impl_.svalue.ptr = NULL;
    std::cout << "Empty constructor called on " << myAddress() << std::endl;
  }

  DataClass(const char* string)
  {
    std::cout << "Constructor called on " << myAddress() << std::endl;
    std::size_t len = std::strlen(string);
    impl_.svalue.ptr = copyString(string, len);
    impl_.svalue.size = len;
  }

  DataClass(const DataClass& other) {
    std::cout << "Copy constructor, copying from " << other.myAddress() << " to " << myAddress() << std::endl;
    impl_ = copyData(other.impl_);
  }

  ~DataClass()
  {
    std::cout << "Destructor called on " << myAddress() << std::endl;
    deleteString(impl_.svalue.ptr);
  }

  std::string myAddress()const
  {
    std::ostringstream strs;
    strs << reinterpret_cast<const void*>(this);
    return strs.str();
  }

  std::string toString() const
  {
    std::ostringstream strs;
    strs << "\"" << impl_.svalue.ptr << "\"  - string address: " << reinterpret_cast<const void*>(impl_.svalue.ptr);
    return strs.str();
  }

  const char* c_str() const
  {
    return impl_.svalue.ptr;
  }

  DataClass& operator=(const DataClass& other) {
    std::cout << "Assignment, copying from " << other.myAddress() <<  " to " << myAddress() << std::endl;
    deleteString(impl_.svalue.ptr);
    impl_ = copyData(other.impl_);
    return *this;
  }
};

static void PrintAllData(DataClass toprint[], std::size_t size)
{
  std::cout << "PrintAllVariants passed array of " << size << " variants at " << reinterpret_cast<const void*>(toprint) << std::endl;
  if (size > 0)
  {
    for (std::size_t i = 0; i < size; i++)
      std::cout <<  i << ": " << toprint[i].myAddress() << " - " << toprint[i].toString() << std::endl;
  }
}

example.i

%module example
%include "std_string.i"
%include "arrays_csharp.i"
%include "carrays.i"

#ifdef _WIN32
%include <windows.i>
#endif

%inline %{
  namespace std {
    typedef unsigned int size_t;
  }
%}
%ignore DataEncapsulator;
%ignore copyString;
%ignore copyData;
%ignore deleteString;

%{
#include "example.h"
%}
%rename(copy) *::operator=;
%include "example.h"


// Methodology 1
CSHARP_ARRAYS(DataClass, DataClass);
%apply DataClass INPUT[]{ DataClass *toprint }
%inline %{
static void PrintAllDataArray(DataClass *toprint, std::size_t size)
{
  PrintAllData(toprint, size);
}
%}

// Methodology 2
%array_class(DataClass, DataClassArray);
%inline %{
  static void PrintAllVariants(DataClassArray va, std::size_t size)
  {
    PrintAllData(DataClassArray_cast(&va), size);
  }
%}

consumer.cs

var a = new DataClass("first");
var b = new DataClass("second");
// Methodology 1
DataClassArray va = new DataClassArray(2);
va.setitem(0, a);
va.setitem(1, b);
example.PrintAllVariants(va, 2);


// Methodology 2
DataClass[] da = new DataClass[2];
da[0] = a;
da[1] = b;
example.PrintAllDataArray(da, 2);
Console.WriteLine();
Community
  • 1
  • 1
mapgccv
  • 9
  • 4

1 Answers1

0

After a lot of investigation, going to extremes to avoid the use of std::vector, an incredibly simple solution suggested by a friend: change the signature of the helper function in methodology 2 to pass the DataClassArray by reference, so from:

static void PrintAllVariants(DataClassArray va, std::size_t size)

to

static void PrintAllVariants(DataClassArray &va, std::size_t size)

Hope it helps.

mapgccv
  • 9
  • 4