2013-05-28 130 views
0

嘗試將現有的32位應用程序轉換爲64位時,我遇到了一些麻煩,使一些COM Interop代碼正常工作。代碼訪問結構化存儲API,使用從各種Windows SDK頭/ IDL文件翻譯的託管代碼。Windows結構化存儲 - 32位與64位COM互操作

當我嘗試撥打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. 
+0

SizeConst = 0肯定是不正確的,這需要SizeParamIndex。 PropertySpec.name在64位模式下有錯誤的偏移量,它是8。用C++/CLI編寫這段代碼的好理由。 –

+0

這就是ShellAPI的PROPSPEC結構,我只是用了更友好的名字。根據MSDN的說法,「kind」(ulKind)是一種ULONG,它在32位和64位都是相同的大小;和「propertyId」(propid)和「name」(lpwstr)是一個聯合,所以它們都應該從4開始。我在那裏弄錯了嗎? –

+0

(此外,在SizeConst好抓,說不上怎麼我錯過了,但它似乎沒有任何區別...) –

回答

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

是,這是宣佈該結構的好方法。現在你把它留給交互編碼器來計算data.name字段的偏移量,它就是正確的。

名稱字段是一個IntPtr,它在32位模式下需要4個字節,而在64位模式下需要8個字節。結構的字段與字段大小的整數倍的偏移對齊。默認包裝是8,這意味着任何8個字節或更少的字段將被對齊。這使得該字段在32位模式下的對齊要求爲4,在64位模式下爲8。以前,您使用[FieldOffset(4)]屬性將偏移量設爲4。對於32位代碼,但對於64位代碼錯誤的偏移量。

在這個MSDN Library article有一些結構包裝的背景。

+0

只是爲了確保我明白了:在64位模式下,由於'uint'字段是用'LPWSTR'場匿名聯合,這也將是8字節對齊的,即使它只有「需要「4個字節?這就是爲什麼我的電話失敗了,即使我從來沒有使用過'name'字段? –

+0

這是正確的。 * name *字段也「提升」了uint字段。工會使用本地代碼的方式。 –

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位的世界。

+0

所有這些都保留了COM調用的簽名;實際行爲是相同的。 (而不是拋出'STG_E_INVALIDPARAMETER',現在它只是返回'STG_E_INVALIDPARAMETER'。) –

+0

不,它不相同。如果我不把PreserveSig,它不起作用,如果我這樣做,它的確行得通。你測試過了嗎? –

+0

是的,我已經測試了它是否帶有簽名重寫,並且這兩個變體都在32位上工作,並且這兩種變體在64位上都失敗了。唯一的區別是'PreserveSig'調用返回錯誤HRESULT,重寫的調用將異常中的錯誤包裝並拋出。 –

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

我改變了這個,現在工作在兩個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; 
} 
+0

注意,這正是在P/Invoke的互操作助理,當你給它的非託管簽名自動完成:http://clrinterop.codeplex.com/releases/view/14120 –