Search code examples
c#c++pinvokemarshalling

Marshalling LPDWORD into C# for P/Invoke


I have a C++ function (WinAPI) with the following signature:

__declspec(dllimport) LPDWORD WINAPI MyFunction(HDET hDet, WORD wStartChan, WORD wNumChans,
    LPDWORD lpdwBuffer, LPWORD lpwRetChans, LPDWORD lpdwDataMask,
    LPDWORD lpdwROIMask, LPCSTR lpszAuth);

I have one example of it being called in C++, which is as follows:

::MyFunction((HDET)pUConn->GetHDET(), (WORD)lStartChan, (WORD)lNumChans,
                &(m_DataCache[lView].pdwChans[lStartChan]), &wRetChans,
                &(m_DataCache[lView].dwDataMask), &(m_DataCache[lView].dwROIMask),
                pUConn->GetAuthorization());
//m_DataCache[lView].pdwChans is an array of DWORDs. (Declared as DWORD * pdwChans;)

Now, I can marshal all of the parameters except the 4th. It's a pointer to an array of DWORDs. That makes me think int **. However, when I pass in a ref int[] I get back null. Here's my C# declaration:

[System.Runtime.InteropServices.DllImport("MCBCIO32.dll", EntryPoint = "MIOGetData")]
public static extern IntPtr MIOGetData(int hDet, ushort sChan, ushort nChan,
ref int[] Buffer, ref short rchan, ref int datamask, ref int roimask, string auth);

I've also tried IntPtr and a simple int[]. Int[] returns an array of 0s. IntPtr is interesting, (interesting but wrong) - it gives me back weird values, non-zero but clearly incorrect. And they change every time I do another run, so I think I'm wandering around in random memory. I marshal memory for the buffer and copy it over as follows:

/***Snip***/
int arrsize = 100;
int[] buffer = new int[arrsize];

short rchan = -1;
IntPtr p = Marshal.AllocHGlobal(arrsize * sizeof(int));
int datamask = 0, roimask = 0;
MCBWrapper.FUNC(hDet, 0, 1, p, ref rchan, ref datamask, ref roimask, "");
Marshal.Copy(p, buffer, 0, arrsize);
Marshal.FreeHGlobal(p);
p = IntPtr.Zero;
for (int r = 0; r < arrsize; r++)
{
    Console.Write($"{buffer[r]} ");
}

In this case, the buffer is marshaled as IntPtr.
I have tried a few other ways of marshaling but to little effect. IntPtr seems the most promising. I've also tried using a ref to an IntPtr (with nearly identical code), and it gives me similar results to a single indirection.

I'd appreciate any thoughts on this - I'm not used to working with marshaling.


Solution

  • It's not a ** pointer-to-pointer. It's just a pointer. So you need to pass in a buffer. So it's int[] not ref int[].

    The buffer size looks like it's being passed in as nChan, and the used length of the buffer is passed back in rchan. You specify that using SizeParamIndex.

    You also need to specify LPStr on the final string parameter.

    The usage of the other two ref parameters are unclear, they should probably be either [In] in or [Out] out. Also HDET is unclear what size it is.

    [DllImport("MCBCIO32.dll")]
    public static extern IntPtr MIOGetData(
      int hDet,
      ushort sChan,
      ushort nChan,
      [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] int[] Buffer,
      [Out] out short rchan,
      ref int datamask,
      ref int roimask,
      [MarshalAs(UnmanagedType.LPStr)] string auth);
    

    You use it like this

    int arrsize = 100;
    int[] buffer = new int[arrsize];
    int datamask = 0, roimask = 0;
    
    var result = MCBWrapper.FUNC(hDet, 0, 1, buffer, out var rchan, ref datamask, ref roimask, "");
    
    for (int r = 0; r < arrsize; r++)
    {
        Console.Write($"{buffer[r]} ");
    }
    

    Note that if you did need to use Marshal.AllocHGlobal, you must make sure to free the memory in a finally to prevent leaks.