-1

I have a C (not C++, as is most common) function in a DLL with the following signature:

unsigned long MyFunc(mystruct *arr, unsigned long arrLen);

where arr is an array of mystruct and arrLen is the length of that array

The structure is like so:

typedef struct mystruct {

  unsigned long type;

  void* val;

  unsigned long valLen;

} mystruct;

Where type is a code that lets me know what is the actual pointer type of val, and valLen is the size in bytes of the value pointed to by val.

I'm trying to call this function from C#, and am getting the following error:

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

this is how I declared the structure in C#:


[StructLayout(LayoutKind.Sequential)]
public struct mystruct{
  public uint type;
  public IntPtr val;
  public uint valLen;
} 

this is my P/Invoke declaration:

[DllImport("mydll.dll")]
    public static extern uint MyFunc(mystruct[] arr, uint arrLen);

Let's say I'm trying to pass 3 elements in the array. One is an uint (so val should correspond to an unsigned long* in C), one is a byte (so val should correspond to an unsigned char in C) and another an array of bytes (so val should correspond to a char * in C).

I have also tried to only pass a single element array (the element being the uint one) and I got the same error. This tells me that the problem is not necessarily to do with passing the array of bytes but also with the simplest types.

I have checked the DLL and commented out the lines that dereference val in MyFunc and with those lines and only those lines commented out, there is no error. Thus, I believe the problem lies with how I pass the pointers. Another reason is because if I pass a null array there is no error, even without the code commented out.

I have tried to do this in 2 different ways.

0- without allocating memory manually

mystruct[] arr = new mystruct[3];
arr[0].type = 0;
uint my_uint = 10;
GCHandle handle_uint = GCHandle.Alloc(my_uint, GCHandleType.Pinned);
arr[0].val = handle_uint.AddrOfPinnedObject();
arr[0].valLen = sizeof(uint);

arr[1].type = 1;
byte my_byte= 1;
GCHandle handle_byte = GCHandle.Alloc(my_byte, GCHandleType.Pinned);
arr[1].val= handle_byte.AddrOfPinnedObject();
arr[1].valLen= sizeof(byte);

arr[2].type = 2;
byte[] my_byte_arr = new byte[32];
for (int i = 0; i < 32; i++) {
    my_byte_arr[i] = 255;
}
GCHandle handle_byte_arr = GCHandle.Alloc(my_byte_arr, GCHandleType.Pinned);
arr[2].val = handle_byte_arr.AddrOfPinnedObject();
arr[2].valLen = (uint) my_byte_arr.Length;

MyFunc(arr, (uint) arr.Length);
handle_uint.Free();
handle_byte.Free();
handle_byte_arr.Free();

1- allocating memory manually - for brevity sake, I only show how I did it for an uint. Another reason is because I only tried this one with a single uint argument


mystruct[] arr = new mystruct[1];
arr[0].valLen = sizeof(uint);
arr[0].type = 0;
arr[0].val = Marshal.AllocCoTaskMem((int) arr[0].valLen);

Marshal.WriteInt32(arr[0].val, 10);
MyFunc(arr, (uint) arr.Length);
Marshal.FreeCoTaskMem(arr[0].val);

These questions are related:

0- How to pass an object pointer from and to a DLL? However, the solution presented is not correct for my case, because I can call all other functions from the DLL, just not this one because, as I explained, I am most likely passing the pointers incorrectly in this case. I also have successfully called other functions from this DLL, and even this function as long as that void* is never dereferenced by the DLL 1- C# call C++ DLL passing pointer-to-pointer argument

chilliefiber
  • 571
  • 2
  • 7
  • 18
  • 1
    Note that local primitive variables are stored on the stack and they don't get moved around by the GC, therefore no pinning is needed. However you can only get their addresses in an `unsafe` block. – Ben Voigt Jun 15 '23 at 19:09

1 Answers1

-2

Try following :

       [StructLayout(LayoutKind.Sequential)]
        public struct mystruct
        {
            public uint type;
            public IntPtr val;
            public uint valLen;
        }
        [DllImport("mydll.dll")]
        public static extern uint MyFunc(IntPtr arr, uint arrLen);
        static void Main(string[] args)
        {

            mystruct[] arr = new mystruct[3];
            arr[0].type = 0;
            byte[] data0 = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
            IntPtr ptr0 = Marshal.AllocHGlobal(data0.Length);
            Marshal.Copy(data0, 0, ptr0, data0.Length);
            arr[0].val = ptr0;
            arr[0].valLen = (uint)data0.Length;

            arr[1].type = 0;
            byte[] data1 = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
            IntPtr ptr1 = Marshal.AllocHGlobal(data0.Length);
            Marshal.Copy(data1, 0, ptr1, data1.Length);
            arr[1].val = ptr1;
            arr[1].valLen = (uint)data1.Length;

            arr[2].type = 0;
            byte[] data2 = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
            IntPtr ptr2 = Marshal.AllocHGlobal(data0.Length);
            Marshal.Copy(data2, 0, ptr2, data2.Length);
            arr[2].val = ptr2;
            arr[2].valLen = (uint)data2.Length;

            int size = arr.Length * Marshal.SizeOf(typeof(mystruct));
            IntPtr myStrPtr = Marshal.AllocCoTaskMem(size);
            for (int i = 0; i < arr.Length; i++)
                Marshal.StructureToPtr(arr[i], myStrPtr + i * size , true);//error

            MyFunc(myStrPtr, 3);
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • You're going to leak memory really badly like that, you need to put `Marshal.FreeHGlobal` in a `finally`. Also the final marshalling of the array is definitely unnecessary, the marshaller should be able to do that. – Charlieface Jun 16 '23 at 11:07