Marshalling variant properties in C#
This post is a continuation of the one covering WPD property retrieval in C#. That post explained how to marshal strings and the basic int properties. Handling other types such as VT_DATE and VT_BOOL is trickier so I'll try to cover them here.
VT_DATE
VT_DATE variants hold the date time value in a field of type double. So we'll need to update our C# PropVariant definition to include a double field.
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct PropVariant
{
[FieldOffset(0)]
public short variantType;
[FieldOffset(8)]
public IntPtr pointerValue;
[FieldOffset(8)]
public byte byteValue;
[FieldOffset(8)]
public long longValue;
[FieldOffset(8)]<br> public double dateValue;
}
Once we have a field that can hold the value, we need to convert it to something C# can understand. PROPVARIANTs are really OLE automation structures, so we can use the DateTime.FromOADate method to coerce our double into a DateTime object.
DateTime date = DateTime.FromOADate(pvValue.dateValue);
To convert from DateTime to the double value in the PropVariant, use DateTime.ToOADate.
VT_BOOL
VT_BOOL variants hold the bool value in a field of type short. So we'll need to update the C# PropVariant definition to include the short field.
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct PropVariant
{
[FieldOffset(0)]
public short variantType;
[FieldOffset(8)]
public IntPtr pointerValue;
[FieldOffset(8)]
public byte byteValue;
[FieldOffset(8)]
public long longValue;
[FieldOffset(8)]
public double dateValue;
[FieldOffset(8)]<br> public short boolValue;
}
Once we have the short field, we can convert it to a bool using Convert.ToBoolean.
bool b = Convert.ToBoolean(pvValue.boolValue);
VT_CLSID
VT_CLSID variants hold the GUID value in a field of type CLSID* . The value itself is a pointer to a blob of memory allocated using CoTaskMemAlloc. Since we are dealing with a pointer, we can use the pointerValue field to access this.
We then use PtrToStructure (similar to how we handled LPWSTR) to marshal this pointer into a Guid we can use ourselves.
Guid guid = (Guid)Marshal.PtrToStructure(pvValue.pointerValue, typeof(Guid));
StructureToPtr can be used to marshal a Guid back into the PropVariant.
I'll update this post if I encounter other types that require special handling. Feel free to leave a comment on any that you come across as well.
Comments
Anonymous
April 30, 2007
Hello, I love your posts, they are extremely useful. I have converted alot of your code (with some modifications to Visual Basic.Net and have managed to enumerate, delete content, add folders, create playlists and contacts, and appointements, and such. The only problem I am having is to marshal the arrays for object references back into a string of object ids. For example, I create a playlist as you described and it works perfect. i can create the playlist with object ids useing the "StringToPropVariant" I already know the .net frmae work does not support the typwe of array in which the object references are storaed. My question is how do I expose the array from Visual Basic .NET? any help you can give me would be greatly appreciated.Anonymous
April 20, 2009
This is great. Now how do you handle VT_VECTOR|VT_xxxx types in c#?Anonymous
April 23, 2009
Use MTP commands sent through the Device.SendCommand() method. Here is an example... public string[] GetAllObjectHandles() { List<string> tmpHandles = new List<string>(); try { IPortableDeviceValues pParameters = (IPortableDeviceValues)new PortableDeviceTypesLib.PortableDeviceValues(); IPortableDeviceValues pResults = null; { pParameters.SetGuidValue(pKeys.WPD_PROPERTY_COMMON_COMMAND_CATEGORY, pKeys.WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_READ.fmtid); pParameters.SetUnsignedIntegerValue(pKeys.WPD_PROPERTY_COMMON_COMMAND_ID, pKeys.WPD_COMMAND_MTP_EXT_EXECUTE_COMMAND_WITH_DATA_TO_READ.pid); pParameters.SetUnsignedIntegerValue(pKeys.WPD_PROPERTY_MTP_EXT_OPERATION_CODE, 0x1007); IPortableDevicePropVariantCollection pSubValues = (IPortableDevicePropVariantCollection)new PortableDeviceTypesLib.PortableDevicePropVariantCollection(); PropVariant pNewPropVariant = new PropVariant(); pNewPropVariant.VariantType = 19; pNewPropVariant.LongValue = 0xffffffff; IntPtr ptrValue = Marshal.AllocHGlobal(Marshal.SizeOf(pNewPropVariant)); Marshal.StructureToPtr(pNewPropVariant, ptrValue, false); tag_inner_PROPVARIANT pvValue = (tag_inner_PROPVARIANT)Marshal.PtrToStructure(ptrValue, typeof(tag_inner_PROPVARIANT)); { pSubValues.Add(pvValue); } PropVariant pNewPropVariant2 = new PropVariant(); pNewPropVariant2.VariantType = 19; pNewPropVariant2.LongValue = 0x0; IntPtr ptrValue2 = Marshal.AllocHGlobal(Marshal.SizeOf(pNewPropVariant2)); Marshal.StructureToPtr(pNewPropVariant2, ptrValue2, false); tag_inner_PROPVARIANT pvValue2 = (tag_inner_PROPVARIANT)Marshal.PtrToStructure(ptrValue2, typeof(tag_inner_PROPVARIANT)); { pSubValues.Add(pvValue2); } PropVariant pNewPropVariant3 = new PropVariant(); pNewPropVariant3.VariantType = 19; pNewPropVariant3.LongValue = 0x0; IntPtr ptrValue3 = Marshal.AllocHGlobal(Marshal.SizeOf(pNewPropVariant3)); Marshal.StructureToPtr(pNewPropVariant3, ptrValue3, false); tag_inner_PROPVARIANT pvValue3 = (tag_inner_PROPVARIANT)Marshal.PtrToStructure(ptrValue3, typeof(tag_inner_PROPVARIANT)); { pSubValues.Add(pvValue3); } pParameters.SetIPortableDevicePropVariantCollectionValue(pKeys.WPD_PROPERTY_MTP_EXT_OPERATION_PARAMS, pSubValues); } pDevice.SendCommand(0, pParameters, pResults); uint pCelt = 0; pResults.GetCount(pCelt); if (pCelt <= 1) { return tmpHandles.ToArray; return; } uint tmpBufferSize = 0; uint tmpTransferSize = 0; string tmpTransferContext = string.Empty; { pResults.GetStringValue(pKeys.WPD_PROPERTY_MTP_EXT_TRANSFER_CONTEXT, tmpTransferContext); pResults.GetUnsignedIntegerValue(pKeys.WPD_PROPERTY_MTP_EXT_TRANSFER_TOTAL_DATA_SIZE, tmpBufferSize); pResults.GetUnsignedIntegerValue(pKeys.WPD_PROPERTY_MTP_EXT_OPTIMAL_TRANSFER_BUFFER_SIZE, tmpTransferSize); try { pResults.GetErrorValue(pKeys.WPD_PROPERTY_COMMON_HRESULT, (HRESULT_FROM_WIN32)Result); } catch { } } pParameters.Clear(); pResults.Clear(); byte[] tmpData = new byte[(int)tmpTransferSize]; { pParameters.SetGuidValue(pKeys.WPD_PROPERTY_COMMON_COMMAND_CATEGORY, pKeys.WPD_COMMAND_MTP_EXT_READ_DATA.fmtid); pParameters.SetUnsignedIntegerValue(pKeys.WPD_PROPERTY_COMMON_COMMAND_ID, pKeys.WPD_COMMAND_MTP_EXT_READ_DATA.pid); pParameters.SetStringValue(pKeys.WPD_PROPERTY_MTP_EXT_TRANSFER_CONTEXT, tmpTransferContext); pParameters.SetBufferValue(pKeys.WPD_PROPERTY_MTP_EXT_TRANSFER_DATA, tmpData, tmpBufferSize); pParameters.SetUnsignedIntegerValue(pKeys.WPD_PROPERTY_MTP_EXT_TRANSFER_NUM_BYTES_TO_READ, tmpBufferSize); } pDevice.SendCommand(0, pParameters, pResults); ulong[] pbBufferOut = new ulong[(int)tmpTransferSize]; uint cbBytesRead = 0; { pResults.GetBufferValue(pKeys.WPD_PROPERTY_MTP_EXT_TRANSFER_DATA, pbBufferOut, cbBytesRead); try { pResults.GetErrorValue(pKeys.WPD_PROPERTY_COMMON_HRESULT, (HRESULT_FROM_WIN32)Result); } catch { } } byte[] newBuffer = new byte[(int)cbBytesRead]; IntPtr tmpPtr = new IntPtr((long)pbBufferOut(0)); string tmpString = string.Empty; for (int i = 0; i <= newBuffer.Length - 1; i += 4) { string tmpObjectHandle = "o" + Convert.ToString(Marshal.ReadInt32(tmpPtr, 8 + i), 16).ToUpper; tmpHandles.Add(tmpObjectHandle); } pParameters.Clear(); pResults.Clear(); { pParameters.SetGuidValue(pKeys.WPD_PROPERTY_COMMON_COMMAND_CATEGORY, pKeys.WPD_COMMAND_MTP_EXT_END_DATA_TRANSFER.fmtid); pParameters.SetUnsignedIntegerValue(pKeys.WPD_PROPERTY_COMMON_COMMAND_ID, pKeys.WPD_COMMAND_MTP_EXT_END_DATA_TRANSFER.pid); pParameters.SetStringValue(pKeys.WPD_PROPERTY_MTP_EXT_TRANSFER_CONTEXT, tmpTransferContext); } pDevice.SendCommand(0, pParameters, pResults); try { { int tmpResult = 0; pResults.GetErrorValue(pKeys.WPD_PROPERTY_COMMON_HRESULT, tmpResult); Result = (HRESULT_FROM_WIN32)tmpResult; } } catch { } Marshal.FreeHGlobal(tmpPtr); } catch (Exception ex) { if (Result == HRESULT_FROM_WIN32.S_Ok) Result = Converters.ExceptionToComError(ex); if (Result == HRESULT_FROM_WIN32.S_Ok) Result = HRESULT_FROM_WIN32.E_Fail; if (DeviceMethodError != null) { DeviceMethodError(ex); } } return tmpHandles.ToArray; } public string[] GetObjectHandles(string ParentObjectId, [System.Runtime.InteropServices.OptionalAttribute, System.Runtime.InteropServices.DefaultParameterValueAttribute(HRESULT_FROM_WIN32.S_Ok)] ref // ERROR: Optional parameters aren't supported in C# HRESULT_FROM_WIN32 Result) { IEnumPortableDeviceObjectIDs pEnum = null; string[] tmpIds = new string[999]; uint pFetched = 0; try { pDevice.Content(pContent); pContent.EnumObjects(null, ParentObjectId, null, pEnum); pEnum.Next(999, tmpIds, pFetched); Array.Resize(ref tmpIds, (int)pFetched); } catch (Exception ex) { } //Place error handling code here... return tmpIds; }