Как правильно вызывать перечислители COM в .NET?

Я вызываю предоставленную извне COM DLL, для которой я создал оболочку COM-взаимодействия. В качестве аргумента давайте назовем интерфейс, который я хочу вызвать, IEnumFoo.

IEnumFoo имеет типичный шаблон перечислителя COM:

HRESULT Next ( 
   ULONG        celt,
   IFoo**       rgelt,
   ULONG*       pceltFetched
);

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

Когда я выбираю «Добавить ссылку» и указываю Visual Studio на эту DLL, она генерирует сборку COM-взаимодействия со следующей подписью:

void Next(uint, out IFoo, out uint)

Это позволяет коду .NET запрашивать только один объект за раз, что может значительно увеличить нагрузку на использование этих API.

Есть ли какой-то механизм, который я могу использовать для генерации версии Next, который позволил бы мне предоставить больше IFoo "слотов" над ними, которые порадовали бы упаковщика? (Я не прочь вручную отредактировать IL в сборке взаимодействия :))


person Billy ONeal    schedule 07.03.2014    source источник


Ответы (3)


Правильная подпись для этого будет такой:

void Next(
    uint celt,
    [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] IFoo[] rgelt,
    out uint pceltFetched);

Согласно MSDN, по крайней мере, нет механизма для его автоматической генерации. Даже если исходный IDL для интерфейса length_is был применен к rgelt, эта информация теряется в библиотеке типов. Поэтому вам нужно будет отредактировать сборку взаимодействия вручную.

Еще один вариант - полностью вручную определить этот интерфейс в основной сборке и просто игнорировать сгенерированную версию взаимодействия. Помните, что при приведении типов к RCW любой интерфейс с соответствующим идентификатором GUID (то есть тем, для которого QueryInterface успешно работает) будет работать, поэтому у вас может быть несколько разных управляемых интерфейсов, которые представляют разные представления одного и того же COM-интерфейса.

person Pavel Minaev    schedule 07.03.2014
comment
Я определенно пошел бы на редактирование IL, поскольку переопределение одного интерфейса среди многих в библиотеке типов может быть сложной задачей из-за эффекта снежного кома перекрестных ссылок. Вы можете переопределить всю библиотеку. Обратите внимание, что такой инструмент, как mono cecil (mono-project.com/Cecil), очень полезен для настройка скомпилированной сборки. - person Simon Mourier; 07.03.2014
comment
Похоже, это сработает :) (Тот же комментарий о помните: хотя - предполагается, что я знаю, что делаю: P) - person Billy ONeal; 07.03.2014

Не ответ на ваш вопрос, а предложение попробовать другой подход. Я бы создал оболочку C ++ / CLI для перечисления через COM-интерфейс в неуправляемом коде (таким образом, избегая накладных расходов на маршалинг), а затем построил бы управляемый List или другой контейнер, в котором вы возвращаете свои объекты.

Это почти гарантированно будет проще, чем ручная настройка IL сборки взаимодействия, и вы также можете легко отладить ее. Неуправляемый код C ++ будет довольно простым, как и управляемая оболочка вокруг него.

person xxbbcc    schedule 07.03.2014
comment
Написать прокладку вокруг этой библиотеки в C ++ / CLI в настоящее время невозможно. (Хотя это стоит рассмотреть в будущем) - person Billy ONeal; 07.03.2014
comment
@BillyONeal Необязательно делать это для всей библиотеки - вы можете написать это только для счетчиков. Вам нужно будет передать только экземпляр объекта, перечислитель, а затем оболочка вернет управляемый список. Для всех других задач вы по-прежнему можете использовать сгенерированную сборку взаимодействия. - person xxbbcc; 07.03.2014
comment
Хорошо, это интересно (+1). Все еще хочу найти решение, которое позволяет избежать C ++ / CLI, но это вариант. (Нужно выяснить, как создать .NET COM RCW из C ++ / CLI :), если я тоже пойду по этому пути) - person Billy ONeal; 07.03.2014

Если объект, реализующий этот интерфейс, является собственным, просто переопределите интерфейс в своем коде, как он должен быть, убедившись, что в интерфейсе используются те же атрибуты ComImport и Guid. Затем возьмите объект и примените его к своему интерфейсу. Вы можете нормально звонить через этот интерфейс.

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

person Jason Malinowski    schedule 07.03.2014
comment
Предложение Remember: предполагает, что читатель действительно понимает, как работает взаимодействие. Чего на этом плакате нет :) Спасибо! - person Billy ONeal; 07.03.2014
comment
Или это может быть основой нового понимания. ;-) - person Jason Malinowski; 07.03.2014