2013-05-28 1 views
0

При попытке конвертировать существующее 32-разрядное приложение в 64-разрядное, мне не удалось заставить код COM Interop работать правильно. Код обращается к структурированному API хранения, используя управляемый код, который я переводил из различных файлов заголовков/IDL Windows SDK.Структурированное хранилище Windows - 32-разрядное и 64-битное COM-взаимодействие

Код не работает, когда я пытаюсь позвонить в IPropertyStorage.ReadMultiple(), с STG_E_INVALIDPARAMETER. Предыдущие вызовы interop, 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

РазмерConst = 0, конечно, неверно, для этого требуется SizeParamIndex. Свойство PropertySpec.name имеет неправильное смещение в 64-битном режиме, это 8.Хорошие причины для написания этого кода в C++/CLI btw. –

+0

Это структура PROPSPEC из ShellAPI, я просто использовал более дружественные имена. Согласно MSDN, «kind» (ulKind) является ULONG, который имеет одинаковый размер как на 32, так и на 64-битных; и «propertyId» (провидный) и «имя» (lpwstr) являются союзом, поэтому оба они должны начинать со смещения 4. Я получил что-то не так? –

+0

(также, хороший улов на SizeConst, dunno, как я пропустил это, но это, казалось, не имеет никакого значения ...) –

ответ

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

Да , это хороший способ объявить эту структуру. Теперь вы оставите его до маршаллера pinvoke interop, чтобы вычислить смещение поля данных .Name, и он получает все правильно.

имя Поле - это IntPtr, оно занимает 4 байта в 32-битном режиме, но 8 байтов в 64-битном режиме. Поля структуры выравниваются со смещением, которое является целым числом, кратным размеру поля. Упаковка по умолчанию - 8, что означает, что любое поле размером 8 байт или меньше будет выровнено. Это дает этому полю требование выравнивания 4 в 32-битном режиме, 8 в 64-битном режиме. Раньше вы вынуждали его со смещением 4 с помощью атрибута [FieldOffset (4)]. Хорошо для 32-битного кода, но неправильное смещение для 64-битного кода.

В этом MSDN Library article есть некоторый фон.

+0

, чтобы убедиться, что я получу его: в 64-битном режиме, поскольку поле 'uint' находится в анонимном объединении с полем' LPWSTR', оно также будет выровнено по 8 байт, хотя оно только " требуется "4 байта? Вот почему мой вызов завершился неудачно, хотя я никогда не использовал поле «имя» ни для чего? –

+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 declarations, вы можете посмотреть этот 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

После нескольких повторений определений взаимодействия я, наконец, наткнулся на ответ. Я не совсем уверен почему это имеет значение, но изменение, которое я сделал, должно было заменить единственные определения структуры 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; 
} 

Я изменил, что в этом, и теперь он работает на обоих 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 Interop Assistant автоматически, когда вы кормите его неуправляемой подписью: http://clrinterop.codeplex.com/releases/view/14120 –