Определена ли упаковка структур, передаваемых в интерфейсах COM?

Я работаю со сторонним COM-сервером со своим собственным пользовательским интерфейсом, который устанавливает и получает структуры как некоторые из его свойств. Как это происходит, я использую C++ для клиента. Я разместил представительный код из файла IDL ниже с измененными именами и удаленными идентификаторами GUID.

Определена ли упаковка структуры, или это просто удача, что мой клиентский код использует те же параметры упаковки, что и COM-сервер? Скорее всего, что-то пойдет не так в проектах, в которых были изменены настройки упаковки компилятора C++ по умолчанию? Можно ли использовать параметр пакета "Прагма", чтобы убедиться, что параметры упаковки клиентского компилятора верны?

Я не вижу никаких упаковочных прагм или выражений ни в IDL, ни в заголовочном файле, сгенерированном из MIDL. Что бы произошло, если бы клиент был в C# или VB? Является ли поведение упаковки более четким, если оно вызывается через механизм IDispatch?

struct MyStruct
{
    int a, b;
};

[
    object,
    uuid( /* removed */ ),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IVideoOutputSettings : IDispatch{

    [propget, id(1), HRESULT MyProperty([out, retval] struct MyStruct* pVal);
    [propput, id(1), HRESULT MyProperty([in] struct MyStruct newVal);

    /* other methods */
};

2 ответа

Решение

Упаковка по умолчанию идет вдоль 8-байтовых границ, в соответствии со ссылкой на переключатель командной строки MIDL здесь:

/ Zp switch @ MSDN (справочник по языку MIDL)

Другие части вашего кода с большей вероятностью сломаются первыми, если значение пакета будет изменено, поскольку файл IDL обычно предварительно компилируется заранее, и редко кто-то намеренно изменяет переключатели командной строки, заданные для MIDL (но не настолько редко, что кто-то может возиться с C-scope #pragma pack и забудьте восстановить состояние по умолчанию).

Если у вас есть веская причина изменить настройку, вы можете явно указать упаковку с помощью pragma pack заявление.

атрибут pragma @ MSDN (справочник по языку MIDL)

Очень удачно, что ни одна из сторон не изменила настройки, которые мешали бы упаковке по умолчанию. Может ли это пойти не так? Да, если кто-то изо всех сил пытается изменить настройки по умолчанию.

При использовании файла IDL детали обычно компилируются в библиотеку типов (.tlb), и предполагается, что платформа одинакова как для серверов, так и для клиентов при использовании одной и той же библиотеки типов. Это предлагается в сносках для /Zp переключаться, так как определенные значения не будут выполняться для определенных не x86 или 16-битных целей. Также могут быть 32-битные <-> 64-битные случаи преобразования, которые могут привести к разрушению ожиданий. К сожалению, я не знаю, есть ли еще больше случаев, но настройки по умолчанию работают с минимальной суетой.

C# и VB не имеют встроенного поведения для обработки информации в.tlb; вместо этого инструмент, подобный tlbimp, обычно используется для преобразования определений COM в определения, используемые из.NET. Я не могу проверить, все ли ожидания успешны между C#/VB.NET и COM-клиентами и серверами; Однако я могу убедиться, что использование определенного параметра прагмы, отличного от 8, будет работать, если вы ссылаетесь на файл.tlb, созданный из IDL, скомпилированного с этим параметром. Хотя я бы не советовал идти против стандартного пакета прагмы, ниже приведены шаги, которые нужно выполнить, если вы хотите использовать рабочий пример в качестве справочного материала. Я создал проект C++ ATL и проект C# для проверки.

Вот инструкции на стороне C++.

  1. Я создал проект ATL под названием SampleATLProject с настройками по умолчанию в Visual Studio 2010, поля не изменились. Это должно создать проект DLL для вас.
  2. Скомпилировал проект, чтобы убедиться, что создаются надлежащие файлы интерфейса на стороне C (SampleATLProject_i.c и SampleATLProject_i.h).
  3. Я добавил простой объект ATL под названием SomeFoo к проекту. Опять же, по умолчанию не было изменено. Это создает класс с именем CSomeFoo это добавлено в ваш проект.
  4. Скомпилируйте SampleATLProject.
  5. Я щелкнул правой кнопкой мыши файл SampleATLProject.idl, затем в настройках MIDL установил выравнивание элемента Struct равным 4 байта (/Zp4).
  6. Скомпилируйте SampleATLProject.
  7. Я изменил IDL, чтобы добавить определение структуры под названием "BarStruct". Это повлекло за собой добавление определения структуры в стиле C с атрибутом uuid MIDL и записи в разделе библиотеки, ссылающейся на определение структуры. Смотрите фрагмент ниже.
  8. Скомпилируйте SampleATLProject.
  9. В представлении классов я щелкнул правой кнопкой мыши на ISomeFoo и добавил метод под названием FooIt, что занимает struct BarStruct в качестве параметра [in], называемого theBar.
  10. Скомпилируйте SampleATLProject.
  11. В SomeFoo.cpp я добавил некоторый код для распечатки размера структуры и выбросил окно сообщения, содержащее детали.

Вот мой IDL для проекта ATL.

import "oaidl.idl";
import "ocidl.idl";

[uuid(D2240D8B-EB97-4ACD-AC96-21F2EAFFE100)]
struct BarStruct
{
  byte a;
  int b;
  byte c;
  byte d;
};

[
  object,
  uuid(E6C3E82D-4376-41CD-A0DF-CB9371C0C467),
  dual,
  nonextensible,
  pointer_default(unique)
]
interface ISomeFoo : IDispatch{
  [id(1)] HRESULT FooIt([in] struct BarStruct theBar);
};
[
  uuid(F15B6312-7C46-4DDC-8D04-9DEA358BD94B),
  version(1.0),
]
library SampleATLProjectLib
{
  struct BarStruct;
  importlib("stdole2.tlb");
  [
    uuid(930BC9D6-28DF-4851-9703-AFCD1F23CCEF)      
  ]
  coclass SomeFoo
  {
    [default] interface ISomeFoo;
  };
};

Внутри CSomeFoo класс, вот реализация для FooIt(),

STDMETHODIMP CSomeFoo::FooIt(struct BarStruct theBar)
{
  WCHAR buf[1024];
  swprintf(buf, L"Size: %d, Values: %d %d %d %d", sizeof(struct BarStruct), 
           theBar.a, theBar.b, theBar.c, theBar.d);

  ::MessageBoxW(0, buf, L"FooIt", MB_OK);

  return S_OK;
}

Далее на стороне C#:

  1. Перейдите в отладочный или требуемый выходной каталог для SampleATLProject и запустите tlbimp.exe для файла.tlb, сгенерированного как часть выходных данных проекта C++. Следующее работало для меня:

    tlbimp SampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff

  2. Затем я создал консольное приложение C# и добавил ссылку на Foo.dll в проект.

  3. В папке "Ссылки" перейдите в Свойства для Foo и отключите Embed Interop Types, установив для него значение false.
  4. Я добавил оператор using для ссылки на пространство имен SampleATL.FooStuff как дано tlbimp, добавил [STAThread] приписывать Main() (Модели квартир COM должны соответствовать потреблению внутрипроцессного производства) и добавили некоторый код для вызова компонента COM.

Tlbimp.exe (Импорт библиотеки типов) @ MSDN

Вот исходный код этого консольного приложения.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SampleATL.FooStuff;

namespace SampleATLProjectConsumer
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            BarStruct s;
            s.a = 1;
            s.b = 127;
            s.c = 255;
            s.d = 128;

            ISomeFoo handler = new SomeFooClass();
            handler.FooIt(s);
        }
    }
}

Наконец, он запускается, и я получаю модальное всплывающее окно со следующей строкой:

Size: 12, Values: 1 127 255 128

Чтобы быть уверенным, что можно изменить прагма-пакет (так как 4/8-байтовая упаковка является наиболее распространенным типом выравнивания), я выполнил следующие шаги, чтобы изменить его на 1:

  1. Я вернулся в проект C++, перешел к свойствам для SampleATLProject.idl и изменил выравнивание элемента Struct на 1 (/Zp1).
  2. Перекомпилировать SampleATLProject
  3. Запустите tlbimp снова с обновленным файлом.tlb.
  4. Значок предупреждения появится в.NET File Reference для Foo, но может исчезнуть, если вы нажмете на ссылку. Если этого не произойдет, вы можете удалить и повторно добавить ссылку на консольный проект C#, чтобы убедиться, что он использует новую обновленную версию.

Я запустил его отсюда и получил этот вывод:

Size: 12, Values: 1 1551957760 129 3

Это странно. Но если мы принудительно отредактируем прагму уровня C в SampleATLProject_i.h, мы получим правильный вывод.

#pragma pack(push, 1)
/* [uuid] */ struct  DECLSPEC_UUID("D2240D8B-EB97-4ACD-AC96-21F2EAFFE100") BarStruct
    {
    byte a;
    int b;
    byte c;
    byte d;
    } ;
#pragma pack(pop)

SampleATLProject перекомпилируется здесь, без изменений в проекте.tlb или.NET, и мы получаем следующее:

Size: 7, Values: 1 127 255 128

относительно IDispatch, это зависит от того, является ли ваш клиент поздним. Клиенты с поздней привязкой должны анализировать информацию о типе IDispatch и определить правильные определения для нетривиальных типов. Документация для ITypeInfo а также TYPEATTR предполагает, что это возможно, учитывая, что cbAlignment поле предоставляет необходимую информацию. Я подозреваю, что большинство из них никогда не изменится и не пойдет против значений по умолчанию, так как отладку было бы утомительно, если что-то пошло не так или если между версиями приходилось менять ожидания. Кроме того, структуры обычно не поддерживаются многими клиентами сценариев, которые могут потреблять IDispatch, Часто можно ожидать, что только типы, регулируемые IDL oleautomation Ключевое слово поддерживаются.

Интерфейс IDispatch @ MSDN
IDispatch:: GetTypeInfo @ MSDN
Интерфейс ITypeInfo @ MSDN
Структура TYPEATTR @ MSDN

ключевое слово oleautomation @ MSDN

Да, структуры являются проблемой в COM. Если вы используете основанные на IUnknown интерфейсы, вам придется бросать кости с правильными настройками компилятора. Несколько причин изменить значение по умолчанию.

Если вы используете COM Automation, тогда вы должны объявить структуру с помощью typedef в.IDL. Чтобы клиентский код мог использовать IRecordInfo для правильного доступа к структуре, руководствуясь информацией библиотеки типов. Все, что вам нужно сделать, это убедиться, что параметр /Zp вашего компилятора совпадает с параметром /Zp файла midl.exe. Не сложно сделать.

Вы решаете проблему полностью, понимая, что любая структура может быть описана интерфейсом со свойствами. Теперь это не имеет значения.

Другие вопросы по тегам