开发者

SWIG: C++ to C#, pointer to pointer marshalling

I have some legacy code I want to port to C#. I cannot modify the C++ code, I just have to make do with what I'm given.

So, the situation. I'm using SwIG, and I came across this function:

void MarshalMe(int iNum, FooClass** ioFooClassArray);

If I ran SWIG over this, it wouldn't know what to do with the array, so it will create a SWIGTYPE_p_pFooClass. Fair enough! C开发者_如何转开发# code for this would look like

void MarshalMe(int iNum, SWIGTYPE_p_p_FooClass ioFooClassArray); // Not ideal!

There are some techniques for marshalling this kind of code correctly, so I tried a few of them:

%typemap(ctype)   FooClass** "FooClass**"
%typemap(cstype)  FooClass** "FooClass[]"
%typemap(imtype, inattributes="[In, Out, MarshalAs(UnmanagedType.LPArray)]") FooClass** "FooClass[]"
%typemap(csin)    FooClass** "$csinput"
%typemap(in)      FooClass** "$1 = $input;"
%typemap(freearg) FooClass** ""
%typemap(argout)  FooClass** ""

This effectively creates a nicer signature:

void MarshalMe(int iNum, FooClass[] ioFooClassArray); // Looks good! Would it work?

However, when I try to run it, I get the following error:

{"Exception of type 'System.ExecutionEngineException' was thrown."}

Any ideas about the actual typemap?


The exception tells you that the function has corrupted the garbage collected heap. It writing past the end of the array. If it is actually a FooClass[], big question, then you'll have to create the array first:

 FooClass[] array = new FooClass[666];
 MarshalMe(42, array);

Which assumes that the function will fill the array with FooClass objects. The size of the array really matters here, you'll have to have some kind of idea how many elements you'll get back. That can only work reliable if "iNum" is an argument that says how long the array is. Pass array.Length.

It could also mean that the function will create the array itself and return a pointer to it. You're really screwed if that's the case, you cannot release the memory for the array.


[Swig] Java: Another way to pass pointer-to-pointer

  1. I had a similar problem with a C-function (API) which returned a pointer-to-pointer as an input argument. I was trying to call the C-function from JAVA and I had no way modify the API.

The API.h header file contained:

extern int ReadMessage(HEADER **hdr);

The original C-call looked like:

HEADER *hdr;
int status;
status = ReadMessage(&hdr);

The function of the API was to store data at the memory location specified by the pointer-to-pointer.

  1. I tried to use SWIG to create the appropriate interface file. SWIG.i created the file SWIGTYPE_p_p_header.java from API.h. The problem is the SWIGTYPE_p_p_header constructor initialized swigCPtr to 0.

The JAVA call looked like:

SWIGTYPE_p_p_header hdr = new SWIGTYPE_p_p_header();
status = SWIG.ReadMessage(hdr);

But when I called the API from JAVA the ptr was always 0.

  1. I finally gave up passing the pointer-to-pointer as an input argument. Instead I defined another C-function in SWIG.i to return the pointer-to-pointer in a return value. I thought it was a Kludge ... but it worked!

You may want to try this:

SWIG.i looks like:

// return pointer-to-pointer
%inline %{
   HEADER *ReadMessageHelper() {
   HEADER *hdr;
   int returnValue;
   returnValue = ReadMessage(&hdr);
   if (returnValue!= 1) hdr = NULL;
   return hdr;
}%}
  1. The inline function above could leak memory as Java won't take ownership of the memory created by ReadMessageHelper, since the HEADER instance iscreated on the heap.

The fix for the memory leak is to define ReadMessageHelper as a newobject in order for Java to take control of the memory.

%newobject ReadMessageHelper();

JAVA call now would look like:
    HEADER hdr;
    hdr = SWIG.ReadMessageHelper();
  1. If you are lucky, as I was, you may have another API available to release the message buffer. In which case, you wouldn’t have to do step 4.

  2. William Fulton, the SWIG guru, had this to say about the approach above:

“I wouldn't see the helper function as a kludge, more the simplest solution to a tricky problem. Consider what the equivalent pure 100% Java code would be for ReadMessage(). I don't think there is an equivalent as Java classes are passed by reference and there is no such thing as a reference to a reference, or pointer to a pointer in Java. In the C function you have, a HEADER instances is created by ReadMessage and passed back to the caller. I don't see how one can do the equivalent in Java without providing some wrapper class around HEADER and passing the wrapper to the ReadMessage function. At the end of the day, ReadMessage returns a newly created HEADER and the Java way of returning newly created objects is to return it in the return value, not via a parameter.”


Using SWIG typemap to pass pointer-to-pointer:

Here is another approach using typemaps. It is targetting Perl, not Java, but the concepts are the same. And I finally managed to get it working using typemaps and no helper functions:

For this function:

typedef void *    MyType;
int getblock( int a, int b, MyType *block );

I have 2 typemaps:

%typemap(perl5, in, numinputs=0) void ** data( void * scrap )
{
    $1 = &scrap;
}

%typemap(perl5, argout) void ** data
{
    SV* tempsv = sv_newmortal();
    if ( argvi >= items ) EXTEND(sp,1);
    SWIG_MakePtr( tempsv, (void *)*$1, $descriptor(void *), 0);
    $result = tempsv;
    argvi++;
}

And the function is defined as:

int getblock( int a, int b, void ** data ); 

In my swig .i file. Now, this passes back an opaque pointer in the argout typemap, becaust that's what useful for this particular situation, however, you could replace the SWIG_MakePtr line with stuff to actually do stuff with the data in the pointer if you wanted to. Also, when I want to pass the pointer into a function, I have a typemap that looks like this:

%typemap(perl5, in) void * data
{
    if ( !(SvROK($input)) croak( "Not a reference...\n" );

    if ( SWIG_ConvertPtr($input, (void **) &$1, $1_descriptor, 0 ) == -1 )
        croak( "Couldn't convert $1 to $1_descriptor\n");
}

And the function is defined as:

int useblock( void * data );

In my swig .i file.

Obviously, this is all perl, but should map pretty directly to Java as far as the typemap architecture goes. Hope it helps...


I managed to solve this using Swig Managed Arrays and Pinning documentation.

Given a function in C++

void myArrayCopy(int *sourceArray, int *targetArray, int nitems);

Declare the method as unsafe in C#

%csmethodmodifiers myArrayCopy "public unsafe";

Add the appropriate typemaps

%include "arrays_csharp.i"
%apply int FIXED[] {int *sourceArray} 
%apply int FIXED[] {int *targetArray}

As a result, we get the following method in the module class:

public unsafe static void myArrayCopy(int[] sourceArray, int[] targetArray, int nitems) 
{
    fixed ( int *swig_ptrTo_sourceArray = sourceArray ) 
    {
        fixed ( int *swig_ptrTo_targetArray = targetArray ) 
        {
            examplePINVOKE.myArrayCopy((IntPtr)swig_ptrTo_sourceArray,            

                (IntPtr)swig_ptrTo_targetArray,
                nitems);
        }
    }
}

In practice this might differ a little with FooClass** but Swig does support direct pointer to pointer marshalling, which also avoids a copy as well so can be considered better performance

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