Creating arrays of fixed size of PInvoke

Hi,

I'm working to support some code which was written in C but needs to be interfaced from .NET. In my application I support both .NET 1.1 and 2.0. Thanks to C++/CLI I had no problems getting things working with 2.0 but for 1.1 there are still some customers who're interested in this support.

Basically I have a few structures which needs to be passed to the C functions through PInvoke.

Currently the structures look like:

[StructLayout(LayoutKind.Sequential)]

public struct MyStructA

{

public int nType;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]

public String strDescription;

public int nLength;

}

[StructLayout(LayoutKind.Sequential)]

public struct MyStructB

{

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]

public String strStartDate;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]

public String strEndDate;

public int nType;

<< declaration of MyStructA as an array with 16 elements >>

}

I would like to declare an array with 16 elements of type MyStructA inside MyStructB. For strings I could use the ByValTStr with the static size but I cannot seem to find a way to do the same for non-simple objects.

Which construct do I need for archiving this I've been looking through the MarshalAs documentation without finding anything useful.

Thanks in advance.

-- Henrik



Answer this question

Creating arrays of fixed size of PInvoke

  • pinoyz

    Thanks once more for your suggestions!

    For now I defined the structure as an array of bytes which is your first proposal. It basically makes the implemented functionality useless in .NET 1.1. Unfortunatly I cannot perform better then what the compiler can and I cannot justify spending more time on some functionality which is used very little by my .NET 1.1 users anyway. It is only a good excuse for having them update to .NET 2.0 if they need this functionality. Fortunatly the interest for .NET 1.1 is decreasing and I see several other vendors are starting to drop support for .NET 1.1 slowly as .NET 2.0 becomes more popular. I'm sure everyone can agree that .NET 1.0 is dead by now and as long as my .NET 1.1 code compiles and 99% of the functionality is present then it's justifiable to omit this specific function.

    Thanks once more for your feedback. I just think I'll keep things as a raw byte array and will say that .NET 1.1 is soon dead anyway.

    Does anyone know when Microsoft stops supporting it anyway

    -- Henrik


  • DankS

    Henrik,
    The following should work:

    public struct MyStructB {
    ... // your definition
    [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)]
    StructA [] items;
    }

    Don't forget to initialize the array with the same size...

    HTH
    --mc


  • Callum

    Well, aside of a typo in my previous post (I meant MyStructA, sorry...), I cannot see anything wrong with the code. I doubt that typo can be the cause of your problems, unless you do have a StructA somewhere. The obvious request is to post some code, so that I can help finding the issue... in the meanwhile I set up a little test, and I verified that it works. See if this can help you.

    In the C++ dll (that I called dlltest2.dll), the structures and the function are declared as follows:

    struct MyStructA {
      int nType;
      char strDescription [256];
      int nLength;
    };

    struct MyStructB {
      char strStartDate [256];
      char strEndDate [256];
      int nType;
      MyStructA items [16];
    };

    extern "C" DLLTEST2_API int ChangeStruct (MyStructB* parameter) {
      for (int i = 0; i < 16; i++) {
        strcat (parameter->items [ i ].strDescription, " + DLL"); // yes, I know strcat is deprecated... but this is just a silly test
      }
      return 0;
    }

    The C# program:

    namespace ConsoleApplication2 {
      class Program {
        static void Main (string [] args) {
          MyStructB b = new MyStructB ();
          b.items = new MyStructA [16];
          for (int i = 0; i < 16; i++) {
            b.items [ i ] = new MyStructA ();
            b.items [ i ].strDescription = "managed";
          }
          ChangeStruct (ref b);
          for (int i = 0; i < 16; i++) {
            Console.WriteLine (b.items [ i ].strDescription);
          }
          Console.ReadLine ();
        }

        [DllImport ("dlltest2.dll")]
        static extern int ChangeStruct (ref MyStructB parameter);
      }

      [StructLayout (LayoutKind.Sequential)]
      public struct MyStructA {
        public int nType;
        [MarshalAs (UnmanagedType.ByValTStr, SizeConst = 256)]
        public string strDescription;
        public int nLength;
      }

      [StructLayout (LayoutKind.Sequential)]
      public struct MyStructB {
        [MarshalAs (UnmanagedType.ByValTStr, SizeConst = 256)]
        public String strStartDate;
        [MarshalAs (UnmanagedType.ByValTStr, SizeConst = 256)]
        public String strEndDate;
        public int nType;
        [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)]
        public MyStructA [] items;
      }
    }

    You should get 16 times "managed + DLL" on the screen.

    I obviously had to make a few assumptions here, so this might not apply to your case... but it might be a starting point for further discussion.

    HTH
    --mc


  • jortiz

    Sorry Henrik, I missed the part about .NET 1.1 in your original post.

    AFAIK there is no "magic" keyword that would solve the problem, and I must say I never found a technique I really like. My personal favorite is to use pointers, but that's obviously unsafe and I doubt you would want that.

    Another possibility that, while "technically safe" doesn't require too much work, is to pass the inner array as an opaque block of bytes. It's not too bad, if the API is not being called a zillion times per second.

    You would need a separate struct, built exactly like MyStructB except that it has a byte [] instead of the MyStructA [].

    struct MyStructC {
        [MarshalAs (UnmanagedType.ByValTStr, SizeConst = 256)]
        public String strStartDate;
        [MarshalAs (UnmanagedType.ByValTStr, SizeConst = 256)]
        public String strEndDate;
        public int nType;
        [MarshalAs (UnmanagedType.ByValArray, SizeConst = 264 * 16)]
        public byte [] items;
    }

    The external function declaration becomes:

    [DllImport (...)]
    static extern int ChangeStructure (ref MyStructC param);

    We should now wrap this method, and here we will marshal the individual items of the struct.

    static int ChangeStructure (ref MyStructB b) {
        int structSize = Marshal.SizeOf (typeof (MyStructA));
        MyStructC bb = new MyStructC ();
        bb.nType = b.nType;
        bb.strEndDate = b.strEndDate;
        bb.strStartDate = b.strStartDate;
        bb.items = new byte [16 * structSize];
          
        IntPtr block;
        try {
          block = Marshal.AllocCoTaskMem (16 * structSize);
          for (int i = 0; i < 16; i++) {
            IntPtr p = new IntPtr (block.ToInt32 () + i * structSize);
            Marshal.StructureToPtr (b.items [ i ], p, false);
          }
          Marshal.Copy (block, bb.items, 0, 16 * structSize);
            
          ChangeStruct (ref bb);
        
          b.nType = bb.nType;
          b.strEndDate = bb.strEndDate;
          b.strStartDate = bb.strStartDate;  
          Marshal.Copy (bb.items, 0, block, 16 * structSize);
          for (int i = 0; i < 16; i++) {
            IntPtr p = new IntPtr (block.ToInt32 () + i * structSize);
            b.items [ i ] = (MyStructA) Marshal.PtrToStructure (p, typeof (MyStructA));
          }
        } finally {
          Marshal.FreeCoTaskMem (block);
        }
    }

    The code was written in a hurry, so it lacks proper checking and generality.

    I don't know if this is any better than what you are using... probably not. Just an idea.

    HTH
    --mc


  • drakest1

    Thanks for the example code. It turned out to be very useful.

    As it turns out VS.NET 2003 gives exactly the same error on the example code you provided:

    Unhandled Exception: System.TypeLoadException: Can not marshal field items of type ConsoleApplication2.MyStructB: This type can not be marshaled as a structure field.
    at ConsoleApplication2.Program.ChangeStruct(MyStructB& parameter)
    at ConsoleApplication2.Program.Main(String[] args)

    However on VS2005 it works just like it should.

    So this makes it quite odd. It's good to know that it works with 2005 but it doesn't really help me in this situation. So it seems I've been doing everything right but it's a compiler problem which has been fixed since then. For my code I have a C++/CLI wrapper so for .NET 2.0 I have a much better and more clean solution on this then this hacky one. However it seems that not everyone has updated to VS2005 yet which makes it needed that I get it working with 2003.

    I wonder if there are any other keywords which work better From what it seems it is limited what I can do to improve on this.

    Thanks.

    -- Henrik


  • Muricy

    Thanks for the suggestion I was almost sure that it would work out fine until I actually ran it. At runtime I get the following exception:

    Unhandled Exception: System.TypeLoadException: Can not marshal field StructA of type MyStructB: This type can not be marshaled as a structure field.

    It's not really an explicit error so I'm a little clueless on how to proceed.

    Thanks,

    -- Henrik


  • Creating arrays of fixed size of PInvoke