Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshal array of struct and IntPtr

Tags:

c#

struct

I am very very close to a solution to my issue, but I need a little guidance on the finishing touches to make everything work. Over the last week, I have learned a lot about

Just as a reference, I asked a similar question last week about the same topic, but due to a HUGE oversight on my side, I was asking the wrong question.

I am trying to use an unmanaged c++ dll that is the API for communication with a connected device. I have successfully created the wrapper, and most of the other function calls, but this last one is driving me crazy.

For some background info (probably not required to answer this question - and keep in mind my basic thought process was flawed at the time) is here: Calling un-managed code with pointer (Updated)

In my original question, I was asking about creating an IntPtr to a struct(1) containing an array of struct(2).... In fact, struct(1) does not contain an array at all, it contains a Pointer to an array.

Here is the documentation from the API I am trying to implement as a reference:

extern “C” long WINAPI PassThruIoctl
(
    unsigned long ChannelID,
    unsigned long IoctlID,
    void *pInput,
    void *pOutput
)


// *pInput Points to the structure SCONFIG_LIST, which is defined as follows:
// *pOutput is not used in this function and is a null pointer

typedef struct
{
    unsigned long NumOfParams; /* number of SCONFIG elements */
    SCONFIG *ConfigPtr; /* array of SCONFIG */
} SCONFIG_LIST

// Where:
// NumOfParms is an INPUT, which contains the number of SCONFIG elements in the array pointed to by ConfigPtr.
// ConfigPtr is a pointer to an array of SCONFIG structures.

// The structure SCONFIG is defined as follows:
typedef struct
{
    unsigned long Parameter; /* name of parameter */
    unsigned long Value; /* value of the parameter */
} SCONFIG

Here are my struct definitions as I currently have them defined

[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1
public struct SConfig 
{
   public UInt32 Parameter;
   public UInt32 Value;
}



[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1
public struct SConfig_List
{
    public UInt32 NumOfParams;
    public IntPtr configPtr;

    public SConfig_List(UInt32 nParams, SConfig[] config)
    {
        this.NumOfParams = nParams;

        //  I have tried these 2 lines together
        IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams);
        this.configPtr = new IntPtr(temp.ToInt32());

        // I have tried this by itself
        // this.configPtr  = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams);


        // and this line
        // this.configPtr = Marshal.AllocHGlobal(sizeof(SConfig)*(int)nParams);  // this only complies with unsafe struct
    }
}

Here is the snippet of code that sets up these into variables and calls the function which interfaces with the API

SConfig[] arr_sconfig;
arr_sconfig = new SConfig[1];

arr_sconfig[0].Parameter = 0x04;
arr_sconfig[0].Value = 0xF1;
SConfig_List myConfig = new SConfig_List(1, arr_sconfig);

m_status = m_APIBox.SetConfig(m_channelId, ref myConfig);

And Finally, here is the function that passes this information on to the dll:

public APIErr SetConfig(int channelId, ref SConfig_List config)
{
    unsafe
    {
        IntPtr output = IntPtr.Zero; // Output not used, just a null pointer for this function

        //   These 2 lines of code cause API dll to yell about invalid pointer (C# is happy but it doesnt work with dll)
        //   IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(J_2534_API.SConfig_List)));
        //   IntPtr input = new IntPtr(temp.ToInt32());

        //  The following 2 lines only compile with unsafe - but API dll "likes" the pointer - but I am not getting desired results
        //  The dll is properly getting the Number of Parameters (NumOfParams), but the data within the array is not being
        //  referenced correctly
        IntPtr input = Marshal.AllocHGlobal(sizeof(SConfig_List)); // Only works with unsafe
        Marshal.StructureToPtr(config, input, true);

        APIErr returnVal = (APIErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);

        return returnVal;
    }
}

Before I realized my huge oversight in the basic idea, I never could even get C# to be happy, either I had the syntax wrong and the code would not compile, or it would compile but gave a runtime error (never even calling the external dll)

These problems are behind me. The code compiles fine now, and executes without any runtime errors. In addition, the dll I am using has a logging function, so I can see that I am actually calling the correct function. I am even passing some data correctly to it. The NumOfParams variable is being read properly by the function, but the array of structs appears to be garbage data.

I have been reading a very helpful article here: http://limbioliong.wordpress.com/2012/02/28/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-1/

And I have been reading MSDN, but so far I have not been able to come across the magic combination of code that makes this thing work, so I am reaching out one more time for help.

I am pretty sure my issue is that I am not setting the IntPtr variables up correctly, and they are not pointing to the correct area in memory.

I have tried various combinations of unsafe and safe code. Also, I know that I am not explicitly freeing the memory at this point, so pointers on that would be helpful as well. In my research, here are some ideas that might work, I just cant seem to get them just right

[MarshalAs(UnmanagedType.LPWStr)]

[MarshalAs(UnmanagedType.ByValArray, SizeConst=...)]

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst=100)]

One final question: I assume that since the c++ declaration is unsigned long, then the UInt32 is the proper type in C#?

like image 285
mleega Avatar asked Oct 18 '25 14:10

mleega


1 Answers

Lots of things wrong with the SConfig_List constructor in the snippet. Biggest issue is that it allocates memory for the array but completely forgets to copy the structures as well. So the native code gets the pointer okay but looks at uninitialized memory. You can fix it like this:

    public SConfig_List(SConfig[] config) {
        this.NumOfParams = config.Length;
        int size = Marshal.SizeOf(config[0]);
        IntPtr mem = this.configPtr = Marshal.AllocHGlobal(size * config.Length);
        for (int ix = 0; ix < config.Length; ++ix) {
            Marshal.StructureToPtr(config[ix], mem, false);
            mem = new IntPtr((long)mem + size);
        }
    }

Be sure to not forget to call Marshal.FreeHGlobal() again after the call completed or you'll leak the memory.

The simplest way to avoid marshaling the SConfig_List is to just provide a better declaration for the C function:

[DllImport(...)]
private static extern ApiErr PassThruIoctl(
    int channelID, 
    uint ioctlID,
    ref SConfig_List input,
    IntPtr output);

Which makes a decent wrapper method look like this:

public APIErr SetConfig(int channelId, SConfig[] config) {
    var list = new SConfig_List(config);
    var retval = PassThruIoctl(channelId, Ioctl.SET_CONFIG, ref list, IntPtr.Zero);
    Marshal.FreeHGlobal(list.configPtr);
    return retval;
}
like image 172
Hans Passant Avatar answered Oct 21 '25 02:10

Hans Passant