user5877732
user5877732

Reputation: 381

Call a method through Interop which returns an instance of a struct

I'm new to Interop and need to call a managed C++ method from C# which returns an instance of the following struct:

typedef struct DataBlock_ {
  unsigned char data[10240];
  unsigned int numberOfBytes;
  unsigned long int startAddr;    
} DataBlock;

The C++ method which returns the instance is declared as follows:

__declspec(dllexport) DataBlock getDefaultPass( void ) 
{
    DataBlock default_pass = {
        {
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF,
            (char)0xFF,(char)0xFF,(char)0xFF,(char)0xFF
        },
        32,
        0xFFE0
    };
    return default_pass;
}

I've declared the struct and method in C# as follows:

public static partial class My
{
    [StructLayout(LayoutKind.Sequential)]
    public struct DataBlock
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10240)]
        public byte[] data;
        //public fixed byte data[10240]; <-- this requires 'unsafe' but still doesn't work      
        public UInt32 numberOfBytes;
        public UInt32 startAddr;
    }

    [DllImport("my.dll")]
    static public extern DataBlock getDefaultPass( );

    [DllImport("my.dll")]
    static public extern byte sendPassword(DataBlock data);
}

I call the method from C# as follows:

var defaultPassword = My.getDefaultPass();
var response = My.sendPassword(defaultPassword);

but the call to getDefaultPass() throws

An unhandled exception of type 'System.Runtime.InteropServices.MarshalDirectiveException' occurred in ConsoleApplication1.exe

Additional information: Method's type signature is not PInvoke compatible.

Based on this question, I tried changing the declaration of data to public fixed byte data[10240] and marked the struct as unsafe, but then the method returns an instance with numberOfBytes and startAddr set to 0, and the subsequent call to sendPassword() fails (note that in this answer the subsequent calls use a pointer to the struct as opposed to the instance itself, as is my case). How then should I be calling the method(s) from C#?

The project targets .NET 3.5 and x86.

Thanks in advance for any help.

Upvotes: 0

Views: 157

Answers (2)

user5877732
user5877732

Reputation: 381

For the sake of completeness and to complement Luaan's answer, since the C++ method in the question has no parameters, I wanted to cover the case where a method did have parameters, specifically as it relates to the position of the out parameter when the method takes 2 or more arguments.

Consider the C++ method

__declspec(dllexport) DataBlock readText(char * dataArray , int bytesToRead)

It's not immediately obvious whether the out parameter should be first or last in the C# method. Contrary to the framework's convention of placing the out parameter as the last parameter (e.g. TryParse), here it must be the first parameter, otherwise the call will fail:

[DllImport("my.dll", CallingConvention = CallingConvention.Cdecl)]
static public extern void readText(out DataBlock dataBlock, string dataArray, int bytesToRead);

Upvotes: 0

Luaan
Luaan

Reputation: 63752

The struct is fine - it fulfills all the rules to be used as a return value in P/Invoke.

You need to use the proper calling convention (in your case, CallingConvention.Cdecl).

There's also an extra optimization that some compilers use, where a big structure (such as yours) is passed by reference, rather than returned. You can replicate this in C# like so:

static public extern void getDefaultPass(out DataBlock data);

Upvotes: 1

Related Questions