Lee_666

Hi all,

I've been looking for some assistance on this all day with no luck, so any pointers would be helpful.

struct SID_SEARCH_RESULT_DATA
{
ULONG ulFieldID;
USHORT usFieldLength;
char szFieldData[FIELD_LENGTH];
};

I want to pass this from a c# app into an unmanaged c++ dll and populate it. I've tried a couple of things but with no joy - can anyone give me an idea of the c++ exported method signature and the c# extern method declaration required to pass an array of this struct and populate it

The main complication is that I won't know how many results I will have up front, so I need to be able to assign sufficient memory on the c++ dll side.

Any help would be hugely appreciated.

Thanks , Lee.

Edit: The C++ dll is not a COM object or anything similar, just a straight DLL. That's why I want to do this via p/invoke and some sort of marshalling wizardry. As you may be able to tell, this has landed on my desk with an urgent sticker on it and I'm new to c# & .NET, so I just need to get it working.


Re: Visual C# General C# interop - passing array of struct to a DLL.

TaylorMichaelL

C/C++ is replete with scenarios where memory sizes are assumed. In all cases though somebody somewhere has to be able to specify how much room is available. If your unmanaged function is going to populate an array of structures then you must be able to tell it how many structures you're passing or it must define the maximum # of structures it will populate or else bad things will happen. This really has nothing to do with managed vs. unmanaged code. You have to determine how this information is passed otherwise you're just waiting for a crash. Simply allocating a large # of structures does not solve the problem.

However once you've identified how the # of structures to populate is determined you can invoke it from managed code. The first thing is to convert your structure. This is pretty straightforward.

Code Snippet

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]

internal struct SID_SEARCH_RESULT_DATA

{

public uint ulFieldID;

public ushort usFieldLength;

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

public char szFieldData[FIELD_LENGTH];

}

You can then pass an array of these objects to unmanaged code like so:

Code Snippet

[DllImport(...)]

private static extern void SomeUnmanagedCode ( ref SID_SEARCH_RESULT_DATA[] data );

In the above code I assumed that the szFieldData array is fixed length. In the above case you'll probably find that you'll only ever get 1 element back because the marshaler has no way of knowing how many elements to return. Generally you tell the marshaller how many elements to return back through a parameter like so:

Code Snippet

private static extern void SomeUnmanagedCode ( [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] ref SID_SEARCH_RESULT_DATA[] data, ref int returnedData );

Michael Taylor - 4/30/07

http://p3net.mvps.org





Re: Visual C# General C# interop - passing array of struct to a DLL.

boban.s

Try with something like this:

public const int FIELD_LENGTH = 11; //length of filed is 10 for example, so length of char array has always one character more

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

public struct SID_SEARCH_RESULT_DATA

{

public ulong ulFieldID;

public ushort usFieldLength;

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

public string szFieldData;

}






Re: Visual C# General C# interop - passing array of struct to a DLL.

Lee_666

Thanks for the answers folks, that looks to be exactly what I needed to know.

2 quick points while I go and make the amendments you've suggested:

1. Maximum results issue from the first answer post. I can actually find out what the max number of results will be, so hopefully I can add that to the marshaller instruction as suggested.

2. I take it that on the C++ side I can just say:

__declspec(dllexport) bool SearchTest(SID_SEARCH_RESULT_DATA structArray[], int &size)

{

// populate structures in array

// set size parameter

// return successful

}

Rather than do anything horribly exciting with pointers to the passed in array





Re: Visual C# General C# interop - passing array of struct to a DLL.

TaylorMichaelL

Yes but your function and the caller must agree on the size of the array. For example if your function plans to return 100 elements then the caller better have allocated an array of at least 100 in size otherwise bad things will happen. Generally speaking the safest approach is to have the caller specify the total # of elements that are being passed in and have the function use the parameter as the size of the array. The function can then return (as a return value or in the reference parameter otherwise) the # of elements actually updated. This eliminates any possibility of memory corruption (assuming the caller properly allocated the array).

With P/Invoke the marshaler knows how many elements to pass to the unmanaged code because it knows the size of the array but the unmanaged side doesn't know how many elements it's getting (hence the parameter) nor does the marshaler know how many elements are being returned (hence the reference parameter).

Michael Taylor - 4/30/07

http://p3net.mvps.org





Re: Visual C# General C# interop - passing array of struct to a DLL.

Lee_666

Ok folks, home straight now I hope!

Consider:

[DllImport....]

public static extern bool SearchTest22([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = MAX_RESULTS)]

ref SID_SEARCH_RESULT_DATA[] data,

int MAX_RESULTS,

ref int returnedData);

Used by:

// searchResultArray already created using new

SearchTest22(ref searchResultArray, NativeMethods.MAX_RESULTS, ref iResults);

The C++ side just populates searchResultArray[0], and sets iResults to 1. The iResults value appears to return correctly.

This setup gives a MarshalDirectiveException stating: Cannot use SizeParamIndex for ByRef array parameters.

If I therefore remove the SizeParamIndex directive, I get a stack corruption error stating a marshalling problem. If I leave it in but remove the ref keyword from the extern definition and the method call, I get no errors, but the array comes back empty (although the C++ side population code works correctly).

What am I missing The ref keywords are required to get the array writable on the C++ dll side, but it won't run in it's current format if they are.

Lee.





Re: Visual C# General C# interop - passing array of struct to a DLL.

TaylorMichaelL

I'm not 100% sure the SizeParamIndex works in this case. Nevertheless it should be set to 2 as I assume that when the function returns returnedData is the # of elements that are being returned. The MarshalAs attribute for arrays only applies on marshaling from unmanaged back to managed. You need to make sure that you allocate the array before passing it to the unmanaged code. You can also try removing the ref option. I'm not sure it is needed in this case.

Note that in your example you are assigning the index to MAX_RESULTS which wouldn't be right. SizeParamIndex is the index (within the parameter list) of the parameter that houses the # of elements to be returned. If you want to set a fixed size then use SizeConst instead.

Michael Taylor - 4/30/07

http://p3net.mvps.org





Re: Visual C# General C# interop - passing array of struct to a DLL.

Ming.Chen

1. SizeParamIndex is the INDEX of the parameter that indicates the length of the array. In your case that's the index of MAX_RESULTS, which is 1.

2. In passing variable length struct array from C to C#, I kinda like the Manual way better: Simply marshal the returned pointer as IntPtr, and then read the structs one by one with Marshal.PtrToStructure. This might be slightly slower but easier to debug.






Re: Visual C# General C# interop - passing array of struct to a DLL.

Lee_666

Ok, here we are now then:

TaylorMichaelL, thanks for the note about SizeParamIndex - I did indeed misinterpret it. I have changed it to be the index of the max size variable in the parameter list.

However, still no joy. If I remove the 'ref' keyword from the method declaration and the call, then the function seems fine, and the reference size parameter is set, and the return code is ok, but the array is blank, and doesn't contain any of the data I entered into it in the C++ unmanaged method.

If I leave the 'ref' keyword in, I get a runtime error :

"Cannot marshal 'parameter #1': Cannot use SizeParamIndex for ByRef array parameters."

when the exported method is called. It seems that the ref is need to pass by reference, but then I can't specify SizeParamIndex. If I don't specify SizeParamIndex I get an error stating " The error code is 0xc0000005" and talking about "user marshaling errors for COM-interop or PInvoke, which may corrupt the stack."

Frustrating, because I thought that with your help so far I'd finally got it! Any other comments

Ming.Chen, what is this about marshalling the returned pointer as IntPtr That's the approach I tried at first, but couldn't get it to work. How do I pass the array reference as an IntPtr, and how would I dereference afterward

Thanks again all for your help so far, I'd be stuffed without it.

Lee.





Re: Visual C# General C# interop - passing array of struct to a DLL.

Ming.Chen

I just took another look at your C side func signature and I think you need to first clarify how you want the function in C to behave:

__declspec(dllexport) bool SearchTest(SID_SEARCH_RESULT_DATA structArray[], int &size)

This signature implys that you need to allocate the memory block of structArray in the CALLER, because the SearchTest is passed a pointer and is suppsed to fill the memory block only. Because the caller passes the memory, it needs to know how many items to allocate. size parameter can only indicate how many items are FILLED upon return, not the length of the array. If this is indeed the intended behavior, the size of the structArray should be fixed, so the SizeConst pattern would work and there is no need for manual interpretation of the returned array.

