在尝试将现有的32位应用程序转换为64位时,我遇到了使某些COM Interop代码正常工作的问题。代码正在使用从各种Windows SDK标头/ IDL文件翻译的托管代码访问结构化存储API。
当我尝试使用IPropertyStorage.ReadMultiple()
呼叫STG_E_INVALIDPARAMETER
时代码失败。以前的互操作电话StgOpenStorageEx
和IPropertySetStorage.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.
答案 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)
在尝试互操作定义的多次迭代之后,我终于偶然发现了答案。我不完全确定为什么这会产生影响,但我所做的更改是将单个PROPSPEC
和PROPVARIANT
结构定义替换为嵌套的结构定义;基本上,我把匿名工会分成了自己的类型。我假设在执行此操作时会出现某种对齐问题。
具体来说,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位的世界。