Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Let's use the GetDevicePropValue command (MTP spec - section D.2.21) to illustrate this. GetDevicePropValue takes one parameter - the device property code that we want to retrieve the current value for. We'll retrieve the BatteryLevel device property (MTP spec - section C.2.2) which is of type UINT8.
From the WPD API, we will need this sequence of commands:
- WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_READ - to initiate the transfer
- WPD_COMMAND_MTP_EXT_READ_DATA - to actually transfer the data
- WPD_COMMAND_MTP_EXT_END_DATA_TRANSFER - to flag command completion and retrieve the MTP response code
Initiating the sequence
#include <portabledevice.h>
#include <portabledeviceapi.h>
#include <wpdmtpextensions.h>
// We'll return the BatteryLevel in the BYREF parameter
HRESULT GetBatteryLevel(IPortableDevice* pDevice, BYTE& bBatteryLevel)
{
HRESULT hr = S_OK;
const WORD PTP_OPCODE_GETDEVICEPROPVALUE = 0x1015;
const WORD PTP_DEVICEPROPCODE_BATTERYLEVEL = 0x5001;
const WORD PTP_RESPONSECODE_OK = 0x2001; // 0x2001 indicates command success
// Build basic WPD parameters for the command
CComPtr<IPortableDeviceValues> spParameters;
if (hr == S_OK)
{
hr = CoCreateInstance(CLSID_PortableDeviceValues,
NULL,
CLSCTX_INPROC_SERVER,
IID_IPortableDeviceValues,
(VOID**)&spParameters);
}
// WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_READ is the command we need here
if (hr == S_OK)
{
hr = spParameters->SetGuidValue(WPD_PROPERTY_COMMON_COMMAND_CATEGORY,
WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_READ.fmtid);
}
if (hr == S_OK)
{
hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_COMMON_COMMAND_ID,
WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_READ.pid);
}
// Specify the actual MTP op-code that we want to execute here
if (hr == S_OK)
{
hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_MTP_EXT_OPERATION_CODE,
(ULONG) PTP_OPCODE_GETDEVICEPROPVALUE);
}
// GetDevicePropValue requires the property code as an MTP parameter
// MTP parameters need to be first put into a PropVariantCollection
CComPtr<IPortableDevicePropVariantCollection> spMtpParams;
if (hr == S_OK)
{
hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection,
NULL,
CLSCTX_INPROC_SERVER,
IID_IPortableDevicePropVariantCollection,
(VOID**)&spMtpParams);
}
PROPVARIANT pvParam = {0};
pvParam.vt = VT_UI4;
// Specify the BatteryLevel property as the MTP parameter
if (hr == S_OK)
{
pvParam.ulVal = PTP_DEVICEPROPCODE_BATTERYLEVEL;
hr = spMtpParams->Add(&pvParam);
}
// Add MTP parameters collection to our main parameter list
if (hr == S_OK)
{
hr = spParameters->SetIPortableDevicePropVariantCollectionValue(
WPD_PROPERTY_MTP_EXT_OPERATION_PARAMS, spMtpParams);
}
// Send the command to initiate the transfer
CComPtr<IPortableDeviceValues> spResults;
if (hr == S_OK)
{
hr = pDevice->SendCommand(0, spParameters, &spResults);
}
// Check if the driver succeeded in sending the command by interrogating WPD_PROPERTY_COMMON_HRESULT
HRESULT hrCmd = S_OK;
if (hr == S_OK)
{
hr = spResults->GetErrorValue(WPD_PROPERTY_COMMON_HRESULT, &hrCmd);
}
if (hr == S_OK)
{
printf("Driver return code (initiating): 0x%08X\n", hrCmd);
hr = hrCmd;
}
// If the transfer was initiated successfully, the driver will return us a context cookie
LPWSTR pwszCookie = NULL;
if (hr == S_OK)
{
hr = spResults->GetStringValue(WPD_PROPERTY_MTP_EXT_TRANSFER_CONTEXT, &pwszContext);
}
// The driver will also let us know how many bytes will be transferred. This is important to
// retrieve since we have to read all the data that the device will send us (even if it
// isn't the size we were expecting), else we run the risk of the device going out of sync
// with the driver.
ULONG cbReportedDataSize = 0;
if (hr == S_OK)
{
hr = pResults->GetUnsignedIntegerValue(WPD_PROPERTY_MTP_EXT_TRANSFER_TOTAL_DATA_SIZE,
&cbReportedDataSize);
}
// Note: The driver provides an additional property - WPD_PROPERTY_MTP_EXT_OPTIMAL_TRANSFER_BUFFER_SIZE
// which suggests the chunk size that we should retrieve the data in. If your application will be
// transferring a large amount of data (>256K), then you should use this property to break down the
// transfer into small chunks so that your app is more responsive.
// We'll skip this here since device properties are never that big (especially BatteryLevel)
Reading the data
// If no data will be transferred we need to skip reading in the data
BOOL bSkipDataPhase = FALSE;
if (hr == S_OK && cbReportedDataSize == 0)
{
hr = S_FALSE;
bSkipDataPhase = TRUE;
}
// WPD_COMMAND_MTP_EXT_READ_DATA is the command where we actually read in the data
(void) spParameters->Clear();
if (hr == S_OK)
{
hr = spParameters->SetGuidValue(WPD_PROPERTY_COMMON_COMMAND_CATEGORY,
WPD_COMMAND_MTP_EXT_READ_DATA.fmtid);
}
if (hr == S_OK)
{
hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_COMMON_COMMAND_ID,
WPD_COMMAND_MTP_EXT_READ_DATA.pid);
}
// We need to specify the same context that we received earlier
if (hr == S_OK)
{
hr = spParameters->SetStringValue(WPD_PROPERTY_MTP_EXT_TRANSFER_CONTEXT, pwszContext);
}
// We'll need to also allocate a buffer for the command to read data into - this should
// be the same size as the number of bytes we are expecting to read (per chunk if applicable)
BYTE* pbBufferIn = NULL;
if (hr == S_OK)
{
pbBufferIn = (BYTE*) CoTaskMemAlloc(cbReportedDataSize);
if (pbBufferIn == NULL)
{
hr = E_OUTOFMEMORY;
}
}
// Pass the allocated buffer as a parameter
if (hr == S_OK)
{
hr = spParameters->SetBufferValue(WPD_PROPERTY_MTP_EXT_TRANSFER_DATA,
pbBufferIn, cbReportedDataSize);
}
// Specify the number of bytes to transfer as a parameter
if (hr == S_OK)
{
hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_MTP_EXT_TRANSFER_NUM_BYTES_TO_READ,
cbReportedDataSize);
}
// Send the command to transfer the data
spResults = NULL;
if (hr == S_OK)
{
hr = pDevice->SendCommand(0, spParameters, &spResults);
}
// Check if the driver succeeded in tranferring the data
HRESULT hrCmd = S_OK;
if (hr == S_OK)
{
hr = spResults->GetErrorValue(WPD_PROPERTY_COMMON_HRESULT, &hrCmd);
}
if (hr == S_OK)
{
printf("Driver return code (reading data): 0x%08X\n", hrCmd);
hr = hrCmd;
}
// IMPORTANT: The API doesn't really transfer the data into the buffer we provided earlier.
// Instead it is available in the results collection.
BYTE* pbBufferOut = NULL;
ULONG cbBytesRead = 0;
if (hr == S_OK)
{
hr = pResults->GetBufferValue(WPD_PROPERTY_MTP_EXT_TRANSFER_DATA, &pbBufferOut, &cbBytesRead);
}
// Reset hr to S_OK since we skipped the data phase
if (hr == S_FALSE && bSkipDataPhase == TRUE)
{
hr = S_OK;
}
Retrieving the response
// WPD_COMMAND_MTP_EXT_END_DATA_TRANSFER is the command to signal transfer completion
(void) spParameters->Clear();
if (hr == S_OK)
{
hr = spParameters->SetGuidValue(WPD_PROPERTY_COMMON_COMMAND_CATEGORY,
WPD_COMMAND_MTP_EXT_END_DATA_TRANSFER.fmtid);
}
if (hr == S_OK)
{
hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_COMMON_COMMAND_ID,
WPD_COMMAND_MTP_EXT_END_DATA_TRANSFER.pid);
}
// We need to specify the same context that we received earlier
if (hr == S_OK)
{
hr = spParameters->SetStringValue(WPD_PROPERTY_MTP_EXT_TRANSFER_CONTEXT, pwszContext);
}
// Send the completion command
spResults = NULL;
if (hr == S_OK)
{
hr = pDevice->SendCommand(0, spParameters, &spResults);
}
// Check if the driver successfully ended the data transfer
if (hr == S_OK)
{
hr = spResults->GetErrorValue(WPD_PROPERTY_COMMON_HRESULT, &hrCmd);
}
if (hr == S_OK)
{
printf("Driver return code (ending transfer): 0x%08X\n", hrCmd);
hr = hrCmd;
}
// If the command was executed successfully, we check the MTP response code to see if the device
// could handle the command. If the device could not handle the command, the data phase would
// have been skipped (detected by cbReportedDataSize==0) and the MTP response will indicate the
// error.
DWORD dwResponseCode;
if (hr == S_OK)
{
hr = spResults->GetUnsignedIntegerValue(WPD_PROPERTY_MTP_EXT_RESPONSE_CODE, &dwResponseCode);
}
if (hr == S_OK)
{
printf("MTP Response code: 0x%X\n", dwResponseCode);
hr = (dwResponseCode == (DWORD) PTP_RESPONSECODE_OK) ? S_OK : E_FAIL;
}
// If the command was handled by the device, return the property value in the BYREF property
if (hr == S_OK)
{
if (pbBufferOut != NULL)
{
bBatteryLevel = (BYTE)(*pbBufferOut);
}
else
{
// MTP response code was OK, but no data phase occurred
hr = E_UNEXPECTED;
}
}
// If response parameters are present, it will be contained in the WPD_PROPERTY_MTP_EXT_RESPONSE_PARAMS
// property. GetDevicePropValue does not return additional response parameters, so we skip this code
// If required, you may find that code in the post that covered sending MTP commands without data
// Free up any allocated memory
CoTaskMemFree(pbBufferIn);
CoTaskMemFree(pbBufferOut);
CoTaskMemFree(pwszContext);
return hr;
}
Initiating the data transfer is pretty easy here. The driver lets us know how much data to expect. To transfer the data, we need to pre-allocate a buffer and provide that in our READ_DATA command parameters. Once the data is successfully read, the data is available in the results collection of the sent command. Once all data is transferred, we send the END_DATA_TRANSFER command and retrieve the response code.
Things to remember:
- Allocate a buffer before sending the READ_DATA command - this is required
- The transferred data is not in the allocated buffer but is, instead, in the results collection of the command
- Watch out for the case when data will not be transferred
[The fact that the transferred data is not available in our allocated buffer but is in a different buffer is a side-effect of how the WPD API uses the WDF framework. I'll ping someone on the WDF team to comment on this but this issue may be addressed in a later iteration of the WPD API.]
Comments
- Anonymous
May 29, 2009
PingBack from http://paidsurveyshub.info/story.php?title=dimeby8-sending-mtp-commands-through-wpd-part-3-data-from-device