4

Below I have a code snippet from c++. I need to return array of pointers (to TempStruct).

The problem is that on c# side I get only one element. On the other I get AV.

**C++**
extern "C" __declspec(dllexport) void GetResult(TempStruct** outPtr, long *size)
{       
    *outPtr = (TempStruct*)new TempStruct*[2];

     outPtr[0] = new TempStruct("sdf", 123);
     outPtr[1] = new TempStruct("abc", 456);

    *size = 2;      
}

**C#**    
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void GetResult(out IntPtr outPtr, out int size);

IntPtr ptr = IntPtr.Zero;
int length;        
GetResult(out ptr, out length);

int size = Marshal.SizeOf(typeof(TempStruct));
TempStruct[] someData2 = new TempStruct[length];

for (int i = 0; i < length; i++)
{
   IntPtr wskptr = (IntPtr)(ptr.ToInt64() + (size * i));
   someData2[i] = (TempStruct)Marshal.PtrToStructure(wskptr, typeof(TempStruct));            
} 
1
  • 1
    Don't you mean e.g. (*outPtr)[0]? And shouldn't the outPtr argument be a tripple pointer (i.e. TempStruct*** outPtr) since you allocate an array of pointers? The casting you do when allocating memory is masking that last error, don't ever cast the result of new. Commented Jun 24, 2015 at 11:18

2 Answers 2

4

You are doing it wrong.

You are mixing pointer types.

By using the new TempStruct() you are creating an array of pointers to TempStruct. The example I gave you created an array of TempStruct. See the difference?

Now... TempStruct** outPtr should be TempStruct*** outPtr (because you want to return (*) an array (*) of pointers (*)... Or TempStruct**& if you prefer :-)

Change this line

someData2[i] = (TempStruct)Marshal.PtrToStructure(Marshal.ReadIntPtr(wskptr), typeof(TempStruct));

Because you must read the single pointers.

I do hope you are deleting the various TempStruct with delete and using the

delete[] ptr;

operator to delete the array of structures.

Full example:

C++:

struct TempStruct
{
    char* str;
    int num;

    // Note the strdup. You don't know the source of str.
    // For example if the source is "Foo", then you can't free it.
    // Using strdup solves this problem.
    TempStruct(const char *str, int num) 
        : str(strdup(str)), num(num)
    {
    }

    ~TempStruct()
    {
        free(str);
    }
};

extern "C"
{
    __declspec(dllexport) void GetResult(TempStruct ***outPtr, int *size)
    {
        *outPtr = new TempStruct*[2];

        (*outPtr)[0] = new TempStruct("sdf", 123);
        (*outPtr)[1] = new TempStruct("abc", 456);

        *size = 2;
    }

    __declspec(dllexport) void FreeSomeData(TempStruct **ptr, int size)
    {
        for (int i = 0; i < size; i++)
        {
            delete ptr[i];
        }

        delete[] ptr;
    }
}

C#:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
internal struct TempStruct
{
    public string str;
    public int num;
}

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResult(out IntPtr outPtr, out int numPtr);

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeSomeData(IntPtr ptr, int num);

// C++ will return its TempStruct array in ptr
IntPtr ptr;
int size;

GetResult(out ptr, out size);

TempStruct[] someData2 = new TempStruct[size];

for (int i = 0; i < size; i++)
{
    IntPtr ptr2 = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
    someData2[i] = (TempStruct)Marshal.PtrToStructure(ptr2, typeof(TempStruct));
}

// Important! We free the TempStruct allocated by C++. We let the
// C++ do it, because it knows how to do it.
FreeSomeData(ptr, size);

Note that you don't need [Serializable] and Pack=1 on the C# struct

More correct for the C++:

__declspec(dllexport) void GetResult(TempStruct **&outPtr, int &size)
{
    outPtr = new TempStruct*[2];

    outPtr[0] = new TempStruct("sdf", 123);
    outPtr[1] = new TempStruct("abc", 456);

    size = 2;
}

It is more correct because both outPtr and size can't be NULL. See https://stackoverflow.com/a/620634/613130 . The C# signature is the same.

Sign up to request clarification or add additional context in comments.

8 Comments

Still I can only read only 1st item. I changed parameter to triple pointers, and changed c# code as you wrote above
delete[] ptr isn't enough. Code in the question requires each item to be deleted too. Which is why I believe that the C++ code is wrong.
@DavidHeffernan Yep... Corrected.
@John See full example.
@John The outPtr in the example isn't normally NULL. If you used that method from C++, you would: TempStruct **ptr; int size; GetResult(&ptr, &size), so you would pass a value for outPtr: &ptr. The same in C#, you use out ptr. Clearly you could add a NULL check, because someone could do a GetResult(NULL, NULL). Technically the "right" way to write it in C++ would be void GetResult(TempStruct **&outPtr, int &size), because both outPtr and size can't be NULL (see stackoverflow.com/a/620634/613130)
|
3

The C++ code is wrong. It's returning an array of pointers to struct. The fact that you cast the value returned by new should have alerted you to the fact that you made a mistake. You want to return an array of struct.

It should be:

*outPtr = new TempStruct[2];
(*outPtr)[0].str = "sdf";
(*outPtr)[0].i = 123;
(*outPtr)[1].str = "abc";
(*outPtr)[1].i = 456;
*size = 2;      

4 Comments

Don't you mean e.g. (*outPtr)[0]? :)
@JoachimPileborg Yes. Thank you. I'm just rubbish at C++!
@DavidHeffernan The only problem is that you can't easily use parameterized constructors with that syntax. Someone had asked that here and the solutions where horrible. It is possible to use this one, but I would prefer to go to the dentist... :-)
@xanatos Well yes, but I'd prefer that to all that extra allocation and indirection. And if the caller can know how many elements are needed then the whole thing can be done by the p/invoke marshaller with no explicit allocation. In the C++ code I'd just make a function that returns a new value: TempStruct MakeNewStruct(const char* str, const int i)

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.