简体   繁体   中英

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.
[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 .

After trying multiple iterations of the interop definitions I finally stumbled on an answer. I'm not exactly sure why this makes a difference, but the change I made was to replace the single PROPSPEC and PROPVARIANT structure definitions with nested ones; basically, I split the anonymous unions out into their own types. I assume there is some kind of alignment issue that gets resolved when I do this.

Specifically, the working 32-bit definition of PROPSPEC looked like this:

[StructLayout(LayoutKind.Explicit)]
public struct PropertySpec
{
    [FieldOffset(0)]
    public PropertySpecKind kind;

    [FieldOffset(4)]
    public uint propertyId;
    [FieldOffset(4)]
    public IntPtr name;
}

I changed that into this, and it now works on both archictectures:

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

[StructLayout(LayoutKind.Explicit)]
public struct PropertySpecData
{
    [FieldOffset(0)]
    public uint propertyId;

    [FieldOffset(0)]
    public IntPtr name;
}

You should define the interface like this:

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

    [PreserveSig]
    uint WriteMultiple(
        uint count,
        [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] PropertySpec[] properties,
        [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]  PropertySpec[] values,
        uint miniumumPropertyId);

        // other methods left as an exercise to the reader...
}

Note the usage of the PreserveSig attribute . Well, it means you'll have to test the return values now :-)

Note: if you need more compound storage p/invoke declarations, you can have a look at this 100% free Nuget tool: CodeFluent Runtime Client . It contains a CompoundStorage class utility that you can use, or just inspect it using .NET Reflector or ILSpy and grab the p/invoke definitions it contains. It should support 32 and 64-bit worlds.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM