Windows结构化存储 - 32位与64位COM互操作

时间:2013-05-28 17:50:25

标签: c# com-interop

在尝试将现有的32位应用程序转换为64位时,我遇到了使某些COM Interop代码正常工作的问题。代码正在使用从各种Windows SDK标头/ IDL文件翻译的托管代码访问结构化存储API。

当我尝试使用IPropertyStorage.ReadMultiple()呼叫STG_E_INVALIDPARAMETER时代码失败。以前的互操作电话StgOpenStorageExIPropertySetStorage.Open似乎工作正常。 MSDN声称这个错误意味着我的PROPSPEC参数有问题,但是当编译为32位应用程序时,相同的参数值工作正常,并且我得到的值是指定属性的正确字符串值。

以下是我认为的相关内容:

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

3 个答案:

答案 0 :(得分:5)

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

是的,这是宣布该结构的好方法。现在,您将它留给pinvoke interop marshaller来计算 data.name 字段的偏移量,并使其正确。

name 字段是IntPtr,它在32位模式下占用4个字节,在64位模式下占用8个字节。结构的字段与字段大小的整数倍的偏移对齐。默认打包为8,这意味着任何8字节或更少字节都将对齐。这使得该字段在32位模式下的对齐要求为4,在64位模式下为8。以前,您通过使用[FieldOffset(4)]属性将其强制为偏移量4。好的是32位代码,但64位代码的错误偏移。

这个MSDN Library article中有关于结构打包的一些背景知识。

答案 1 :(得分:1)

在尝试互操作定义的多次迭代之后,我终于偶然发现了答案。我不完全确定为什么这会产生影响,但我所做的更改是将单个PROPSPECPROPVARIANT结构定义替换为嵌套的结构定义;基本上,我把匿名工会分成了自己的类型。我假设在执行此操作时会出现某种对齐问题。

具体来说,PROPSPEC的32位工作定义如下:

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

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

我把它改成了这个,它现在适用于两个架构:

[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;
}

答案 2 :(得分:0)

你应该像这样定义界面:

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

请注意PreserveSig attribute的用法。嗯,这意味着你现在必须测试返回值: - )

注意:如果您需要更多复合存储p / invoke声明,可以查看这个100%免费的Nuget工具:CodeFluent Runtime Client。它包含您可以使用的CompoundStorage类实用程序,或者只是使用.NET Reflector或ILSpy检查它并获取它包含的p / invoke定义。它应该支持32位和64位的世界。