Укажите структуру упаковки в C # реализации COM-интерфейса - PullRequest
0 голосов
/ 27 июня 2011

Можно ли указать размер упаковки структуры в реализации C # интерфейса COM?

(я знаю, как это сделать, когда структура определена на управляемой стороне, но мой вопрос касается того, когда она определена на неуправляемой стороне и реализована на управляемой стороне .)

У меня есть библиотека типов COM, которая определяет тип структуры, и метод интерфейса, который возвращает массив этих структур. У меня есть сервер C # и неуправляемый сервер C ++, которые оба реализуют этот интерфейс, и клиент C ++, который его использует. Сервер C ++ и клиент C ++ упаковывают структуру до 4 байтов в 32-разрядной сборке и до 8 байтов в 64-разрядной сборке.

Но сервер C # всегда упаковывается в 4 байта, независимо от платформы (x86, x64, AnyCPU). Это нормально? Это может быть отменено?

Структура выглядит следующим образом:

typedef [v1_enum] enum { blah... } HandlerPriority;
struct HandlerInfo { BSTR MessageName; HandlerPriority Priority; }

Компиляторы Visual Studio C ++ и MIDL используют упаковку по умолчанию / Zp8. В 32-битной сборке оба члена структуры имеют ширину 4 байта и поэтому не дополняются. В 64-битной сборке указатель строки составляет 8 байтов, а enum 4, поэтому перечисление дополняется. Естественно, это вызывает проблемы, когда клиент C # отправляет незаполненные данные.

Я могу исправить (обойти?) Проблему, указав / Zp4 для удаления отступов, и все, кажется, работает нормально. Но мне интересно, будет ли это лучшим решением?

Я предполагаю, что упаковка по умолчанию - / Zp8 только из соображений производительности. Насколько я понимаю, по умолчанию на x64 аппаратное обеспечение перехватывает и обрабатывает исключения выравнивания, поэтому, по крайней мере, мы не будем аварийно завершать работу. И в этой конкретной ситуации меня не волнует снижение производительности, поскольку интерфейсная функция вызывается только при запуске системы. И даже если бы мне было все равно, я все равно мог бы принять это как стоимость взаимодействия COM / .NET. Но мне немного неловко, потому что это неправильно (я полагаю, из C ++ фона)

С другой стороны, если просто невозможно поменять упаковку на управляемой стороне, тогда я буду жить с этим.

Может кто-нибудь дать мне совет?

Ответы [ 3 ]

2 голосов
/ 19 марта 2012

Размер упаковки определяется программой tlbimp.exe в RCW, который он генерирует. Если я передаю / platform: x64 в командной строке, тогда RCW говорит «.pack 8», но если я говорю / platform: x86 или / platform: agnostic, то он говорит «.pack 4».

Я хочу / платформа: независимая, чтобы я мог использовать один и тот же RCW на 32- и 64-битных платформах. Обычно я нахожу, что AnyCPU - это больше проблем, чем стоит, но этот проект является SDK, и я не хочу навязывать свои взгляды на эту тему моим пользователям, если я могу избежать этого.

Я также хочу 8-байтовую упаковку, потому что 4-байтовая упаковка на x64 может быть дорогой. Смотри http://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx.

Решение, на котором я остановился, состоит в том, чтобы декомпилировать RCW, изменить директиву .pack в сгенерированном исходном коде и перекомпилировать.

2 голосов
/ 29 марта 2013

У нас была похожая проблема, некоторые структуры, когда передавались из C # в C ++ через COM, работали в 32-битном COM, но были повреждены в 64-битном.

Я потратил некоторое время на изучение этого потокабыло также полезно.

Похоже, что TlbImp.exe пытается упаковать каждую структуру как выровненные по 4 байта, и только если она не соответствует определенным критериям, упаковывает структуру как выровненную по 8 байтов.Это делается как для 32-битного, так и для 64-битного режима.

Но у него есть странные правила для его определения.Я обнаружил, что для x64, если структура имеет только 4-байтовые целые числа, перечисления и списки защиты, она упаковывается как выровненные по 4 байта, в противном случае она упаковывается как выровненные по 8 байтов.Для x86 структура упаковывается как выровненные по 8 байт, когда в ней присутствует 64-битная переменная, в противном случае она упаковывается как выровненная по 4 байта.Возможно, есть и другие варианты, но это только то, что мы нашли в нашем продукте.

C ++, с другой стороны, с его упаковкой по умолчанию (8 байт), упаковывает каждый тип, выровненный минимум на 8 байт илиразмер члена.

Таким образом, для 32-разрядного режима проблем не возникает, поскольку нет разницы между выравниванием в 4 и 8 байтов, если в структуре нет элементов размером 8 байтов.Но если они есть, TlbImp.exe упаковывает структуру как выровненную по 8 байтам, так что это также соответствует C ++.

Для 64-битного режима, однако, есть случай, когда структура имеет 8-байтовый член, но упакован как выровненный 4 байта.В нашем случае это когда есть SAFEARRAY.Если структура имеет только целые числа SAFEARRAY и 32- (или меньше?) Битов, она упаковывается в 4 байта, выровненных для платформы x64.Но, поскольку SAFEARRAY является 64-битным указателем на стороне C ++, он упакован там по-разному, заполняя области перед SAFEARRAY до 8-байтовых полей.Я не знаю, является ли это ошибкой или функцией.

Чтобы исправить эту проблему, мы вставили инструкции C ++ pragma pack перед такими 4-байтовыми выровненными структурами в 64-битной версии.Мы определили такие структуры, декомпилировав сборку с помощью ildasm.exe и выполнив поиск «.pack 4» (спасибо Ciaran за подсказку).

Такая структура в IDL выглядит следующим образом:

   // some of our structures must be 4 bytes aligned because TlbImp packs them this way
   cpp_quote("#pragma pack(push, 4)")

   typedef [uuid("3F253C09-D7F5-3BE-9698-00CB49A7005C"),
         helpstring("SOME structure"),
         version(5.1)] struct SOMEINFO
   {
      long ItemsActive;
      SAFEARRAY(BSTR) ItemIDs; // without pack 4 it would be 8 bytes aligned on x64
      SAFEARRAY(long) ItemRuns;
      SAFEARRAY(SOMEFunctions) ItemFuncList;
      SAFEARRAY(VARIANT) ItemFactors;
      long MIndex;
      SAFEARRAY(VARIANT) DeltaItems;
   } SOMEINFO;

   cpp_quote("#pragma pack(pop)") // matches pack(push, 4) placed before the structure

В качестве альтернативы, мы могли бы просто добавить к этой структуре 64-битную фиктивную переменную или BSTR, чтобы принудительно вставить ее в .pack 8,но мы не хотели менять интерфейс.

Итак, мы в основном следуем TlbImp по его странной логике.Но в нашем коде есть только несколько таких структур, так что это хорошо для нас.Таким образом, и 32-, и 64-разрядные COM-устройства правильно передают все структуры.

1 голос
/ 13 марта 2012

Будет ли атрибут Pack работать здесь для вас?Вот пример из моего собственного кода:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct TOKEN_PRIVILEGES
{
   public int privilegeCount;
   public LUID_AND_ATTRIBUTES privileges;
}

Я использовал это при настройке служб и использую атрибут Pack = 1 для выравнивания поля привилегий точно после поля privilegeCount при маршалинге этой структуры в Win32.

...