However, if you intend to ask SearchTest to allocate memory and return a pointer, the signature on C side would be something like:

__declspec(dllexport) bool SearchTest(SID_SEARCH_RESULT_DATA** lppStructArray, int &size)

Notice the pointer to pointer as the parameter. This matches your C# side call with ref SID_SEARCH_RESULT_DATA[].

Here, what you can potentially do is declare the first parameter as an IntPtr type. Once you received the pointer in C#, use a loop to extract the structs one by one:

[DllImport(...)]

private static extern void SomeUnmanagedCode ( ref IntPtr pdata, ref int size );

for(int i = 0; i < size; ++ i) {

SID_SEARCH_RESULT_DATA obj = (SID_SEARCH_RESULT_DATA)Marshal.PtrToStructure(pdata, typeof(SID_SEARCH_RESULT_DATA));

pdata += Marshal.SizeOf(SID_SEARCH_RESULT_DATA);

}

...






Re: Visual C# General C# interop - passing array of struct to a DLL.

Lee_666

Thanks Ming.Chen

In fact, the C side code could behave either way - for simplicity sake the array is created as a class variable on the c# side:

[MarshalAs(UnmanagedType.ByValArray)]

SID_SEARCH_RESULT_DATA[] searchResultArray = new SID_SEARCH_RESULT_DATA[MAX_SEARCH_RESULTS];

public static extern bool SearchTest([MarshalAs(UnmanagedType.LPArray, SizeConst = MAX_SEARCH_RESULTS)]

ref SID_SEARCH_RESULT_DATA[] data,

int maxRes,

ref int returnedData);

Called like this:

SearchTest(ref searchResultArray, MAX_SEARCH_RESULTS, ref iResults);

However, this doesn't work. The compiler tells me I can't specify SizeConst if I specify ref for the array param. Remove the ref and the array gets accessed on the C side, but as it's passed by value it's empty on return. Remove the SizeConst and it access violates when it returns to the C# side calling method.

Based on the memory being allocated on the CALLER side, should I still have my c++ parameter as a pointer pointer

I'm off home from the office soon, but I'm going to spend the evening beavering away at this and implementing your suggestions for the IntPtr so I'll post back in the morning if I have any joy (or not!).

Thanks again for helping a newbie .NET / C# programmer - bit of a culture shock from Borland C++!

Lee.





Re: Visual C# General C# interop - passing array of struct to a DLL.

Lee_666

Think I got it!

Struct:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

public struct SID_SEARCH_RESULT_DATA

{

public uint ulFieldID; // ID number of the field as provided by the database

public ushort usFieldLength; // The length of the field in bytes

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

public string strFieldData; // data for the field

};

Export Def:

[DllImport("C:\\Code\\.....")]

public static extern bool SearchTest([MarshalAs(UnmanagedType.LPArray)]

ref SID_SEARCH_RESULT_DATA[] data,

int maxRes,

ref int returnedData);

Call:

NativeMethods.SearchTest(ref searchResultArray, MAX_SEARCH_RESULTS, ref iResults);

C++ Unmanaged:

__declspec(dllexport) bool SearchTest(SID_SEARCH_RESULT_DATA *structArray[], int iMaxRes, int &size)

{ .....

And it appears to work! The key seems to be not specifying the SizeConst or SizeParamIndex in the export Def, and making sure that the C++ function takes a pointer to an array, not just an array or just a pointer. Basic really, not I see it!

Hopefully that will be it, as the memory management is done on the C# side I don't have to muck around with exported destroy functions.

Thanks for all your help guys and gals!

Lee.





Re: Visual C# General C# interop - passing array of struct to a DLL.

Hackfaq

Hello. I have same problem, but the dll function has DLLFunction(HOM* lpHom); declaration

typedef struct {

LPSTR lpszLexWordForm;

enum WFPOS iPartOfSpeech;

DWORD dwHighWordId;

DWORD dwLowWordId;

}HOM;

How can I send lpHom there and receive array from function