Search code examples
c#com-interop

Windows Structured Storage - 32-bit vs 64-bit COM Interop


While attempting to convert an existing 32-bit application to 64-bit, I've run into trouble getting some COM Interop code to work properly. The code is accessing the structured storage API, using managed code I translated from the various Windows SDK header/IDL files.

The code is failing when I try to call into IPropertyStorage.ReadMultiple(), with STG_E_INVALIDPARAMETER. The previous interop calls, to StgOpenStorageEx and IPropertySetStorage.Open, appear to work fine. MSDN claims this error means something's wrong with my PROPSPEC parameters, but the same parameter values work fine when compiled as a 32-bit application and the value I get back is the correct string value for the specified property.

Here are what I think are the relevant bits:

// PropertySpecKind enumeration.
public enum PropertySpecKind : uint
{
    Lpwstr = 0,
    PropId = 1
}

// PropertySpec structure:
[StructLayout(LayoutKind.Explicit)]
public struct PropertySpec
{
    [FieldOffset(0)] public PropertySpecKind kind;
    [FieldOffset(4)] public uint propertyId;
    [FieldOffset(4)] public IntPtr name;
}

// PropertyVariant Structure:
[StructLayout(LayoutKind.Explicit)]
public struct PropertyVariant
{
    [FieldOffset(0)] public Vartype vt;
    [FieldOffset(8)] public IntPtr pointerValue;
}

// IPropertyStorage interface
[ComImport]
[Guid("00000138-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyStorage
{
    int ReadMultiple(
        uint count,
        [MarshalAs(UnmanagedType.LPArray, SizeConst = 0)] PropertySpec[] properties,
        [Out, MarshalAs(UnmanagedType.LPArray, SizeConst = 0)] PropertyVariant[] values);

    void WriteMultiple(
        uint count,
        [MarshalAs(UnmanagedType.LPArray, SizeConst = 0)] PropertySpec[] properties,
        [MarshalAs(UnmanagedType.LPArray, SizeConst = 0)] PropertyVariant[] values,
        uint miniumumPropertyId);
}

var properties = new PropertySpec[1];
properties[0].kind = PropertySpecKind.PropId; 
properties[0].propertyId = 2;

var propertyValues = new PropertyVariant[1];

// This helper method just calls StgOpenStorageEx with appropriate parameters.
var propertySetStorage = StorageHelper.GetPropertySetStorageReadOnly(fileName);
var propertyStorage = propertySetStorage.Open(StoragePropertySets.PSGUID_SummaryInformation, StorageMode.Read | StorageMode.ShareExclusive);    
propertyStorage.ReadMultiple(1, properties, propertyValues); // Exception is here.

Solution

  • [StructLayout(LayoutKind.Sequential)]
    public struct PropertySpec
    {
        public PropertySpecKind kind;
        public PropertySpecData data;
    }
    

    Yes, that's a good way to declare that structure. Now you leave it up to the pinvoke interop marshaller to calculate the offset of the data.name field and it gets it right.

    The name field is an IntPtr, it takes 4 bytes in 32-bit mode but 8 bytes in 64-bit mode. Fields of a structure are aligned to an offset that's an integer multiple of the field size. The default packing is 8, meaning that any field that's 8 bytes or less will be aligned. That gives that field an alignment requirement of 4 in 32-bit mode, of 8 in 64-bit mode. Previously, you forced it at an offset of 4 by using the [FieldOffset(4)] attribute. Okay for 32-bit code but wrong offset for 64-bit code.

    There's some background on structure packing in this MSDN Library article.