We'll pick the SetDevicePropValue MTP command (MTP spec - section D.2.22) to illustrate this example. This command requires the device property code as a parameter. We'll try setting the DateTime property (MTP spec - section C.2.18). The DateTime property is of type STRING and we've already covered in a previous post on how MTP strings are created. We'll use the PackString function from that post here.
When data is involved, sending MTP commands is broken up into three parts:
From the WPD API, this turns out to be a sequence of three commands:
This example will see the use of WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_WRITE, WPD_COMMAND_MTP_EXT_WRITE_DATA and WPD_COMMAND_MTP_EXT_END_DATA_TRANSFER.
#include <portabledevice.h> #include <portabledeviceapi.h> #include <wpdmtpextensions.h> HRESULT SetDateTime(IPortableDevice* pDevice, LPCWSTR pwszDateTime) { HRESULT hr = S_OK; const WORD PTP_OPCODE_SETDEVICEPROPVALUE = 0x1016; const WORD PTP_DEVICEPROPCODE_DATETIME = 0x5011; 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_WRITE 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_WRITE.fmtid); } if (hr == S_OK) { hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_COMMON_COMMAND_ID, WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_WRITE.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_SETDEVICEPROPVALUE); } // SetDevicePropValue 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 DateTime property as the MTP parameter if (hr == S_OK) { pvParam.ulVal = PTP_DEVICEPROPCODE_DATETIME; 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); } // Figure out the data that we'll be sending - in this case it will be an MTP string BYTE* pbBuffer = NULL; DWORD cbBufferSize = 0; if (hr == S_OK) { hr = PackString(pwszDateTime, &pbBuffer, &cbBufferSize); } // We need to inform the device how much data will arrive - this is a required parameter if (hr == S_OK) { hr = spParameters->SetUnsignedLargeIntegerValue(WPD_PROPERTY_MTP_EXT_TRANSFER_TOTAL_DATA_SIZE, &cbBufferSize); } // 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; } // The driver will return us a context cookie that we will need to use during our data transfer LPWSTR pwszCookie = NULL; if (hr == S_OK) { hr = spResults->GetStringValue(WPD_PROPERTY_MTP_EXT_TRANSFER_CONTEXT, &pwszContext); }
// WPD_COMMAND_MTP_EXT_WRITE_DATA is the command where we actually send in the data (void) spParameters->Clear(); if (hr == S_OK) { hr = spParameters->SetGuidValue(WPD_PROPERTY_COMMON_COMMAND_CATEGORY, WPD_COMMAND_MTP_EXT_WRITE_DATA.fmtid); } if (hr == S_OK) { hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_COMMON_COMMAND_ID, WPD_COMMAND_MTP_EXT_WRITE_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 need to specify the number of bytes arriving with this command. This allows us to // send the data in chunks if required (multiple WRITE_DATA commands). In this case // we will send the data in a single chunk if (hr == S_OK) { hr = spParameters->SetUnsignedIntegerValue(WPD_PROPERTY_MTP_EXT_TRANSFER_NUM_BYTES_TO_WRITE, cbBufferSize); } // Provide the data that needs to be transferred if (hr == S_OK) { hr = spParameters->SetBufferValue(WPD_PROPERTY_MTP_EXT_TRANSFER_DATA, pbBuffer, cbBufferSize); } // Send the data to the device spResults = NULL; if (hr == S_OK) { hr = pDevice->SendCommand(0, spParameters, &spResults); } // Check if the data was sent successfully by interrogating COMMON_HRESULT if (hr == S_OK) { hr = spResults->GetErrorValue(WPD_PROPERTY_COMMON_HRESULT, &hrCmd); } if (hr == S_OK) { printf("Driver return code (sending data): 0x%08X\n", hrCmd); hr = hrCmd; } // The driver will inform us on the number of bytes that were actually transferred. Normally this // should be the same as the number that we provided. DWORD cbBytesWritten = 0; if (hr == S_OK) { hr = spResults->GetUnsignedIntegerValue(WPD_PROPERTY_MTP_EXT_TRANSFER_NUM_BYTES_WRITTEN, &cbBytesWritten); }
// 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 and the data. Note that there is a distinction between the command // and the data being successfully sent to the device and the command and data being handled successfully // by the device 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 response parameters are present, it will be contained in the WPD_PROPERTY_MTP_EXT_RESPONSE_PARAMS // property. SetDevicePropValue 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(pbBuffer); CoTaskMemFree(pwszContext); return hr; }
Sending a command with data to the device turns out to be a little more involved than sending a command without data. Things to remember are that: