0

I'm new to C# and I'm learning C# by write some small tools.

There are many Windows API whose pointer parameters could be NULL or non-NULL depends on different use case. My question is, how to declare such parameters in DllImport?

For example:

LONG QueryDisplayConfig(
  _In_       UINT32 Flags,
  _Inout_    UINT32 *pNumPathArrayElements,
  _Out_      DISPLAYCONFIG_PATH_INFO *pPathInfoArray,
  _Inout_    UINT32 *pNumModeInfoArrayElements,
  _Out_      DISPLAYCONFIG_MODE_INFO *pModeInfoArray,
  _Out_opt_  DISPLAYCONFIG_TOPOLOGY_ID *pCurrentTopologyId
);

When Flags is QDC_DATABASE_CURRENT, the last parameter pCurrentTopologyId must NOT be null. When Flags is other value, pCurrentTopologyId must be null.

If the parameter is declared as "out IntPtr" or "ref IntPtr", in order that the API can change the referred memory. However if passing it IntPtr.Zero as required by the API, the API call will return ERROR_NOACCESS.

[DllImport(user32_FileName, SetLastError=true)]
internal static extern int QueryDisplayConfig(
    [In] QDC_FLAGS Flags,
    [In, Out] ref UInt32 pNumPathArrayElements, 
    [Out] DISPLAYCONFIG_PATH_INFO[] pPathInfoArray,
    [In, Out] ref UInt32 pNumModeInfoArrayElements, 
    [Out] DISPLAYCONFIG_MODE_INFO[] pModeInfoArray,
    out IntPtr pCurrentTopologyId
);

If the parameter is declared as "IntPtr", then IntPtr.Zero can be passed as the NULL pointer. However if passing a IntPtr, the API call will also return ERROR_NOACCESS.

[DllImport(user32_FileName, SetLastError=true)]
internal static extern int QueryDisplayConfig(
    [In] QDC_FLAGS Flags,
    [In, Out] ref UInt32 pNumPathArrayElements, 
    [Out] DISPLAYCONFIG_PATH_INFO[] pPathInfoArray,
    [In, Out] ref UInt32 pNumModeInfoArrayElements, 
    [Out] DISPLAYCONFIG_MODE_INFO[] pModeInfoArray,
    IntPtr pCurrentTopologyId
);

I don't expect declaring different version of extern functions, especially when the number of different parameters combination can be numerous.

Any suggestion?

River
  • 8,585
  • 14
  • 54
  • 67
Ivellios
  • 100
  • 7
  • Which function specifically are you calling, and what's the declaration you use to call it? Did you encounter a specific problem or are you speculating? – Jeroen Mostert Nov 27 '14 at 08:18
  • QueryDisplayConfig. I add the declaration as above. I met actual problem when call the API with different parameters. – Ivellios Nov 28 '14 at 03:14

1 Answers1

0

When you have an optional parameter that is an enum, as you have here, then I think there is little alternative but to declare two separate overloads. The overload that you use to pass null declares the parameter like this:

IntPtr pCurrentTopologyId

You always pass IntPtr.Zero when calling this overload.

The other overload, the one that you call when you pass a non-null value declares the parameter like this:

out DISPLAYCONFIG_TOPOLOGY_ID pCurrentTopologyId

where DISPLAYCONFIG_TOPOLOGY_ID is a C# enum with base type of int.

Your code gets this second variant wrong because you declare it as out IntPtr pCurrentTopologyId. Well IntPtr is the wrong size on 64 bit (8 bytes rather than 4).

If you wish to declare just a single p/invoke and not use overloads then you just transfer the work elsewhere. To do that you have to pick the first option:

IntPtr pCurrentTopologyId

Fine when you want to pass IntPtr.Zero. But when you need to pass the address of an enum variable you need to pin your variable using GCHandle and AddrOfPinnedObject. All perfectly possible but yet more boilerplate. So, take your pick!

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490