views:

162

answers:

1

Please bear with me as i am new to marshalling. I have a C structure and function declared as the following:

typedef struct 
{
    char* name;
    BT_ADDR address;
} DeviceList;

extern "C" _declspec(dllexport)DeviceList* PerformQuery();

The BT_ADDR structure is the same structure defined in wsbth2.h in Win CE SDK. PerformQuery returns a pointer to an array of DeviceList.

In my C# program, i perform the following pinvoke declaration as followed

[StructLayout(LayoutKind.Sequential)]
struct DeviceList
{
    public string name;
    public ulong address;
}

[DllImport("BT_Conn.dll")]
public extern static DeviceList[] PerformQuery();

After running the C# program, a NotSupportedException is returned. Can you kindly advise me what is wrong with my marshalling?

+1  A: 

One problem is that the marshaller can't tell how many items are in the returned array, which means it can't marshal it.

Does the PerformQuery() API have some other way of determining the length of the array?

If it only ever returns 1 item, you may want to make it return an IntPtr and then use Marshal.PtrToStructure() as described here:

http://stackoverflow.com/questions/779444/p-invoke-c-function-that-returns-pointer-to-a-struct

Update:

You could try a C interface like this - ie one function which returns the number of items, and one which fills a pre-allocated array with the items.

extern "C" _declspec(dllexport) int GetQueryNumItems(); 
extern "C" _declspec(dllexport) void GetQueryItems(DeviceList* items); 

Then the C# definition would look like this:

[DllImport("BT_Conn.dll")]  
public extern static int GetQueryNumItems();
[DllImport("BT_Conn.dll")]  
public extern static void GetQueryItems([In, Out] DeviceList[] items);

And you would call that in C# like this:

int numItems = GetQueryNumItems();
DeviceList[] items = new DeviceList[numItems];
GetQueryItems(items);

When interfacing C# and C++ I usually find it easier to create a C++/CLI class library, which provides a managed interface that wraps the C code, in a mixed DLL. However that option might not be available to you if you're running on CE.

Saxon Druce
Hi Saxon, PerformQuery() API would actually return more than one item. To determine the length of the array, i would use sizeof(*devList)/sizeof(DeviceList) in C++, but how should i declare my API to inform the marshaller on the number of items in the returned array?
C K Siew
@CK: In C++ `sizeof(*devList)` will return `sizeof(DeviceList)`, ie the size of a single item. If you have a `DeviceList*` pointer, then the compiler can't tell how many items there are. It's only if you have a `DeviceList[]` array (with a fixed number of items) that sizeof can return the full size of the array.
Saxon Druce
@CK: By the way, is PerformQuery() a function you are writing, or is it a function in an existing DLL which you have to interface with?
Saxon Druce
Agreed, you can't find the size in C++ either.
Hans Passant
PerformQuery is a function that i am writing. With the length of the array, how should i re-declare my API so that the marshaller knows how to marshall?
C K Siew
@CK: I've updated my answer with one way you could do it. If it's not possible to split PerformQuery() into two functions which get the size and the items, then there are also other more complicated ways to do this.
Saxon Druce