Определена ли упаковка структур, передаваемых в интерфейсах 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++.
- Я создал проект ATL под названием SampleATLProject с настройками по умолчанию в Visual Studio 2010, поля не изменились. Это должно создать проект DLL для вас.
- Скомпилировал проект, чтобы убедиться, что создаются надлежащие файлы интерфейса на стороне C (SampleATLProject_i.c и SampleATLProject_i.h).
- Я добавил простой объект ATL под названием
SomeFoo
к проекту. Опять же, по умолчанию не было изменено. Это создает класс с именемCSomeFoo
это добавлено в ваш проект. - Скомпилируйте SampleATLProject.
- Я щелкнул правой кнопкой мыши файл SampleATLProject.idl, затем в настройках MIDL установил выравнивание элемента Struct равным 4 байта (/Zp4).
- Скомпилируйте SampleATLProject.
- Я изменил IDL, чтобы добавить определение структуры под названием "BarStruct". Это повлекло за собой добавление определения структуры в стиле C с атрибутом uuid MIDL и записи в разделе библиотеки, ссылающейся на определение структуры. Смотрите фрагмент ниже.
- Скомпилируйте SampleATLProject.
- В представлении классов я щелкнул правой кнопкой мыши на
ISomeFoo
и добавил метод под названиемFooIt
, что занимаетstruct BarStruct
в качестве параметра [in], называемого theBar. - Скомпилируйте SampleATLProject.
- В 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#:
Перейдите в отладочный или требуемый выходной каталог для SampleATLProject и запустите tlbimp.exe для файла.tlb, сгенерированного как часть выходных данных проекта C++. Следующее работало для меня:
tlbimp SampleATLProject.tlb /out:Foo.dll /namespace:SampleATL.FooStuff
Затем я создал консольное приложение C# и добавил ссылку на Foo.dll в проект.
- В папке "Ссылки" перейдите в Свойства для
Foo
и отключите Embed Interop Types, установив для него значение false. - Я добавил оператор 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:
- Я вернулся в проект C++, перешел к свойствам для SampleATLProject.idl и изменил выравнивание элемента Struct на 1 (/Zp1).
- Перекомпилировать SampleATLProject
- Запустите tlbimp снова с обновленным файлом.tlb.
- Значок предупреждения появится в.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
Да, структуры являются проблемой в COM. Если вы используете основанные на IUnknown интерфейсы, вам придется бросать кости с правильными настройками компилятора. Несколько причин изменить значение по умолчанию.
Если вы используете COM Automation, тогда вы должны объявить структуру с помощью typedef в.IDL. Чтобы клиентский код мог использовать IRecordInfo для правильного доступа к структуре, руководствуясь информацией библиотеки типов. Все, что вам нужно сделать, это убедиться, что параметр /Zp вашего компилятора совпадает с параметром /Zp файла midl.exe. Не сложно сделать.
Вы решаете проблему полностью, понимая, что любая структура может быть описана интерфейсом со свойствами. Теперь это не имеет значения.