I am trying to use pinvoke to marshall an array of structures inside another structure from C to C#. AFAIK, no can do.
So instead, in the C structure I declare a ptr to my array, and malloc. Problems: 1) How do I declare the equivalent on the C# side? 2) How do I allocate and use the equivalent on the C# side?
//The C code
typedef struct {
int a;
int b; } A;
typedef struct {
int c;
// A myStruct[100]; // can't do this, so:
A *myStruct; } B;
//The c# code:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class A{
int a;
int b;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class B{
int c;
// can't declare array of [100] A structures...
?
}
[EDIT]: Somehow I misinterpreted what I read elsewhere about fixed array of objects on the c# side.
And I can fix the array size in C So compiled ok, but then I get "object reference not set to an instance of an object" when using:
data.B[3].a = 4567; So, reading elsewhere about what this error might be, I added this method:
public void initA()
{
for (int i = 0; i < 100; i++) { B[i] = new A(); }
}
Again, compiled OK, but same error msg.
To marshal "complex" structures like this between C and C#, you have a couple of options.
In this case, I would highly recommend that you try to embed a fixed array into your C-side structure, as it will simplify the C# side a lot. You can use the MarshalAs attribute to tell C# how much space it needs to allocate in the array at run-time:
// In C:
typedef struct
{
int a;
int b;
} A;
typedef struct
{
int c;
A data[100];
} B;
// In C#:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct A
{
int a;
int b;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct B
{
int c;
[MarshalAs(UnmanagedType.LPArray, SizeConst=100)]
A[] data = new data[100];
}
If you don't know, or can't specify, a fixed size for your array, then you'll need to do what you did and declare it as a pointer in C. In this case, you cannot tell C# how much memory the array is going to use at run-time, so you're pretty much stuck with doing all of the marshalling by hand. This question has a good rundown of how that works, but the basic idea is:
You should add a field to your structure that includes a count of the array elements (this will make your life much easier)
Declare the field in C# as: IntPtr data; with no attributes.
Use Marshal.SizeOf(typeof(A)) to get the size of the struct in unmanaged memory.
Use Marshal.PtrToStructure to convert a single unmanaged structure to C#
Use IntPtr.Add(ptr, sizeofA) to move to the next structure in the array
Loop until you run out.
Related
I'm trying to populate an array of structures from C++ and pass the result back to C#.
I thought maybe creating a struct with an array of structures maybe the way forward as most examples I have come across use structures(but passing basic types). I have tried the following but no luck so far.
Found an example at: http://limbioliong.wordpress.com/2011/08/20/passing-a-pointer-to-a-structure-from-c-to-c-part-2/?relatedposts_exclude=542
I have the following in C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace CSharpDLLCall
{
class Program
{
[StructLayout(LayoutKind.Sequential,Pack=8)]
public struct Struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public double[] d1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public double[] d2;
}
[StructLayout(LayoutKind.Sequential)]
public struct TestStructOuter
{
public int length;
public IntPtr embedded;
}
static void Main(string[] args)
{
Program program = new Program();
program.demoArrayOfStructs();
}
public void demoArrayOfStructs()
{
TestStructOuter outer = new TestStructOuter();
testStructOuter.embedded = new Struct1[10];
outer.length = 10;
outer.embedded = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Struct1)) * 10);
Marshal.StructureToPtr(outer, outer.embedded, false);
testAPI2(ref outer);
outer = (TestStructOuter)(Marshal.PtrToStructure(outer.embedded, typeof(TestStructOuter)));
Marshal.FreeHGlobal(outer.embedded);
}
[DllImport(#"C:\CPP_Projects\DLL\DLLSample\Release\DLLSample.dll")]
static extern void testAPI2(IntPtr pTestStructOuter);
}
}
In C++ in the header i have
#ifdef DLLSAMPLE_EXPORTS
#define DLLSAMPLE_API __declspec(dllexport)
#else
#define DLLSAMPLE_API __declspec(dllimport)
#endif
#include <iostream>
using namespace std;
#pragma pack(1)
struct struct1
{
double d1[];
double d2[];
};
struct TestStructOuter
{
struct1* embedded;
};
extern "C"
{
DLLSAMPLE_API void __stdcall testAPI2(TestStructOuter* pTestStructOuter);
}
In the cpp I have:
#include "stdafx.h"
#include "DLLSample.h"
__declspec(dllexport) void __stdcall testAPI2(TestStructOuter* pTestStructOuter)
{
// not sure that this is necessary
// for (int i = 0; i < 10 ; ++i)
// {
// pTestStructOuter->embedded = new struct1;
// }
for (int i = 0; i < 10 ; ++i)
{
struct1 s1;
for (int idx = 0; idx < 10; ++idx)
{
s1.d1[i] = i+0.5;
s1.d2[i] = i+0.5;
}
pTestStructOuter->embedded[0] = s1;
}
}
This doesn't seem to work the error i get:
The parameter is incorrect.(Exception from HRESULT:0x80070057 (E_INVALIDARG))
Which probably means that its not recognizing the array of structures. Any ideas how I can do this? Thanks.
Okay, I have a working sample. I'm posting this as another answer because it's a very different approach.
So, on the C++ side, I've got this header file:
struct Struct1
{
int d1[10];
int d2[10];
};
extern "C" __declspec(dllexport) void __stdcall
TestApi2(int* pLength, Struct1 **pStructures);
And the following code:
__declspec(dllexport) void __stdcall
TestApi2(int* pLength, Struct1 **pStructures)
{
int len = 10;
*pLength = len;
*pStructures = (Struct1*)LocalAlloc(0, len * sizeof(Struct1));
Struct1 *pCur = *pStructures;
for (int i = 0; i < len; i++)
{
for (int idx = 0; idx < 10; ++idx)
{
pCur->d1[idx] = i + idx;
pCur->d2[idx] = i + idx;
}
pCur ++;
}
}
Now, the important bit is LocalAlloc. That's actually the place where I've had all the issues, since I allocated the memory wrong. LocalAlloc is the method .NET calls when it does Marshal.AllocHGlobal, which is very handy, since that means we can use the memory in the caller and dispose of it as needed.
Now, this method allows you to return an arbitrary length array of structures. The same approach can be used to eg. return a structure of an array of structures, you're just going deeper. The key is the LocalAlloc - that's memory you can easily access using the Marshal class, and it's memory that isn't thrown away.
The reason you have to also return the length of the array is because there's no way to know how much data you're "returning" otherwise. This is a common "problem" in unmanaged code, and if you've ever done any P/Invoking, you know everything about this.
And now, the C# side. I've tried hard to do this in a nice way, but the problem is that arrays of structures simply aren't blittable, which means you can't simply use MarshalAs(UnmanagedType.LPArray, ...). So, we have to go the IntPtr way.
The definitions look like this:
[StructLayout(LayoutKind.Sequential)]
public class Struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public int[] d1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public int[] d2;
}
[DllImport(#"Win32Project.dll", CallingConvention = CallingConvention.StdCall)]
static extern void TestApi2(out int length, out IntPtr structs);
Basically, we get a pointer to the length of the "array", and the pointer to the pointer to the first element of the array. That's all we need to read the data.
The code follows:
int length;
IntPtr pStructs;
TestApi2(out length, out pStructs);
// Prepare the C#-side array to copy the data to
Struct1[] structs = new Struct1[length];
IntPtr current = pStructs;
for (int i = 0; i < length; i++)
{
// Create a new struct and copy the unmanaged one to it
structs[i] = new Struct1();
Marshal.PtrToStructure(current, structs[i]);
// Clean-up
Marshal.DestroyStructure(current, typeof(Struct1));
// And move to the next structure in the array
current = (IntPtr)((long)current + Marshal.SizeOf(structs[i]));
}
// And finally, dispose of the whole block of unmanaged memory.
Marshal.FreeHGlobal(pStructs);
The only thing that changes if you want to really return a structure of an array of structures is that the method parameters move into the "wrapping" structure. The handy thing is that .NET can automatically handle the marshalling of the wrapper, the less-handy thing is that it can't handle the inner array, so you again have to use length + IntPtr to manage this manually.
First, don't use unknown-length arrays at all. You're killing any possible use of arrays between managed and unmanaged code - you can't tell the required size (Marshal.SizeOf is useless), you have to manually pass the array length etc. If it's not a fixed length array, use IntPtr:
[StructLayout(LayoutKind.Sequential,Pack=8)]
public struct Struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public double[] d1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public double[] d2;
}
[StructLayout(LayoutKind.Sequential)]
public struct TestStructOuter
{
public int length;
public IntPtr embedded;
}
(if your array of embedded is fixed length, feel free to ignore this, but do include the ByValArray, SizeConst bit. You also have to add ArraySubType=System.Runtime.InteropServices.UnmanagedType.Struct to the attribute).
As you've probably noticed, the TestStructOuter is pretty much useless in this case, it only adds complexity (and do note that marshalling is one of the most expensive operations compared to native languages, so if you're calling this often, it's probably a good idea to keep things simple).
Now, you're only allocating memory for the outer struct. Even in your code, Marshal.SizeOf has no idea how big the struct should be; with IntPtr, this is even more so (or, to be more precise, you're only requesting a single IntPtr, ie. 4-8 bytes or so + the length). Most often, you'd want to pass the IntPtr directly, rather than doing this kind of wrapping (using the same thing in C++/CLI is a different thing entirely, although for your case this is unnecessary).
The signature for your method will be something like this:
[DllImport(#"C:\CPP_Projects\DLL\DLLSample\Release\DLLSample.dll",
CallingConvention=System.Runtime.InteropServices.CallingConvention.StdCall)]
public static extern void testAPI2(ref TestStructOuter pTestStructOuter) ;
Now, if you're going with the IntPtr approach, you want to keep allocating memory manually, only you have to do it properly, eg.:
TestStructOuter outer = new TestStructOuter();
testStructOuter.length = 1;
testStructOuter.embedded = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Struct1)));
Marshal.StructureToPtr(embedded, testStructOuter.embedded, false);
And finally, you call the method:
// The marshalling is done automatically
testAPI2(ref outer);
Don't forget to release the memory, ideally in the finally clause of a try around everything since the memory allocation:
Marshal.FreeHGlobal(outer.embedded);
Now, this is overly complicated and not exactly optimal. Leaving out the TestStructOuter is one possibility, then you can simply pass the length and pointer to the embedded array directly, avoiding one unnecessary marshalling. Another option would be to use a fixed size array (if it needs to be an array at all :)) in TestStructOuter, which will cure a lot of your headaches and eliminate any need for manual marshalling. In other words, if TestStructOuter is defined as I've noted before:
[StructLayout(LayoutKind.Sequential)]
public struct TestStructOuter
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10,
ArraySubType=UnmanagedType.Struct)]
public Struct1[] embedded;
}
Suddenly, your whole call and everything becomes as simple as
testAPI2(ref outer);
The whole marshalling is done automatically, no need for manual allocations or conversions.
Hope this helps :)
Hint: Leadn C++/CLI. I had to deal with complex interop two times in my life - once with ISDN (AVM devkits make it a lot easier - sadly C++, I needed C#) and now with Nanex (great real time complex and full market ata feeds, sadl complex C++, I need C#)
Both cases I make my own wrapper in C++/CLI, talking down to C++ and exposing a real object model in C#. Allows me to make things a lot nicer and handle a lot of edge cases that our friendly Marshaller can not handle efficiently.
I have the following c#/c code, where I am doing stuff in a C dll. Am using pinvoke/marshal as the black magic that enables me to dynamically allocate/free stuff in the dll, without c# code knowing anything untoward is going on.
In this snippet, you will see that I am using 2 different ways to alloc/use/free an array of doubles. My question is, what does the "MarshalAs(UnmanagedType..." line do, because both incantations (ie, using or not using the MarshalAs statement) work fine? I should add that I have a poor understanding of C, even less understanding of C#, and I understand the whole pinvoke/marshal about as well as I understand supersymmetric quantum mechanics.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class row
{
public int a;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
IntPtr[] b;
IntPtr [] c;
}
// c code
struct row
{
int a;
double *b;
double *c;
}
void fooe(void)
{
row.b[4] = (double *) malloc(54000);
row.c[4] = (double *) malloc(54000);
free(row.b[4]);
free(row.c[4]);
}
This particular use of ByValArray changes everything.
First, let's consider the without ByValArray case. Here the array is marshalled as a pointer to the first element. The matching C struct is
struct row
{
int a;
void* *b; // more likely you would use a typed pointer
void* *c;
}
No for the case where you use ByValArray. Here the array is marshalled inline and the equivalent C struct is:
struct row
{
int a;
void* b[12];
void* *c;
}
These two versions are very different.
I also wonder if you really meant to use IntPtr[] in the C# code. Did you perhaps really mean to write double[]? So, perhaps you actually meant:
[StructLayout(LayoutKind.Sequential)]
public class row
{
public int a;
double[] b;
double[] c;
}
In which case the matching C struct would be:
struct row
{
int a;
double *b;
double *c;
}
I've no idea what is meant by the fooe function, but it makes no sense at all and does not compile. It looks like you are trying to assign a pointer to a double which is quite meaningless.
The API I am using has something like this:
int simpletran(LPSTRUCT req)
{
printf("%d", req->length);
}
typedef unsigned long ULONG;
typdef struct _st {
ULONG length;
}STRUCT, *LPSTRUCT;
My C# version of it:
[DllImport(LINUXLIB, CallingConvention=CallingConvention.Cdecl)]
public static extern simpletran(STRUCT req);
class STRUCT
{
public UInt32 length;
}
STRUCT st = new STRUCT();
st.length = (UInt32)100;
simpletran(st);
When I call the unmanaged function I get some long negative values like -31245665!!!
I am using C# mono on a Linux machine.
I haven't tested any of this, so it may need some changes, but here's what I see off hand.
First off, STRUCT should be declared as follows:
struct STRUCT
{
public UInt32 length;
}
Notice that we changed from class to struct so that the memory layout of the object is known and matches what the C code is expecting.
Update
After a bit of further consideration, there's an easier way of declaring and calling the method, I'll leave the original answer below for another way of doing it.
Your P/Invoke signature should be:
[DllImport(LINUXLIB, CallingConvention=CallingConvention.Cdecl)]
public static extern int simpletran(ref STRUCT req);
Notice that we changed STRUCT to ref STRUCT since STRUCT is a value type and the C code requires a pointer to that structure.
And you'd call it like this:
STRUCT st = new STRUCT();
st.length = (UInt32)100;
simpletran(ref st);
Original
[DllImport(LINUXLIB, CallingConvention=CallingConvention.Cdecl)]
public static extern int simpletran(IntPtr req);
Notice that we changed STRUCT to IntPtr since STRUCT has to be a value type and the C code requires a pointer to that structure.
And you'd call it like this:
STRUCT st = new STRUCT();
st.length = (UInt32)100;
IntPtr ptr = Marshal.AllocHGlobal(sizeof(STRUCT));
Marshal.StructureToPtr(st, ptr, false);
simpletran(ptr);
Marshal.FreeHGlobal(ptr);
Adding the extra steps in between creating the instance and calling the method for allocating a chunk of unmanaged memory to store the value of the struct in (Marshal.AllocHGlobal) and copying the value of st into that memory with Marshal.StructureToPtr(...). Be sure to free the allocated memory after the call with a call to Marshal.FreeHGlobal
With great help of the stackoverflow community, I've managed to call a native DLL function. However, I can't modify the values of ID or intersects array. No matter what I do with it on the DLL side, the old value remains. It seems read-only.
Here are some code fragments:
C++ struct:
typedef struct _Face {
int ID;
int intersects[625];
} Face;
C# mapping:
[StructLayout(LayoutKind.Sequential)]
public struct Face {
public int ID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 625)]
public int[] intersects;
}
C++ method (type set to DLL in VS2010):
extern "C" int __declspec(dllexport) __stdcall
solve(Face *faces, int n){
for(int i =0; i<n; i++){
for(int r=0; r<625; r++){
faces[i].intersects[r] = 333;
faces[i].ID = 666;
}
}
C# method signature:
[DllImport("lib.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern int solve(Face[] faces, int len);
C# method invocation:
Face[] faces = new Face[10];
faces[0].intersects = new int[625];
faces[0].ID = -1; //.. and add 9 more ..
solve(faces, faces.Length);
// faces[0].ID still equals -1 and not 666
Kindest regards,
e.
You have to tell the pinvoke marshaller explicitly that the array needs to be marshaled back. You do this with the [In] and [Out] attributes. Like this:
[DllImport("...")]
public static extern int solve([In, Out] Face[] faces, int len);
This is an output field only? To get to the bottom of this, I'd try substituting your Face[] parameter with a large-enough a byte[] and see if the byte array gets filled with anything (you'll have to change your [DllExport] a bit).
Also, one other thing I used to experience when doing this with char*'s is that I had to pre-allocate the buffer in C#. For example:
StringBuilder theString=new StringBuilder();
MyUnmanagedFunction(theString);
would not work. But assuming that returned theString was a max 256 characters, I would do this:
StringBuilder theString=new StringBuilder(256);
MyUnmanagedFunction(theString);
And I'd be in business. I'd recommend trying the byte[] substitution, if that doesn't work, try the pre-allocated byte array. Once you are seeing the byte array actually get changed by your C++ code, then you can figure out how to marshal that thing into your C# struct.
In my C# code I'm trying to fetch an array of structures from a legacy C++ DLL (the code I cannot change).
In that C++ code, the structure is defined like this:
struct MyStruct
{
char* id;
char* description;
};
The method that I'm calling (get_my_structures) returns a pointer to an array of MyStruct structures:
MyStruct* get_my_structures()
{
...
}
There is another method that returns the number of stuctures so I do know how many structures get returned.
In my C# code, I have defined MyStruct like this:
[StructLayout(LayoutKind.Sequential)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPStr)] // <-- also tried without this
private string _id;
[MarshalAsAttribute(UnmanagedType.LPStr)]
private string _description;
}
The interop signature looks like this:
[DllImport("legacy.dll", EntryPoint="get_my_structures")]
public static extern IntPtr GetMyStructures();
Finally, the code that fetches the array of MyStruct structures looks like this:
int structuresCount = ...;
IntPtr myStructs = GetMyStructures();
int structSize = Marshal.SizeOf(typeof(MyStruct)); // <- returns 8 in my case
for (int i = 0; i < structuresCount; i++)
{
IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i);
MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct));
...
}
The trouble is, only the very first structure (one at the offset zero) gets marshaled correctly. Subsequent ones have bogus values in _id and _description members. The values are not completely trashed, or so it seems: they are strings from some other memory locations. The code itself does not crash.
I have verified that the C++ code in get_my_structures() does return correct data. The data is not accidentally deleted or modified during or after the call.
Viewed in a debugger, C++ memory layout of the returned data looks like this:
0: id (char*) <---- [MyStruct 1]
4: description (char*)
8: id (char*) <---- [MyStruct 2]
12: description (char*)
16: id (char*) <---- [MyStruct 3]
...
[Update 18/11/2009]
Here is how the C++ code prepares these structures (the actual code is much uglier, but this is a close enough approximation):
static char buffer[12345] = {0};
MyStruct* myStructs = (MyStruct*) &buffer;
for (int i = 0; i < structuresCount; i++)
{
MyStruct* ms = <some other permanent address where the struct is>;
myStructs[i].id = (char*) ms->id;
myStructs[i].description = (char*) ms->description;
}
return myStructs;
Admittedly, the code above does some ugly casting and copies raw pointers around, but it still does seem to do that correctly. At least that's what I see in the debugger: the above (static) buffer does contain all these naked char* pointers stored one after another, and they point to valid (non-local) locations in memory.
Pavel's example shows that this is really the only place where things can go wrong. I will try to analyze what happens with those 'end' locations where the strings really are, not the locations where the pointers get stored.
I cannot reproduce your problem, which leads me to suspect that it's really on C++ side of things. Here's the complete source code for my attempt.
dll.cpp - compile with cl.exe /LD:
extern "C" {
struct MyStruct
{
char* id;
char* description;
};
__declspec(dllexport)
MyStruct* __stdcall get_my_structures()
{
static MyStruct a[] =
{
{ "id1", "desc1" },
{ "id2", "desc2" },
{ "id3", "desc3" }
};
return a;
}
}
test.cs - compile with csc.exe /platform:x86:
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPStr)]
public string _id;
[MarshalAsAttribute(UnmanagedType.LPStr)]
public string _description;
}
class Program
{
[DllImport("dll")]
static extern IntPtr get_my_structures();
static void Main()
{
int structSize = Marshal.SizeOf(typeof(MyStruct));
Console.WriteLine(structSize);
IntPtr myStructs = get_my_structures();
for (int i = 0; i < 3; ++i)
{
IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i);
MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct));
Console.WriteLine();
Console.WriteLine(ms._id);
Console.WriteLine(ms._description);
}
}
}
This correctly prints out all 3 structs.
Can you show your C++ code that fills the structs? The fact that you can call it from C++ directly and get correct results does not necessarily mean it's correct. For example, you could be returning a pointer to a stack-allocated struct. When doing a direct call, then, you'd get a technically invalid pointer, but the data would likely remain preserved. When doing P/Invoke marshalling, the stack could be overwritten by P/Invoke data structures by the point it tries to read values from there.
I would change the structure. Instead of strings etc. , use IntPtr:
[StructLayout(LayoutKind.Sequential)]
public class MyStruct
{
private IntPtr _id;
private IntPtr _description;
}
Then each value of the C# array could be manually marshalled to string using Marshal.PtrToString taking into account charset etc.
I usually end up working these things out by trial and error. Make sure you have the CharSet property set on your StructLayout, and I would try UnmanagedType.LPTStr, seems to work better for char *, even though I am not sure why.
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private string _id;
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private string _description;
}
I think, also, in addition to the answers given, that you need to supply the length as well, ie
[MarshalAsAttribute(UnmanagedType.LPTStr), SizeConst = , ArraySubType = System.Runtime.InteropServices.UnmanagedType.AnsiBStr)]
This is a trial and error to get this right, also, another thing to consider, in some WinAPI calls that expect a string parameter, usually a ref parameter, it might be worth your while to try the StringBuilder class also...Nothing else comes to mind on this other than the points I have mentioned here... Hope this helps, Tom
You have to use UnmanagedType.LPTStr for char*. Also a StringBuilder is recommended for a non const char*:
And a CharSet specification:
[StructLayout(LayoutKind.Sequential, Charset = CharSet.Auto)]
public class MyStruct
{
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private StringBuilder _id;
[MarshalAsAttribute(UnmanagedType.LPTStr)]
private StringBuilder _description;
}
As for the DllImport declaration, have you tried
[DllImport("legacy.dll", EntryPoint="get_my_structures")]
public static extern MarshalAs(UnmanagedType.LPArray) MyStruct[] GetMyStructures();
?
Also, if the previous doesn't work, leave it at IntPtr and try to Mashal the returned structs like this:
for (int i = 0; i < structuresCount; i++)
{
MyStruct ms = (MyStruct) Marshal.PtrToStructure(myStructs, typeof(MyStruct));
...
myStructs += Marshal.SizeOf(ms);
}