10

I would like to marshal a C struct with a variable-length array back to C# but so far I can't get anything better than a pointer-to-struct representation and a pointer to float.

Unmanaged representation:

typedef float        smpl_t;

typedef struct {
  uint_t length;  /**< length of buffer */
  smpl_t *data;   /**< data vector of length ::fvec_t.length */
} fvec_t;

Managed representation:

[StructLayout(LayoutKind.Sequential)]
public unsafe struct fvec_t1
{
    public uint length;

    public float* data;
}

[DllImport("libaubio-4.dll", EntryPoint = "new_fvec", PreserveSig = true, CharSet = CharSet.Ansi,
    CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe fvec_t1* new_fvec1(uint length);

What I would like is to have a .NET style array, where data would be float[] but if I do change the struct to the form below I do get Cannot take the address of, get the size of, or declare a pointer to a managed type in the external function above.

[StructLayout(LayoutKind.Sequential)]
public unsafe struct fvec_t1
{
    public uint length;

    public float[] data;
}

Apparently, it is not possible to a have a variable-length array marshalled back as-is, is this correct or is it there still a way to achieve this ?

3
  • You can do it easily enough passing an array as a parameter. Any reason why you don't do that? Why are you using unsafe? Commented Feb 17, 2014 at 22:27
  • Can you elaborate on this ? The thing is when I specify MarshalAs.Struct the call works but the returned struct is garbage (length is incorrect) so I guess it didn't work correctly. So for now, the only thing working is to return a pointer of struct and manually access the items in data. About unsafe statement, I've just forgot to remove it. Commented Feb 18, 2014 at 13:07
  • You cannot have the array inside a struct and have the marshaller do the work. But you can pass the array as a pointer and get it marshalled. Commented Feb 18, 2014 at 13:14

2 Answers 2

9

short answer you can't marshal variable length array as an array , because Without knowing the size, the interop marshalling service cannot marshal the array elements

but if you know the size it will be like below:

int arr[15]

you will be able to marshal it like this:

[MarshalAs(UnmanagedType.LPArray, SizeConst=15)] int[] arr

if you don't know the length of the array and this is what you want you can convert it to intprt and deal with inptr but first you need to create 2 structs

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct fvec_t1
{
    public uint whatever;

    public int[] data;
}

the other one like below:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct fvec_t2{
    public uint whatever;
}

create a function to initialize the array like below

private static int[] ReturnIntArray()
{
    int [] myInt = new int[30];

    for (int i = 0; i < myInt.length; i++)
    {
        myInt[i] = i + 1;
    }

    return myInt;
}

instantiate the first struct

fvec_t1 instance = new fvec_t1();
instance.whatever=10;
instance.data= ReturnIntArray();

instantiate the second struct

fvec_t2 instance1 = new fvec_t2();

instance1.whatever = instance.whatever

dynamically allocate space for fvec_t2 struct with extended space for data array

IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(fvec_t2)) + Instance.data.Length);

Transfer the existing field values of fvec_t2 to memory space pointed to by ptr

Marshal.StructureToPtr(instance1, ptr, true);

Calculate the offset of data array field which should be at the end of an fvec_t2 struct

int offset = Marshal.SizeOf(typeof(fvec_t2));

get memory address of data array field based on the offset.

IntPtr address = new IntPtr(ptr.ToInt32() + offset);

copy data to ptr

Marshal.Copy(instance.data, 0, address, instance.data.Length);

do the call

bool success = dllfunction(ptr);

Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;
Sign up to request clarification or add additional context in comments.

6 Comments

i Read it and i said that you cant do that :). if you dont like my answe i can delete it
If I could know the size in advance then I wouldn't have asked the question :-)
you can convert it to intprt and deal with inptr, and once again you cant use float[] data for the reason i mentioned above . but i really didnt get it why you donw voted my answer :)
@Aybe check my answer now hope that will help :D
Why do you allocate Marshal.SizeOf(typeof(fvec_t2)) + Instance.data.Length? int type is not 1 byte length.
|
-4

In the above case I'd definitely use the SizeParamIndex.

[StructLayout(LayoutKind.Sequential)]
public struct fvec_t1
{
    uint length;
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] float[] data;
}

Good luck.

2 Comments

Unfortunately, SizeParamIndex only works for arrays passed directly as parameters, not for arrays within a struct. This has to be done manually.
@piedar Really? That's unfortunate, and I'm surprised. According to this, under "Declaring Prototypes", you can use SizeConst there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.