Added nullability to PortableDeviceApi and tests

nullableenabled
David Hall 2023-09-24 20:16:38 -06:00
parent 718d241007
commit 99e7fa0a78
6 changed files with 8884 additions and 8864 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -3,265 +3,265 @@ using static Vanara.PInvoke.SetupAPI;
namespace Vanara.PInvoke; namespace Vanara.PInvoke;
public static partial class PortableDeviceApi public static partial class PortableDeviceApi
{ {
/// <summary> /// <summary>
/// The <c>IConnectionRequestCallback</c> interface defines a single callback method. A Windows Portable Devices (WPD) application /// The <c>IConnectionRequestCallback</c> interface defines a single callback method. A Windows Portable Devices (WPD) application
/// implements this optional Component Object Model (COM) interface to receive notifications about completed requests and to cancel /// implements this optional Component Object Model (COM) interface to receive notifications about completed requests and to cancel
/// pending requests. The requests are sent using the <c>IPortableDeviceConnector::Connect</c> and /// pending requests. The requests are sent using the <c>IPortableDeviceConnector::Connect</c> and
/// <c>IPortableDeviceConnector::Disconnect</c> methods. /// <c>IPortableDeviceConnector::Disconnect</c> methods.
/// </summary> /// </summary>
// https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/iconnectionrequestcallback // https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/iconnectionrequestcallback
[PInvokeData("portabledeviceconnectapi.h")] [PInvokeData("portabledeviceconnectapi.h")]
[ComImport, Guid("272C9AE0-7161-4AE0-91BD-9F448EE9C427"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [ComImport, Guid("272C9AE0-7161-4AE0-91BD-9F448EE9C427"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IConnectionRequestCallback public interface IConnectionRequestCallback
{ {
/// <summary> /// <summary>
/// The <c>OnComplete</c> method notifies an application that a previously scheduled Connect or Disconnect request to the /// The <c>OnComplete</c> method notifies an application that a previously scheduled Connect or Disconnect request to the
/// MTP/Bluetooth device has completed /// MTP/Bluetooth device has completed
/// </summary> /// </summary>
/// <param name="hrStatus">The status of the request to connect or disconnect a given device.</param> /// <param name="hrStatus">The status of the request to connect or disconnect a given device.</param>
/// <returns> /// <returns>
/// <para>The method returns an <c>HRESULT</c>. Possible values include, but are not limited to, those in the following table.</para> /// <para>The method returns an <c>HRESULT</c>. Possible values include, but are not limited to, those in the following table.</para>
/// <list type="table"> /// <list type="table">
/// <listheader> /// <listheader>
/// <term>Return code</term> /// <term>Return code</term>
/// <term>Description</term> /// <term>Description</term>
/// </listheader> /// </listheader>
/// <item> /// <item>
/// <term>S_OK</term> /// <term>S_OK</term>
/// <term>The method succeeded.</term> /// <term>The method succeeded.</term>
/// </item> /// </item>
/// </list> /// </list>
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// An application implements the <c>IConnectionRequestCallback</c> interface to receive notifications about completed requests /// An application implements the <c>IConnectionRequestCallback</c> interface to receive notifications about completed requests
/// and to cancel pending requests. /// and to cancel pending requests.
/// </para> /// </para>
/// <para> /// <para>
/// Windows Portable Devices (WPD) calls this method to notify an application that a previously scheduled request has completed. /// Windows Portable Devices (WPD) calls this method to notify an application that a previously scheduled request has completed.
/// Each request can be tracked and canceled by its application-supplied callback. Therefore, if the application needs to send /// Each request can be tracked and canceled by its application-supplied callback. Therefore, if the application needs to send
/// multiple requests at the same time using the same <c>IPortableDeviceConnector</c> object, each request should be passed a /// multiple requests at the same time using the same <c>IPortableDeviceConnector</c> object, each request should be passed a
/// unique <c>IConnectionRequestCallback</c> object as the input parameter to the <c>IPortableDeviceConnector::Connect</c> and /// unique <c>IConnectionRequestCallback</c> object as the input parameter to the <c>IPortableDeviceConnector::Connect</c> and
/// <c>IPortableDeviceConnector::Disconnect</c> methods. /// <c>IPortableDeviceConnector::Disconnect</c> methods.
/// </para> /// </para>
/// </remarks> /// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/iconnectionrequestcallback-oncomplete HRESULT OnComplete( [in] HRESULT // https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/iconnectionrequestcallback-oncomplete HRESULT OnComplete( [in] HRESULT
// hrStatus ); // hrStatus );
[PreserveSig] [PreserveSig]
HRESULT OnComplete(HRESULT hrStatus); HRESULT OnComplete(HRESULT hrStatus);
} }
/// <summary> /// <summary>
/// The <c>IEnumPortableDeviceConnectors</c> interface supports the enumeration of <c>IPortableDeviceConnector</c> interfaces, /// The <c>IEnumPortableDeviceConnectors</c> interface supports the enumeration of <c>IPortableDeviceConnector</c> interfaces,
/// representing MTP/Bluetooth devices that were paired with the PC. Note that this will retrieve all previously-paired devices, /// representing MTP/Bluetooth devices that were paired with the PC. Note that this will retrieve all previously-paired devices,
/// including devices that are paired but disconnected. To determine if a device is still connected, query the /// including devices that are paired but disconnected. To determine if a device is still connected, query the
/// <c>DEVPKEY_MTPBTH_IsConnected</c> property for that device. /// <c>DEVPKEY_MTPBTH_IsConnected</c> property for that device.
/// </summary> /// </summary>
// https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors // https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors
[PInvokeData("portabledeviceconnectapi.h")] [PInvokeData("portabledeviceconnectapi.h")]
[ComImport, Guid("BFDEF549-9247-454F-BD82-06FE80853FAA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), CoClass(typeof(EnumBthMtpConnectors))] [ComImport, Guid("BFDEF549-9247-454F-BD82-06FE80853FAA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), CoClass(typeof(EnumBthMtpConnectors))]
public interface IEnumPortableDeviceConnectors : Vanara.Collections.ICOMEnum<IPortableDeviceConnector> public interface IEnumPortableDeviceConnectors : Vanara.Collections.ICOMEnum<IPortableDeviceConnector>
{ {
/// <summary> /// <summary>
/// The <c>Next</c> method retrieves the next one or more <c>IPortableDeviceConnector</c> objects in the enumeration sequence. /// The <c>Next</c> method retrieves the next one or more <c>IPortableDeviceConnector</c> objects in the enumeration sequence.
/// </summary> /// </summary>
/// <param name="cRequested"> /// <param name="cRequested">
/// The number of requested devices. This value also indicates the number of elements in the caller-allocated array specified by /// The number of requested devices. This value also indicates the number of elements in the caller-allocated array specified by
/// the pConnectors parameter. /// the pConnectors parameter.
/// </param> /// </param>
/// <param name="pConnectors"> /// <param name="pConnectors">
/// An array of <c>IPortableDeviceConnector</c> pointers, each specifying a paired MTP Bluetooth device. The caller must /// An array of <c>IPortableDeviceConnector</c> pointers, each specifying a paired MTP Bluetooth device. The caller must
/// allocate an array of <c>IPortableDeviceConnector</c> pointers, with the array length specified by the cRequested parameter. /// allocate an array of <c>IPortableDeviceConnector</c> pointers, with the array length specified by the cRequested parameter.
/// On successful return, the caller must free both the array and the returned pointers. The <c>IPortableDeviceConnector</c> /// On successful return, the caller must free both the array and the returned pointers. The <c>IPortableDeviceConnector</c>
/// interfaces are freed by calling the <c>IUnknown::Release</c> method. /// interfaces are freed by calling the <c>IUnknown::Release</c> method.
/// </param> /// </param>
/// <param name="pcFetched"> /// <param name="pcFetched">
/// The number of <c>IPortableDeviceConnector</c> interfaces that are actually retrieved. If no <c>IPortableDeviceConnector</c> /// The number of <c>IPortableDeviceConnector</c> interfaces that are actually retrieved. If no <c>IPortableDeviceConnector</c>
/// interfaces are retrieved and the return value is <c>S_FALSE</c>, there are no more <c>IPortableDeviceConnector</c> /// interfaces are retrieved and the return value is <c>S_FALSE</c>, there are no more <c>IPortableDeviceConnector</c>
/// interfaces to enumerate. /// interfaces to enumerate.
/// </param> /// </param>
/// <returns> /// <returns>
/// <para>The method returns an <c>HRESULT</c>. Possible values include, but are not limited to, those in the following table.</para> /// <para>The method returns an <c>HRESULT</c>. Possible values include, but are not limited to, those in the following table.</para>
/// <list type="table"> /// <list type="table">
/// <listheader> /// <listheader>
/// <term>Return code</term> /// <term>Return code</term>
/// <term>Description</term> /// <term>Description</term>
/// </listheader> /// </listheader>
/// <item> /// <item>
/// <term>S_OK</term> /// <term>S_OK</term>
/// <term>The method succeeded.</term> /// <term>The method succeeded.</term>
/// </item> /// </item>
/// <item> /// <item>
/// <term>S_FALSE</term> /// <term>S_FALSE</term>
/// <term>There are no more MTP Bluetooth devices to enumerate.</term> /// <term>There are no more MTP Bluetooth devices to enumerate.</term>
/// </item> /// </item>
/// </list> /// </list>
/// </returns> /// </returns>
// https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-next HRESULT Next( [in] UINT32 // https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-next HRESULT Next( [in] UINT32
// cRequested, [out] IPortableDeviceConnector **pConnectors, [in, out] UINT32 *pcFetched ); // cRequested, [out] IPortableDeviceConnector **pConnectors, [in, out] UINT32 *pcFetched );
[PreserveSig] [PreserveSig]
HRESULT Next(uint cRequested, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface)] IPortableDeviceConnector[] pConnectors, HRESULT Next(uint cRequested, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface)] IPortableDeviceConnector[] pConnectors,
[In, Out] ref uint pcFetched); [In, Out] ref uint pcFetched);
/// <summary>The <c>Skip</c> method skips the specified number of devices in the enumeration sequence.</summary> /// <summary>The <c>Skip</c> method skips the specified number of devices in the enumeration sequence.</summary>
/// <param name="cConnectors">The number of devices to skip.</param> /// <param name="cConnectors">The number of devices to skip.</param>
/// <returns> /// <returns>
/// <para>The method returns an <c>HRESULT</c>. Possible values include, but are not limited to, those in the following table.</para> /// <para>The method returns an <c>HRESULT</c>. Possible values include, but are not limited to, those in the following table.</para>
/// <list type="table"> /// <list type="table">
/// <listheader> /// <listheader>
/// <term>Return code</term> /// <term>Return code</term>
/// <term>Description</term> /// <term>Description</term>
/// </listheader> /// </listheader>
/// <item> /// <item>
/// <term>S_OK</term> /// <term>S_OK</term>
/// <term>The method succeeded.</term> /// <term>The method succeeded.</term>
/// </item> /// </item>
/// <item> /// <item>
/// <term>S_FALSE</term> /// <term>S_FALSE</term>
/// <term> /// <term>
/// The specified number of devices could not be skipped. One possible cause: The cConnectors parameter specifies more devices /// The specified number of devices could not be skipped. One possible cause: The cConnectors parameter specifies more devices
/// than actually remain in the enumeration sequence. /// than actually remain in the enumeration sequence.
/// </term> /// </term>
/// </item> /// </item>
/// </list> /// </list>
/// </returns> /// </returns>
// https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-skip HRESULT Skip( [in] UINT32 // https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-skip HRESULT Skip( [in] UINT32
// cConnectors ); // cConnectors );
[PreserveSig] [PreserveSig]
HRESULT Skip(uint cConnectors); HRESULT Skip(uint cConnectors);
/// <summary>The <c>Reset</c> method resets the enumeration sequence to the beginning.</summary> /// <summary>The <c>Reset</c> method resets the enumeration sequence to the beginning.</summary>
// https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-reset HRESULT Reset(); // https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-reset HRESULT Reset();
void Reset(); void Reset();
/// <summary>The <c>Clone</c> method creates a copy of the current <c>IEnumPortableDeviceConnectors</c> interface.</summary> /// <summary>The <c>Clone</c> method creates a copy of the current <c>IEnumPortableDeviceConnectors</c> interface.</summary>
/// <returns> /// <returns>
/// The address of a pointer to an <c>IEnumPortableDeviceConnectors</c> interface. The calling application must release this /// The address of a pointer to an <c>IEnumPortableDeviceConnectors</c> interface. The calling application must release this
/// interface when it is done with it. /// interface when it is done with it.
/// </returns> /// </returns>
// https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-clone HRESULT Clone( [out] // https://docs.microsoft.com/en-us/windows/win32/wpd_sdk/ienumportabledeviceconnectors-clone HRESULT Clone( [out]
// IEnumPortableDeviceConnectors **ppEnum ); // IEnumPortableDeviceConnectors **ppEnum );
IEnumPortableDeviceConnectors Clone(); IEnumPortableDeviceConnectors Clone();
} }
/// <summary> /// <summary>
/// The <c>IPortableDeviceConnector</c> interface defines methods used for connection-management and property-retrieval for a paired /// The <c>IPortableDeviceConnector</c> interface defines methods used for connection-management and property-retrieval for a paired
/// MTP/Bluetooth device. /// MTP/Bluetooth device.
/// </summary> /// </summary>
// https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nn-portabledeviceconnectapi-iportabledeviceconnector // https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nn-portabledeviceconnectapi-iportabledeviceconnector
[PInvokeData("portabledeviceconnectapi.h", MSDNShortId = "NN:portabledeviceconnectapi.IPortableDeviceConnector")] [PInvokeData("portabledeviceconnectapi.h", MSDNShortId = "NN:portabledeviceconnectapi.IPortableDeviceConnector")]
[ComImport, Guid("625E2DF8-6392-4CF0-9AD1-3CFA5F17775C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [ComImport, Guid("625E2DF8-6392-4CF0-9AD1-3CFA5F17775C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPortableDeviceConnector public interface IPortableDeviceConnector
{ {
/// <summary>The <c>Connect</c> method sends an asynchronous connection request to the MTP/Bluetooth device.</summary> /// <summary>The <c>Connect</c> method sends an asynchronous connection request to the MTP/Bluetooth device.</summary>
/// <param name="pCallback"> /// <param name="pCallback">
/// A pointer to a IConnectionRequestCallback interface if the application wishes to be notified when the request is complete; /// A pointer to a IConnectionRequestCallback interface if the application wishes to be notified when the request is complete;
/// otherwise, <c>NULL</c>. If multiple requests are being sent simultaneously using the same IPortableDeviceConnector object, a /// otherwise, <c>NULL</c>. If multiple requests are being sent simultaneously using the same IPortableDeviceConnector object, a
/// different instance of the callback object must be used. /// different instance of the callback object must be used.
/// </param> /// </param>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This method will queue a connect request and then return immediately. The connection request will result in a no-op if a /// This method will queue a connect request and then return immediately. The connection request will result in a no-op if a
/// device is already connected. /// device is already connected.
/// </para> /// </para>
/// <para> /// <para>
/// To be notified when the request is complete, applications should provide a pointer to the IConnectionRequestCallback interface. /// To be notified when the request is complete, applications should provide a pointer to the IConnectionRequestCallback interface.
/// </para> /// </para>
/// <para> /// <para>
/// If a previously paired MTP/Bluetooth device is within range, applications can call this method to instantiate the Windows /// If a previously paired MTP/Bluetooth device is within range, applications can call this method to instantiate the Windows
/// Portable Devices (WPD) class driver stack for that device, so that the device can be communicated to using the WPD API. /// Portable Devices (WPD) class driver stack for that device, so that the device can be communicated to using the WPD API.
/// </para> /// </para>
/// </remarks> /// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-connect // https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-connect
// HRESULT Connect( [in, optional] IConnectionRequestCallback *pCallback ); // HRESULT Connect( [in, optional] IConnectionRequestCallback? *pCallback );
void Connect([Optional] IConnectionRequestCallback pCallback); void Connect([Optional] IConnectionRequestCallback? pCallback);
/// <summary>The <c>Disconnect</c> method sends an asynchronous disconnect request to the MTP/Bluetooth device.</summary> /// <summary>The <c>Disconnect</c> method sends an asynchronous disconnect request to the MTP/Bluetooth device.</summary>
/// <param name="pCallback">A pointer to an IConnectionRequestCallback interface.</param> /// <param name="pCallback">A pointer to an IConnectionRequestCallback interface.</param>
/// <remarks> /// <remarks>
/// <para>This method will queue a disconnect request and then return immediately.</para> /// <para>This method will queue a disconnect request and then return immediately.</para>
/// <para> /// <para>
/// To be notified when the request is complete, applications should provide a pointer to the IConnectionRequestCallback /// To be notified when the request is complete, applications should provide a pointer to the IConnectionRequestCallback
/// interface. This will disconnect the MTP/Bluetooth link and remove the Windows Portable Devices (WPD) class driver stack for /// interface. This will disconnect the MTP/Bluetooth link and remove the Windows Portable Devices (WPD) class driver stack for
/// that device. /// that device.
/// </para> /// </para>
/// <para>Once the disconnection completes, the WPD API will no longer enumerate this device.</para> /// <para>Once the disconnection completes, the WPD API will no longer enumerate this device.</para>
/// </remarks> /// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-disconnect // https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-disconnect
// HRESULT Disconnect( [in] IConnectionRequestCallback *pCallback ); // HRESULT Disconnect( [in] IConnectionRequestCallback *pCallback );
void Disconnect(IConnectionRequestCallback pCallback); void Disconnect(IConnectionRequestCallback pCallback);
/// <summary> /// <summary>
/// The <c>Cancel</c> method cancels a pending request to connect or disconnect an MTP/Bluetooth device. The callback object is /// The <c>Cancel</c> method cancels a pending request to connect or disconnect an MTP/Bluetooth device. The callback object is
/// used to identify the request. This method returns immediately, and the request will be cancelled asynchronously. /// used to identify the request. This method returns immediately, and the request will be cancelled asynchronously.
/// </summary> /// </summary>
/// <param name="pCallback">A pointer to an IConnectionRequestCallback interface. This value cannot be <c>NULL</c>.</param> /// <param name="pCallback">A pointer to an IConnectionRequestCallback interface. This value cannot be <c>NULL</c>.</param>
// https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-cancel // https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-cancel
// HRESULT Cancel( [in] IConnectionRequestCallback *pCallback ); // HRESULT Cancel( [in] IConnectionRequestCallback *pCallback );
void Cancel(IConnectionRequestCallback pCallback); void Cancel(IConnectionRequestCallback pCallback);
/// <summary>The <c>GetProperty</c> method retrieves a property for the given MTP/Bluetooth Bus Enumerator device.</summary> /// <summary>The <c>GetProperty</c> method retrieves a property for the given MTP/Bluetooth Bus Enumerator device.</summary>
/// <param name="pPropertyKey">A pointer to a property key for the requested property.</param> /// <param name="pPropertyKey">A pointer to a property key for the requested property.</param>
/// <param name="pPropertyType">A pointer to a property type.</param> /// <param name="pPropertyType">A pointer to a property type.</param>
/// <param name="ppData">The address of a pointer to the property data.</param> /// <param name="ppData">The address of a pointer to the property data.</param>
/// <param name="pcbData">A pointer to the size (in bytes) of the property data.</param> /// <param name="pcbData">A pointer to the size (in bytes) of the property data.</param>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// The properties retrieved by this method are set on the device node. An example property key is /// The properties retrieved by this method are set on the device node. An example property key is
/// <c>DEVPKEY_MTPBTH_IsConnected</c>, which indicates whether the device is currently connected. /// <c>DEVPKEY_MTPBTH_IsConnected</c>, which indicates whether the device is currently connected.
/// </para> /// </para>
/// <para> /// <para>
/// Valid values for the pPropertyType parameter are system-defined base data types of the unified device property model. The /// Valid values for the pPropertyType parameter are system-defined base data types of the unified device property model. The
/// data-type names start with the prefix <c>DEVPROP_TYPE_</c>. /// data-type names start with the prefix <c>DEVPROP_TYPE_</c>.
/// </para> /// </para>
/// <para> /// <para>
/// Once the application no longer needs the property data specified by the ppData parameter, it must call <c>CoTaskMemAlloc</c> /// Once the application no longer needs the property data specified by the ppData parameter, it must call <c>CoTaskMemAlloc</c>
/// to free this data. /// to free this data.
/// </para> /// </para>
/// <para>Examples</para> /// <para>Examples</para>
/// <para>The following example shows how to read the DEVPKEY_MTPBTH_IsConnected property for a paired MTP/Bluetooth device.</para> /// <para>The following example shows how to read the DEVPKEY_MTPBTH_IsConnected property for a paired MTP/Bluetooth device.</para>
/// <para> /// <para>
/// <code>#include &lt;devpkey.h&gt; #include &lt;PortableDeviceConnectAPI.h&gt; HRESULT IsDeviceConnected( __in IPortableDeviceConnector* pDevice, __out BOOL* pIsConnected) { DEVPROPTYPE typeGet; BYTE* pDataGet; UINT32 cbDataGet; *pbIsConnected = FALSE; HRESULT hr = pDevice -&gt;GetProperty(&amp;DEVPKEY_MTPBTH_IsConnected, &amp;typeGet, &amp;pDataGet, &amp;cbDataGet); if (SUCCEEDED(hr)) { DEVPROP_BOOLEAN bIsConnected = *((DEVPROP_BOOLEAN*)pDataGet); if (bIsConnected == DEVPROP_TRUE) { * pIsConnected = TRUE; } // Release memory allocated by GetProperty CoTaskMemFree(pDataGet); } return hr; }</code> /// <code>#include &lt;devpkey.h&gt; #include &lt;PortableDeviceConnectAPI.h&gt; HRESULT IsDeviceConnected( __in IPortableDeviceConnector* pDevice, __out BOOL* pIsConnected) { DEVPROPTYPE typeGet; BYTE* pDataGet; UINT32 cbDataGet; *pbIsConnected = FALSE; HRESULT hr = pDevice -&gt;GetProperty(&amp;DEVPKEY_MTPBTH_IsConnected, &amp;typeGet, &amp;pDataGet, &amp;cbDataGet); if (SUCCEEDED(hr)) { DEVPROP_BOOLEAN bIsConnected = *((DEVPROP_BOOLEAN*)pDataGet); if (bIsConnected == DEVPROP_TRUE) { * pIsConnected = TRUE; } // Release memory allocated by GetProperty CoTaskMemFree(pDataGet); } return hr; }</code>
/// </para> /// </para>
/// </remarks> /// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-getproperty // https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-getproperty
// HRESULT GetProperty( [in] const DEVPROPKEY *pPropertyKey, [out] DEVPROPTYPE *pPropertyType, [out] BYTE **ppData, [out] UINT32 // HRESULT GetProperty( [in] const DEVPROPKEY *pPropertyKey, [out] DEVPROPTYPE *pPropertyType, [out] BYTE **ppData, [out] UINT32
// *pcbData ); // *pcbData );
void GetProperty(in DEVPROPKEY pPropertyKey, out DEVPROPTYPE pPropertyType, out SafeCoTaskMemHandle ppData, out uint pcbData); void GetProperty(in DEVPROPKEY pPropertyKey, out DEVPROPTYPE pPropertyType, out SafeCoTaskMemHandle ppData, out uint pcbData);
/// <summary>The <c>SetProperty</c> method sets the given property on the MTP/Bluetooth Bus Enumerator device.</summary> /// <summary>The <c>SetProperty</c> method sets the given property on the MTP/Bluetooth Bus Enumerator device.</summary>
/// <param name="pPropertyKey">A pointer to a property key for the given property.</param> /// <param name="pPropertyKey">A pointer to a property key for the given property.</param>
/// <param name="PropertyType">The property type.</param> /// <param name="PropertyType">The property type.</param>
/// <param name="pData">A pointer to the property data.</param> /// <param name="pData">A pointer to the property data.</param>
/// <param name="cbData">The size (in bytes) of the property data.</param> /// <param name="cbData">The size (in bytes) of the property data.</param>
/// <remarks>Before calling this method, an application must verify that it has Administrator user rights.</remarks> /// <remarks>Before calling this method, an application must verify that it has Administrator user rights.</remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-setproperty // https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-setproperty
// HRESULT SetProperty( [in] const DEVPROPKEY *pPropertyKey, [in] DEVPROPTYPE PropertyType, [in] const BYTE *pData, [in] UINT32 // HRESULT SetProperty( [in] const DEVPROPKEY *pPropertyKey, [in] DEVPROPTYPE PropertyType, [in] const BYTE *pData, [in] UINT32
// cbData ); // cbData );
void SetProperty(in DEVPROPKEY pPropertyKey, DEVPROPTYPE PropertyType, [In] IntPtr pData, [In] uint cbData); void SetProperty(in DEVPROPKEY pPropertyKey, DEVPROPTYPE PropertyType, [In] IntPtr pData, [In] uint cbData);
/// <summary>The <c>GetPnPID</c> method retrieves the connector's Plug and Play (PnP) device identifier.</summary> /// <summary>The <c>GetPnPID</c> method retrieves the connector's Plug and Play (PnP) device identifier.</summary>
/// <returns>The PnP device identifier.</returns> /// <returns>The PnP device identifier.</returns>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// The identifier retrieved by this method corresponds to a handle to the MTP/Bluetooth Bus Enumerator device node that /// The identifier retrieved by this method corresponds to a handle to the MTP/Bluetooth Bus Enumerator device node that
/// receives connect and disconnect IOCTL requests for a paired MTP/Bluetooth device. Applications can use this identifier with /// receives connect and disconnect IOCTL requests for a paired MTP/Bluetooth device. Applications can use this identifier with
/// the SetupAPI functions to access the device node. /// the SetupAPI functions to access the device node.
/// </para> /// </para>
/// <para> /// <para>
/// Once the application no longer needs the identifier specified by the ppwszPnPID parameter, it must call the /// Once the application no longer needs the identifier specified by the ppwszPnPID parameter, it must call the
/// <c>CoTaskMemAlloc</c> function to free the identifier. /// <c>CoTaskMemAlloc</c> function to free the identifier.
/// </para> /// </para>
/// </remarks> /// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-getpnpid // https://docs.microsoft.com/en-us/windows/win32/api/portabledeviceconnectapi/nf-portabledeviceconnectapi-iportabledeviceconnector-getpnpid
// HRESULT GetPnPID( [out] LPWSTR *ppwszPnPID ); // HRESULT GetPnPID( [out] LPWSTR *ppwszPnPID );
[return: MarshalAs(UnmanagedType.LPWStr)] [return: MarshalAs(UnmanagedType.LPWStr)]
string GetPnPID(); string GetPnPID();
} }
/// <summary>EnumBthMtpConnectors Class</summary> /// <summary>EnumBthMtpConnectors Class</summary>
[PInvokeData("portabledeviceconnectapi.h")] [PInvokeData("portabledeviceconnectapi.h")]
[ComImport, Guid("a1570149-e645-4f43-8b0d-409b061db2fc"), ClassInterface(ClassInterfaceType.None)] [ComImport, Guid("a1570149-e645-4f43-8b0d-409b061db2fc"), ClassInterface(ClassInterfaceType.None)]
public class EnumBthMtpConnectors { } public class EnumBthMtpConnectors { }
} }

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.Ole32;
@ -8,142 +9,142 @@ namespace Vanara.Extensions;
/// <summary>Extension methods for classes in Vanara.PInvoke.PortableDeviceApi.</summary> /// <summary>Extension methods for classes in Vanara.PInvoke.PortableDeviceApi.</summary>
public static class PortableDeviceExtensions public static class PortableDeviceExtensions
{ {
private const BindingFlags BindStPub = BindingFlags.Static | BindingFlags.Public; private const BindingFlags BindStPub = BindingFlags.Static | BindingFlags.Public;
private static Dictionary<Type, Dictionary<PROPERTYKEY, PropertyInfo>> gReversePKLookup = new(); private static Dictionary<Type, Dictionary<PROPERTYKEY, PropertyInfo>> gReversePKLookup = new();
/// <summary>Extracts command results from an <see cref="IPortableDeviceValues"/> for a documented <see cref="PROPERTYKEY"/>.</summary> /// <summary>Extracts command results from an <see cref="IPortableDeviceValues"/> for a documented <see cref="PROPERTYKEY"/>.</summary>
/// <param name="results">The results from a call to <c>IPortableDevice.SendCommand</c>.</param> /// <param name="results">The results from a call to <c>IPortableDevice.SendCommand</c>.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param> /// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="parentType">The type in which <paramref name="cmd"/> is defined, if not other than <see cref="Vanara.PInvoke.PortableDeviceApi"/>.</param> /// <param name="parentType">The type in which <paramref name="cmd"/> is defined, if not other than <see cref="Vanara.PInvoke.PortableDeviceApi"/>.</param>
/// <returns>A dictionary containing the result values.</returns> /// <returns>A dictionary containing the result values.</returns>
/// <exception cref="System.InvalidOperationException">Supplied PROPERTYKEY is not a recognized WPD command.</exception> /// <exception cref="System.InvalidOperationException">Supplied PROPERTYKEY is not a recognized WPD command.</exception>
public static IReadOnlyDictionary<PROPERTYKEY, object> ExtractResults(this IPortableDeviceValues results, in PROPERTYKEY cmd, Type parentType = null) public static IReadOnlyDictionary<PROPERTYKEY, object?> ExtractResults(this IPortableDeviceValues results, in PROPERTYKEY cmd, Type? parentType = null)
{ {
if (!cmd.TryGetCommandInfo(out _, out _, out var rAttrs, parentType)) if (!cmd.TryGetCommandInfo(out _, out _, out var rAttrs, parentType))
throw new InvalidOperationException("Supplied PROPERTYKEY is not a recognized WPD command."); throw new InvalidOperationException("Supplied PROPERTYKEY is not a recognized WPD command.");
var ret = new Dictionary<PROPERTYKEY, object>(); var ret = new Dictionary<PROPERTYKEY, object?>();
foreach (var a in rAttrs) foreach (var a in rAttrs)
{ {
PROPVARIANT pv = new(); PROPVARIANT pv = new();
try { pv = results.GetValue(a.Property); } catch { } try { pv = results.GetValue(a.Property); } catch { }
ret.Add(a.Property, pv.Value); ret.Add(a.Property, pv.Value);
} }
return (IReadOnlyDictionary<PROPERTYKEY, object>)ret; return ret;
} }
/// <summary>Sends a command to the device and retrieves the results synchronously.</summary> /// <summary>Sends a command to the device and retrieves the results synchronously.</summary>
/// <param name="device">The portable device.</param> /// <param name="device">The portable device.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param> /// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="pkResult">The PROPERTYKEY of the result value to return.</param> /// <param name="pkResult">The PROPERTYKEY of the result value to return.</param>
/// <returns>The value returned in <paramref name="pkResult"/>.</returns> /// <returns>The value returned in <paramref name="pkResult"/>.</returns>
public static object SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, in PROPERTYKEY pkResult) => public static object? SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, in PROPERTYKEY pkResult) =>
device.SendCommand(cmd).GetValue(pkResult).Value; device.SendCommand(cmd).GetValue(pkResult).Value;
/// <summary>Sends a command to the device and retrieves the results synchronously.</summary> /// <summary>Sends a command to the device and retrieves the results synchronously.</summary>
/// <param name="device">The portable device.</param> /// <param name="device">The portable device.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param> /// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="addParams"> /// <param name="addParams">
/// An action that can optionally be called to manipulate the <see cref="IPortableDeviceValues"/> instance passed to <see /// An action that can optionally be called to manipulate the <see cref="IPortableDeviceValues"/> instance passed to <see
/// cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>. /// cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.
/// </param> /// </param>
/// <returns>The <see cref="IPortableDeviceValues"/> instance returned by <see cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.</returns> /// <returns>The <see cref="IPortableDeviceValues"/> instance returned by <see cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.</returns>
public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, Action<IPortableDeviceValues> addParams = null) public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, Action<IPortableDeviceValues>? addParams = null)
{ {
IPortableDeviceValues cmdParams = new(); IPortableDeviceValues cmdParams = new();
cmdParams.SetCommandPKey(cmd); cmdParams.SetCommandPKey(cmd);
addParams?.Invoke(cmdParams); addParams?.Invoke(cmdParams);
var cmdResults = device.SendCommand(0, cmdParams); var cmdResults = device.SendCommand(0, cmdParams);
cmdResults.GetErrorValue(WPD_PROPERTY_COMMON_HRESULT).ThrowIfFailed(); cmdResults.GetErrorValue(WPD_PROPERTY_COMMON_HRESULT).ThrowIfFailed();
return cmdResults; return cmdResults;
} }
/// <summary>Sends a command to the device and retrieves the results synchronously.</summary> /// <summary>Sends a command to the device and retrieves the results synchronously.</summary>
/// <param name="device">The portable device.</param> /// <param name="device">The portable device.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param> /// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="addParams"> /// <param name="addParams">
/// A list of <see cref="PROPERTYKEY"/>/ <see cref="object"/> tuples representing the property keys and their related values to add /// A list of <see cref="PROPERTYKEY"/>/ <see cref="object"/> tuples representing the property keys and their related values to add
/// to <see cref="IPortableDeviceValues"/>. /// to <see cref="IPortableDeviceValues"/>.
/// </param> /// </param>
/// <returns>The <see cref="IPortableDeviceValues"/> instance returned by <see cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.</returns> /// <returns>The <see cref="IPortableDeviceValues"/> instance returned by <see cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.</returns>
public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, params (PROPERTYKEY key, object value)[] addParams) => public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, params (PROPERTYKEY key, object value)[] addParams) =>
SendCommand(device, cmd, v => SendCommand(device, cmd, v =>
{ {
if (addParams is not null) if (addParams is not null)
foreach ((PROPERTYKEY key, object value) in addParams) foreach ((PROPERTYKEY key, object value) in addParams)
v.SetValue(key, new PROPVARIANT(value)); v.SetValue(key, new PROPVARIANT(value));
}); });
/// <summary>Adds a new enumeration value or overwrites an existing one.</summary> /// <summary>Adds a new enumeration value or overwrites an existing one.</summary>
/// <typeparam name="T">The type of the enumeration value.</typeparam> /// <typeparam name="T">The type of the enumeration value.</typeparam>
/// <param name="vals">The <see cref="IPortableDeviceValues"/> instance.</param> /// <param name="vals">The <see cref="IPortableDeviceValues"/> instance.</param>
/// <param name="key">A <c>PROPERTYKEY</c> that specifies the item to create or overwrite.</param> /// <param name="key">A <c>PROPERTYKEY</c> that specifies the item to create or overwrite.</param>
/// <param name="enumVal">The enum value.</param> /// <param name="enumVal">The enum value.</param>
/// <remarks> /// <remarks>
/// If an existing value has the same key that is specified by the key parameter, it overwrites the existing value without any /// If an existing value has the same key that is specified by the key parameter, it overwrites the existing value without any
/// warning. The existing key memory is released appropriately. /// warning. The existing key memory is released appropriately.
/// </remarks> /// </remarks>
public static void SetEnumValue<T>(this IPortableDeviceValues vals, in PROPERTYKEY key, T enumVal) where T : Enum, IConvertible => public static void SetEnumValue<T>(this IPortableDeviceValues vals, in PROPERTYKEY key, T enumVal) where T : Enum, IConvertible =>
vals.SetValue(key, new PROPVARIANT(Convert.ChangeType(enumVal, Enum.GetUnderlyingType(typeof(T))))); vals.SetValue(key, new PROPVARIANT(Convert.ChangeType(enumVal, Enum.GetUnderlyingType(typeof(T)))));
/// <summary>Tries to get the command information associated with a provided Command <c>PROPERTYKEY</c>.</summary> /// <summary>Tries to get the command information associated with a provided Command <c>PROPERTYKEY</c>.</summary>
/// <param name="key">The <see cref="PROPERTYKEY"/> of the WPD command.</param> /// <param name="key">The <see cref="PROPERTYKEY"/> of the WPD command.</param>
/// <param name="cmd">The <see cref="WPDCommandAttribute"/> instance with detail about the command.</param> /// <param name="cmd">The <see cref="WPDCommandAttribute"/> instance with detail about the command.</param>
/// <param name="param"> /// <param name="param">
/// An array of <see cref="WPDCommandParamAttribute"/> instances representing valid parameters for <paramref name="key"/>. /// An array of <see cref="WPDCommandParamAttribute"/> instances representing valid parameters for <paramref name="key"/>.
/// </param> /// </param>
/// <param name="result"> /// <param name="result">
/// An array of <see cref="WPDCommandResultAttribute"/> instances representing valid results for <paramref name="key"/>. /// An array of <see cref="WPDCommandResultAttribute"/> instances representing valid results for <paramref name="key"/>.
/// </param> /// </param>
/// <param name="parentType">The type in which <paramref name="key"/> is defined, if not other than <see cref="Vanara.PInvoke.PortableDeviceApi"/>.</param> /// <param name="parentType">The type in which <paramref name="key"/> is defined, if not other than <see cref="Vanara.PInvoke.PortableDeviceApi"/>.</param>
/// <returns> /// <returns>
/// <see langword="true"/> if <paramref name="key"/> is a valid command in <paramref name="parentType"/>; <see langword="false"/> otherwise. /// <see langword="true"/> if <paramref name="key"/> is a valid command in <paramref name="parentType"/>; <see langword="false"/> otherwise.
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// The reflection based lookup is cached so that subsequent lookups are accelerated. As such, expect a slower response the first /// The reflection based lookup is cached so that subsequent lookups are accelerated. As such, expect a slower response the first
/// time this method is called with each unique <paramref name="parentType"/>. /// time this method is called with each unique <paramref name="parentType"/>.
/// </remarks> /// </remarks>
public static bool TryGetCommandInfo(this PROPERTYKEY key, out WPDCommandAttribute cmd, out WPDCommandParamAttribute[] param, out WPDCommandResultAttribute[] result, Type parentType = null) public static bool TryGetCommandInfo(this PROPERTYKEY key, [NotNullWhen(true)] out WPDCommandAttribute? cmd, [NotNullWhen(true)] out WPDCommandParamAttribute[]? param, [NotNullWhen(true)] out WPDCommandResultAttribute[]? result, Type? parentType = null)
{ {
var pi = GetPI(key, parentType); var pi = GetPI(key, parentType);
parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi); parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi);
if (pi is not null) if (pi is not null)
{ {
cmd = pi.GetCustomAttribute<WPDCommandAttribute>(); cmd = pi.GetCustomAttribute<WPDCommandAttribute>();
if (cmd is not null) if (cmd is not null)
{ {
param = pi.GetCustomAttributes<WPDCommandParamAttribute>()?.ToArray() ?? new WPDCommandParamAttribute[0]; param = pi.GetCustomAttributes<WPDCommandParamAttribute>()?.ToArray() ?? new WPDCommandParamAttribute[0];
result = pi.GetCustomAttributes<WPDCommandResultAttribute>()?.ToArray() ?? new WPDCommandResultAttribute[0]; result = pi.GetCustomAttributes<WPDCommandResultAttribute>()?.ToArray() ?? new WPDCommandResultAttribute[0];
return true; return true;
} }
} }
cmd = null; cmd = null;
param = null; param = null;
result = null; result = null;
return false; return false;
} }
internal static PROPERTYKEY? GetKeyFromName(string keyName, Type parentType = null) internal static PROPERTYKEY? GetKeyFromName(string keyName, Type? parentType = null)
{ {
parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi); parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi);
var kv = GetDict(parentType).FirstOrDefault(kv => kv.Value.Name == keyName); var kv = GetDict(parentType).FirstOrDefault(kv => kv.Value.Name == keyName);
return kv.Value is null ? null : kv.Key; return kv.Value is null ? null : kv.Key;
} }
private static Dictionary<PROPERTYKEY, PropertyInfo> GetDict(Type type) private static Dictionary<PROPERTYKEY, PropertyInfo> GetDict(Type type)
{ {
if (!gReversePKLookup.TryGetValue(type, out var dict)) if (!gReversePKLookup.TryGetValue(type, out var dict))
{ {
dict = type.GetProperties(BindStPub).ToDictionary(m => (PROPERTYKEY)m.GetValue(null, null)); dict = type.GetProperties(BindStPub).ToDictionary(m => (PROPERTYKEY)m.GetValue(null, null)!);
gReversePKLookup.Add(type, dict); gReversePKLookup.Add(type, dict);
} }
return dict; return dict;
} }
private static PropertyInfo GetPI(in PROPERTYKEY key, Type parentType = null) private static PropertyInfo? GetPI(in PROPERTYKEY key, Type? parentType = null)
{ {
parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi); parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi);
return GetDict(parentType).TryGetValue(key, out var pi) ? pi : null; return GetDict(parentType).TryGetValue(key, out var pi) ? pi : null;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,205 +9,205 @@ using static Vanara.PInvoke.PortableDeviceApi;
namespace Vanara.PInvoke.Tests; namespace Vanara.PInvoke.Tests;
[TestFixture] [TestFixture]
public class PortableDeviceApiTests public class PortableDeviceApiTests
{ {
IPortableDevice device = null; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
IPortableDeviceManager manager; IPortableDevice device;
static readonly Ole32.PROPERTYKEY eventNameProp = new(Guid.NewGuid(), 1); IPortableDeviceManager manager;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
static readonly Ole32.PROPERTYKEY eventNameProp = new(Guid.NewGuid(), 1);
[OneTimeSetUp] [OneTimeSetUp]
public void _Setup() public void _Setup()
{ {
manager = new(); manager = new();
var deviceId = manager.GetDevices().FirstOrDefault() ?? throw new ArgumentNullException("Device"); var deviceId = manager.GetDevices().FirstOrDefault() ?? throw new ArgumentNullException("Device");
device = new(); device = new();
device.Open(deviceId, GetClientInfo()); device.Open(deviceId, GetClientInfo());
} }
[OneTimeTearDown] [OneTimeTearDown]
public void _TearDown() public void _TearDown()
{ {
device?.Close(); device?.Close();
device = null; }
manager = null;
}
[Test] [Test]
public void DeviceEventing() public void DeviceEventing()
{ {
string evtName = nameof(DeviceEventing); string evtName = nameof(DeviceEventing);
EventWaitHandle evt = new(false, EventResetMode.ManualReset, evtName); EventWaitHandle evt = new(false, EventResetMode.ManualReset, evtName);
EventCallback callback = new(); EventCallback callback = new();
IPortableDeviceValues vals = new(); IPortableDeviceValues vals = new();
vals.SetStringValue(eventNameProp, evtName); vals.SetStringValue(eventNameProp, evtName);
device.Advise(0, callback, vals, out var cookie); device.Advise(0, callback, vals, out var cookie);
TestContext.WriteLine($"Fired = {evt.WaitOne(200)}"); TestContext.WriteLine($"Fired = {evt.WaitOne(200)}");
device.Unadvise(cookie); device.Unadvise(cookie);
} }
class EventCallback : IPortableDeviceEventCallback class EventCallback : IPortableDeviceEventCallback
{ {
void IPortableDeviceEventCallback.OnEvent(IPortableDeviceValues pEventParameters) void IPortableDeviceEventCallback.OnEvent(IPortableDeviceValues? pEventParameters)
{ {
var evtName = pEventParameters.GetStringValue(eventNameProp); var evtName = pEventParameters?.GetStringValue(eventNameProp);
if (EventWaitHandle.TryOpenExisting(evtName, out var evt)) if (evtName is not null && EventWaitHandle.TryOpenExisting(evtName, out var evt))
evt.Set(); evt.Set();
} }
} }
[Test] [Test]
public void EnumDevices() public void EnumDevices()
{ {
try try
{ {
var devices = manager.GetDevices(); var devices = manager.GetDevices();
TestContext.WriteLine("{0} Windows Portable Device(s) found in the system", devices.Length); TestContext.WriteLine("{0} Windows Portable Device(s) found in the system", devices.Length);
foreach (var device in devices) foreach (var device in devices)
{ {
TestContext.WriteLine(device + TestContext.WriteLine(device +
"\n\tManufacturer: " + manager.GetDeviceManufacturer(device) + "\n\tManufacturer: " + manager.GetDeviceManufacturer(device) +
"\n\tDescription: " + manager.GetDeviceDescription(device) + "\n\tDescription: " + manager.GetDeviceDescription(device) +
"\n\tFriendly Name: " + manager.GetDeviceFriendlyName(device)); "\n\tFriendly Name: " + manager.GetDeviceFriendlyName(device));
} }
} }
catch (COMException exception) catch (COMException exception)
{ {
Console.WriteLine("COM exception: code {1}, {0}", exception.Message, exception.ErrorCode); Console.WriteLine("COM exception: code {1}, {0}", exception.Message, exception.ErrorCode);
} }
catch (Exception exception) catch (Exception exception)
{ {
Console.WriteLine(exception.Message); Console.WriteLine(exception.Message);
} }
} }
[Test] [Test]
public void EnumDeviceProps() public void EnumDeviceProps()
{ {
var content = device.Content(); var content = device.Content();
foreach (var s in RecursiveEnumerate(WPD_DEVICE_OBJECT_ID, content).Take(20)) foreach (var s in RecursiveEnumerate(WPD_DEVICE_OBJECT_ID, content).Take(20))
TestContext.WriteLine(s); TestContext.WriteLine(s);
static IEnumerable<string> RecursiveEnumerate(string objId, IPortableDeviceContent content) static IEnumerable<string> RecursiveEnumerate(string objId, IPortableDeviceContent content)
{ {
var enumObjs = content.EnumObjects(0, objId); var enumObjs = content.EnumObjects(0, objId);
foreach (var sobj in enumObjs.Enumerate()) foreach (var sobj in enumObjs.Enumerate())
{ {
yield return sobj; yield return sobj;
foreach (var s in RecursiveEnumerate(sobj, content)) foreach (var s in RecursiveEnumerate(sobj, content))
yield return s; yield return s;
} }
} }
} }
[Test] [Test]
public void EnumDeviceFuncCats() public void EnumDeviceFuncCats()
{ {
var caps = device.Capabilities(); var caps = device.Capabilities();
foreach (var catid in caps.GetFunctionalCategories().Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid.Value)) foreach (var catid in caps.GetFunctionalCategories().Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid!.Value))
{ {
TestContext.WriteLine(GetPI(catid, "WPD_FUNCTIONAL_CATEGORY_")?.Name ?? catid.ToString()); TestContext.WriteLine(GetPI(catid, "WPD_FUNCTIONAL_CATEGORY_")?.Name ?? catid.ToString());
foreach (var type in caps.GetSupportedContentTypes(catid).Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid.Value)) foreach (var type in caps.GetSupportedContentTypes(catid).Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid!.Value))
{ {
TestContext.WriteLine(" t: " + (GetPI(type, "WPD_CONTENT_TYPE_")?.Name ?? type.ToString())); TestContext.WriteLine(" t: " + (GetPI(type, "WPD_CONTENT_TYPE_")?.Name ?? type.ToString()));
foreach (var fmt in caps.GetSupportedFormats(type).Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid.Value)) foreach (var fmt in caps.GetSupportedFormats(type).Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid!.Value))
TestContext.WriteLine(" f: " + (GetPI(fmt, "WPD_OBJECT_FORMAT_")?.Name ?? type.ToString())); TestContext.WriteLine(" f: " + (GetPI(fmt, "WPD_OBJECT_FORMAT_")?.Name ?? type.ToString()));
} }
foreach (var obj in caps.GetFunctionalObjects(catid).Enumerate().Where(pv => pv.VarType == VarEnum.VT_LPWSTR).Select(pv => pv.pwszVal)) foreach (var obj in caps.GetFunctionalObjects(catid).Enumerate().Where(pv => pv.VarType == VarEnum.VT_LPWSTR).Select(pv => pv.pwszVal))
TestContext.WriteLine(" o: " + obj); TestContext.WriteLine(" o: " + obj);
} }
} }
[Test] [Test]
public void EnumDeviceCmds() public void EnumDeviceCmds()
{ {
var caps = device.Capabilities(); var caps = device.Capabilities();
foreach (var cmd in caps.GetSupportedCommands().Enumerate()) foreach (var cmd in caps.GetSupportedCommands().Enumerate())
{ {
TestContext.WriteLine(GetPI(cmd, "WPD_COMMAND_")?.Name ?? cmd.ToString()); TestContext.WriteLine(GetPI(cmd, "WPD_COMMAND_")?.Name ?? cmd.ToString());
Write(caps.GetCommandOptions(cmd), "WPD_OPTION_", " "); Write(caps.GetCommandOptions(cmd), "WPD_OPTION_", " ");
} }
} }
[Test] [Test]
public void EnumDeviceResources() public void EnumDeviceResources()
{ {
var content = device.Content(); var content = device.Content();
var res = content.Transfer(); var res = content.Transfer();
foreach (var cmd in res.GetSupportedResources(WPD_DEVICE_OBJECT_ID).Enumerate()) foreach (var cmd in res.GetSupportedResources(WPD_DEVICE_OBJECT_ID).Enumerate())
{ {
Write(res.GetResourceAttributes(WPD_DEVICE_OBJECT_ID, cmd), "WPD_"); Write(res.GetResourceAttributes(WPD_DEVICE_OBJECT_ID, cmd), "WPD_");
} }
} }
private void Write(IPortableDeviceValues vals, string lookupFilter, string prefix = "") private void Write(IPortableDeviceValues vals, string lookupFilter, string prefix = "")
{ {
foreach (var val in vals.Enumerate()) foreach (var val in vals.Enumerate())
{ {
var name = GetPI(val.Item1, lookupFilter)?.Name ?? val.Item1.ToString(); var name = GetPI(val.Item1, lookupFilter)?.Name ?? val.Item1.ToString();
TestContext.WriteLine($"{prefix}{name} = {val.Item2.Value}"); TestContext.WriteLine($"{prefix}{name} = {val.Item2.Value}");
} }
} }
[Test] [Test]
public void CallDeviceCmd() public void CallDeviceCmd()
{ {
var pkey = WPD_COMMAND_OBJECT_PROPERTIES_GET_SUPPORTED; var pkey = WPD_COMMAND_OBJECT_PROPERTIES_GET_SUPPORTED;
Assert.IsTrue(device.Capabilities().GetSupportedCommands().Enumerate().Contains(pkey)); Assert.IsTrue(device.Capabilities().GetSupportedCommands().Enumerate().Contains(pkey));
var objId = device.Content().EnumObjects(0, WPD_DEVICE_OBJECT_ID).Enumerate().First(); var objId = device.Content().EnumObjects(0, WPD_DEVICE_OBJECT_ID).Enumerate().First();
IPortableDeviceValues vals = new(); IPortableDeviceValues vals = new();
vals.SetCommandPKey(pkey); vals.SetCommandPKey(pkey);
vals.SetStringValue(WPD_PROPERTY_OBJECT_PROPERTIES_OBJECT_ID, objId); vals.SetStringValue(WPD_PROPERTY_OBJECT_PROPERTIES_OBJECT_ID, objId);
IPortableDeviceKeyCollection result = new(); IPortableDeviceKeyCollection result = new();
vals.SetIPortableDeviceKeyCollectionValue(WPD_PROPERTY_OBJECT_PROPERTIES_PROPERTY_KEYS, result); vals.SetIPortableDeviceKeyCollectionValue(WPD_PROPERTY_OBJECT_PROPERTIES_PROPERTY_KEYS, result);
device.SendCommand(0, vals); device.SendCommand(0, vals);
foreach (var key in result.Enumerate()) foreach (var key in result.Enumerate())
TestContext.WriteLine(key); TestContext.WriteLine(key);
} }
[Test] [Test]
public void EnumDeviceEvents() public void EnumDeviceEvents()
{ {
var caps = device.Capabilities(); var caps = device.Capabilities();
foreach (var evt in caps.GetSupportedEvents().Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid.Value)) foreach (var evt in caps.GetSupportedEvents().Enumerate().Where(pv => pv.VarType == VarEnum.VT_CLSID).Select(pv => pv.puuid!.Value))
{ {
TestContext.WriteLine(GetPI(evt, "WPD_EVENT_")?.Name ?? evt.ToString()); TestContext.WriteLine(GetPI(evt, "WPD_EVENT_")?.Name ?? evt.ToString());
foreach (var opt in caps.GetEventOptions(evt).Enumerate()) foreach (var opt in caps.GetEventOptions(evt).Enumerate())
TestContext.WriteLine($" {(GetPI(opt.Item1, "WPD_EVENT_OPTION_")?.Name ?? opt.Item1.ToString())} = {opt.Item2.Value}"); TestContext.WriteLine($" {(GetPI(opt.Item1, "WPD_EVENT_OPTION_")?.Name ?? opt.Item1.ToString())} = {opt.Item2.Value}");
} }
} }
private static PropertyInfo GetPI<T>(T t, string prefix) => private static PropertyInfo? GetPI<T>(T t, string prefix) =>
typeof(PortableDeviceApi).GetProperties(BindingFlags.Public | BindingFlags.Static). typeof(PortableDeviceApi).GetProperties(BindingFlags.Public | BindingFlags.Static).
FirstOrDefault(pi => pi.Name.StartsWith(prefix) && pi.PropertyType.Equals(typeof(T)) && t.Equals(pi.GetValue(null))); FirstOrDefault(pi => pi.Name.StartsWith(prefix) && pi.PropertyType.Equals(typeof(T)) && Equals(t, pi.GetValue(null)));
private IPortableDeviceValues GetClientInfo(bool readOnly = true) private IPortableDeviceValues GetClientInfo(bool readOnly = true)
{ {
// Client information is optional. The client can choose to identify itself, or // Client information is optional. The client can choose to identify itself, or
// to remain unknown to the driver. It is beneficial to identify yourself because // to remain unknown to the driver. It is beneficial to identify yourself because
// drivers may be able to optimize their behavior for known clients. (e.g. An // drivers may be able to optimize their behavior for known clients. (e.g. An
// IHV may want their bundled driver to perform differently when connected to their // IHV may want their bundled driver to perform differently when connected to their
// bundled software.) // bundled software.)
// CoCreate an IPortableDeviceValues interface to hold the client information. // CoCreate an IPortableDeviceValues interface to hold the client information.
IPortableDeviceValues clientInformation = new(); IPortableDeviceValues clientInformation = new();
// Attempt to set all bits of client information // Attempt to set all bits of client information
clientInformation.SetStringValue(WPD_CLIENT_NAME, "Test"); clientInformation.SetStringValue(WPD_CLIENT_NAME, "Test");
clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 1); clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 1);
clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0); clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 2); clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 2);
if (readOnly) if (readOnly)
clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, ACCESS_MASK.GENERIC_READ); clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, ACCESS_MASK.GENERIC_READ);
// Some device drivers need to impersonate the caller in order to function correctly. Since our application does not // Some device drivers need to impersonate the caller in order to function correctly. Since our application does not
// need to restrict its identity, specify SECURITY_IMPERSONATION so that we work with all devices. // need to restrict its identity, specify SECURITY_IMPERSONATION so that we work with all devices.
clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, (uint)FileFlagsAndAttributes.SECURITY_IMPERSONATION); clientInformation.SetUnsignedIntegerValue(WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, (uint)FileFlagsAndAttributes.SECURITY_IMPERSONATION);
return clientInformation; return clientInformation;
} }
} }