当_Out_参数可以为NULL或非NULL时,使用P / Invoke在C#中调用Win API

时间:2014-11-27 06:49:47

标签: c# winapi pinvoke optional-parameters

我是C#的新手,我正在通过编写一些小工具来学习C#。

有许多Windows API,其指针参数可能为NULL或非NULL取决于不同的用例。我的问题是,如何在DllImport中声明这些参数?

例如:

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
);

FlagsQDC_DATABASE_CURRENT时,最后一个参数pCurrentTopologyId不得为空。 当Flags为其他值时,pCurrentTopologyId必须为空。

如果参数声明为“out IntPtr”或“ref IntPtr”,则API可以更改引用的内存。但是,如果根据API的要求传递IntPtr.Zero,API调用将返回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
);

如果参数声明为“IntPtr”,则IntPtr.Zero可以作为NULL指针传递。但是,如果传递IntPtr,API调用也将返回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
);

我不希望声明不同版本的extern函数,特别是当不同参数组合的数量可以很多时。

有什么建议吗?

1 个答案:

答案 0 :(得分:0)

如果你有一个枚举的可选参数,就像你在这里一样,那么我认为除了声明两个独立的重载之外别无选择。用于传递null的重载声明了如下参数:

IntPtr pCurrentTopologyId

调用此重载时,您始终会传递IntPtr.Zero

另一个重载,即传递非null值时调用的重载声明了如下参数:

out DISPLAYCONFIG_TOPOLOGY_ID pCurrentTopologyId

其中DISPLAYCONFIG_TOPOLOGY_ID是基本类型为enum的C#int

您的代码错误地获取了第二个变体,因为您将其声明为out IntPtr pCurrentTopologyId。好IntPtr在64位上是错误的大小(8个字节而不是4个)。

如果您只想声明一个p / invoke并且不使用重载,那么您只需将工作转移到其他地方。要做到这一点,你必须选择第一个选项:

IntPtr pCurrentTopologyId

想要传递IntPtr.Zero时很好。但是当您需要传递枚举变量的地址时,您需要使用GCHandleAddrOfPinnedObject来固定变量。所有完美可能但更多的样板。所以,请你选择!