0

在尝试将现有的 32 位应用程序转换为 64 位时,我在让一些 COM 互操作代码正常工作时遇到了麻烦。该代码使用我从各种 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.
4

3 回答 3

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

是的,这是声明该结构的好方法。现在你把它留给 pinvoke interop marshaller 来计算data.name字段的偏移量,它就正确了。

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.

于 2013-06-03T18:54:02.383 回答
1

在尝试了互操作定义的多次迭代之后,我终于偶然发现了一个答案。我不确定为什么这会有所不同,但我所做的更改是用嵌套定义替换单个PROPSPEC和结构定义;PROPVARIANT基本上,我将匿名工会分成他们自己的类型。我认为当我这样做时会解决某种对齐问题。

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

[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;
}
于 2013-06-03T17:47:13.233 回答
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 属性的使用。好吧,这意味着您现在必须测试返回值 :-)

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

于 2013-06-02T10:28:29.543 回答