2

I'm starting to struggle with this problem. I've searched and searched for help, and everything i've tried doesn't seem to work. I'm obviously doing something wrong.

Anyway - i have a c# structure defined as:

public struct TestStruct
[StructLayout(LayoutKind.Sequential)]
{
   public int num;
   public IntPtr intArrayPtr;
}

and in the main body of my c# code i have:

public class Testing
{
   [DllImport("testing.dll")]
   static extern void Dll_TestArray(out IntPtr intArrayPtr);

   public GetArray()
   {
      IntPtr structPtr = IntPtr.Zero;
      TestStruct testStruct;
      structPtr = Marshal.AllocHGlobal(Marshal.SizeOf(testStruct));
      Marshal.StructureToPtr(testStruct, structPtr, false);

      Dll_TestArray(structPtr);

      testStruct = (TestStruct) Marshal.PtrToStructure(structPtr, typeof(TestStruct));
   }
}

So now for the c++ part. Starting with the structure:

struct TestStruct
{
   public:
     int  num;
     int* intArray;
}

and now the functions:

extern "C" __declspec(dllexport) void Dll_TestArray(TestStruct *&testStruct)
{
   int num = 15;
   testStruct->num = num;
   testStruct->intArray = new int[num];

   for (int i = 0; i < num; i++)
     testStruct->intArray[i] = i+1;  
}

So - the problem i'm having, is that when i get the structure back into c#, my structure is not how it should be. I can see that the num field has been filled in correctly: it shows 15. However, it is the IntPtr that is still set to zero. The array creation done in c++ has not been carried through to c#.

If i try go step back, and go back into the dll function, i can see that the array was created ok and still retains the information.

So the c# intptr in the struct, is not being set to the pointer being created in c++ (if that makes sense).

So my question really is - how does one make this work correctly?

I want to be able to get back from the dll, a structure which contains all the information that i need. That is, in this example, the number of elements, and the pointer to the array. That way, i can then do a Marshal.Copy on the intptr, to get the array.

If there's another way to do this, i'm more than happy to do it. I've tried several ways already, to no avail. This includes trying the following structure in c# (which contains the int array, rather than the intptr):

public struct TestStruct
{
   public int num;

   // i have tried various methods to marshal this- eg:
   // [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I4]
   public int[] intArray;
}

And i've also tried passing the structure by reference itself, rather than an intptr. Any help on this matter would be massively appreciated. I am unable to change the c++ code, but the c# code can be changed.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
user2477533
  • 201
  • 1
  • 2
  • 10
  • Is this your real code? What is `structPtr. false` and `TestStruct *&testStruct` ?? Also, if `intArrayPtr` is defined as `out`, why do you fill the structure initially before calling `Dll_TestArray`? Please post real code (cut and paste is a good idea). – Alex F Jun 13 '13 at 10:14
  • It's pretty much real code, yeah. It's not particularly useful, i'm just trying to get something extremely basic working, so i can then work on something more advanced. I've edited some of the typos. I wasn't use the out parameter before, but i started doing so when i read somewhere that it would be a good thing. So if we use the out parameter, that's why i then use the TestStruct *&testStruct I have renamed some of the variables in this, so that they might make more sense to you. – user2477533 Jun 13 '13 at 10:27
  • I am not experienced in c# at all, so apologies for any daft mistakes. – user2477533 Jun 13 '13 at 10:32
  • You have far bigger problems, there's a significant memory management problem in this code. The array needs to be released to prevent a runaway memory leak. You can't, you cannot call the delete[] operator in C#. A wrapper written in C++/CLI is needed. Also very important that this C++ code is compiled with the exact same compiler and uses the /MD option so that the CRT is shared. – Hans Passant Jun 13 '13 at 10:42
  • @HansPassant Whilst you cannot call `delete[]` from C#, you can simply export a deallocator from the C++ code. Then there's no need for C++/CLI and no need for the same CRT. – David Heffernan Jun 13 '13 at 10:54
  • @David - if that were possible then surely he'd create a better interop interface in the first place? The usual hangup is being stuck with a chunk of existing code. – Hans Passant Jun 13 '13 at 11:14

1 Answers1

1

First of all change the C++ code to use only one level of indirection:

extern "C" __declspec(dllexport) void Dll_TestArray(TestStruct &testStruct)
{
     const int num = 15;
     testStruct.num = num;
     testStruct.intArray = new int[num];
     for (int i=0; i<num; i++)
         testStruct.intArray[i] = i+1;  
}

On the C# side you want this:

public struct TestStruct
{
    public int num;
    public IntPtr intArray;
}

[DllImport("testing.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void Dll_TestArray(out TestStruct testStruct);

public GetArray()
{
    TestStruct testStruct;
    Dll_TestArray(out testStruct);
    int[] arr = new int[testStruct.num];
    Marshal.Copy(testStruct.intArray, arr, 0, arr.Length);
    // need to call back to DLL to ask it to deallocate testStruct.intArray
}

You'll also need to export a function that will deallocate the array you allocated using the C++ heap. Otherwise you'll leak it.

Perhaps an easier approach would be to change the design to have the caller allocating the buffer.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks very much. As far as deallocating memory etc, i am happy taking care of this. I was just struggling to get back the data i wanted in the array. – user2477533 Jun 13 '13 at 12:25