Почему в перечислении System.Runtime.InteropServices.ClassInterfaceType нет значения AutoUnknown?

Перечисление http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.classinterfacetype.aspx в.NET имеет AutoDispatch значение и AutoDual ценность, но нет AutoUnknown значение. Почему нет, и кто-нибудь уже придумал довольно автоматизированный способ сделать это, чтобы мне не пришлось изобретать велосипед?

Для более подробной информации, вот три значения в перечислении в настоящее время и что они делают:

  • ClassInterfaceType.None не создает интерфейс класса для вас. Это рекомендуемое значение Microsoft. Без интерфейса класса вы должны создать свой собственный интерфейс с любыми членами, которые вы намереваетесь, и заставить ваш класс реализовать его. Затем вы можете использовать атрибут http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.classinterfaceattribute.aspx в своем интерфейсе, чтобы сделать его интерфейсом (поддержка клиентов с поздней привязкой), неизвестным интерфейсом (поддержка клиентов с ранней привязкой) или двойным (поддержка клиентов с ранней и поздней привязкой).
  • ClassInterfaceType.AutoDispatch создает интерфейс класса, производный от IDispatch без явных членов. Без явных членов он может поддерживать только абонентов с поздней привязкой.
  • ClassInterfaceType.AutoDual создает интерфейс класса, производный от IDispatch у него есть явные члены для всех открытых нестатических членов этого класса, а также для всех членов его базового класса и любых реализованных интерфейсов. Microsoft настоятельно не рекомендует использовать это значение "из-за ограничений версий", что связано с тем, что в случае изменения любого из членов класса интерфейс класса также изменится. (Таким образом, вызывающие абоненты, которые не связываются повторно после изменения класса, могут в итоге вызвать неправильные методы или передать неверные параметры.)

Так что мне интересно, почему нет значения под названием ClassInterfaceType.AutoUnknown который бы создал интерфейс класса, производный от IUnknown который явно объявляет членов вашего класса (не базовый класс или другие интерфейсы, которые он реализует).

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

Так что же здесь за история, чего мне не хватает? Я знаю, что Microsoft говорит, что рекомендует не использовать AutoDual из-за "проблем с версиями", и что эти опасения в некоторой степени относятся и к AutoUnknown; но, несмотря на эту рекомендацию, у них все еще есть AutoDual. Почему бы им не иметь AutoUnknown?

Несколько слов об этих "проблемах управления версиями": наиболее опасные аспекты этих проблем относятся только к абонентам с поздней связью. AutoUnknown будет генерировать IUnknownинтерфейс, а не IDispatchинтерфейс, поэтому нам нужно только заниматься проблемами с версиями, которые могут возникнуть у ранних клиентов. И поскольку IID любого автоматически сгенерированного интерфейса класса будет меняться каждый раз, когда меняются открытые нестатические члены класса, клиент с ранним связыванием не сможет сделать "плохой вызов" существующим членам, он просто не сможет выполнить QueryInterface() для интерфейса класса. Все еще неудача, но управляемая, а не крах или что-то еще. Это даже не нарушает правило COM, запрещающее изменение интерфейса; это не изменяется, это становится новым интерфейсом (новый IID). Действительно, он практически не отличается от IUnknown половина механизма AutoDual (минус производные элементы). На самом деле, он сталкивается с меньшим количеством "проблем с версиями", чем AutoDual, потому что AutoDual также имеет все проблемы с поздним связыванием.

Итак, еще раз я должен спросить: почему AutoDual существует, если AutoUnknown не существует!

Чтобы привести некоторые реальные примеры, рассмотрим следующий код C#:

using System.Runtime.InteropServices;

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class F
{
    public int foo()
    {
        return 5;
    }
}

генерирует следующий idl:

[
  uuid(1F3A7DE1-99A1-37D4-943E-1BF5CFDF7DFA),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")
]
coclass F {
    [default] interface _F;
    interface _Object;
};

[
  odl,
  uuid(3D0A1144-1C0B-3877-BE45-AD8318898790),
  hidden,
  dual,
  nonextensible,
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")    

]
interface _F : IDispatch {
    [id(00000000), propget,
      custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
    HRESULT ToString([out, retval] BSTR* pRetVal);
    [id(0x60020001)]
    HRESULT Equals(
                    [in] VARIANT obj, 
                    [out, retval] VARIANT_BOOL* pRetVal);
    [id(0x60020002)]
    HRESULT GetHashCode([out, retval] long* pRetVal);
    [id(0x60020003)]
    HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x60020004)]
    HRESULT foo([out, retval] long* pRetVal);
};

Это нормально, но я бы предложил AutoUnknown вместо этого сгенерировать этот idl:

[
  uuid(1F3A7DE1-99A1-37D4-943E-1BF5CFDF7DFA),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")
]
coclass F {
    [default] interface _F;
    interface _Object;
};

[
  odl,
  uuid(BC84F393-DACC-353F-8DBE-F27CB2FB4757),
  version(1.0),
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "_F")    

]
interface _F : IUnknown {
    HRESULT _stdcall foo([out, retval] long* pRetVal);
};

Если сейчас нет способа сделать это автоматически, я напишу что-то, что использует рефлексию, так что я был бы признателен за любые рекомендации в этой области. Например, есть ли какие-либо атрибуты, которые мне нужно проверить в методах? По параметрам? Я закопался в ил System.Runtime.InteropServices.TypeLibConverter чтобы увидеть, как он это делает, но, к сожалению, все вкусности в закрытой внутренней функции nConvertAssemblyToTypeLib(), к которой я не могу добраться.

Спасибо!

1 ответ

Вопрос в том, чего вы пытаетесь достичь?

От ClassInterface docs: "Указывает тип интерфейса класса, который будет сгенерирован для класса, доступного для COM, если интерфейс генерируется вообще".

Идея позади ClassInterface сделать доступным программный интерфейс класса для COM-клиентов - вот почему вы видите полный интерфейс, включая методы базового класса. Цель не состоит в том, чтобы предоставить представление подмножеству функциональных возможностей класса. Вот для чего нужны интерфейсы (и ClassInterfaceType.None)

Предоставление интерфейса класса без возможности позднего связывания (т.е. IDispatch) это то, что вы не должны делать (и ClassInterface к счастью, не включает), так как это напрямую зависит от макета вашего класса для правильной работы. Это означает, что ни класс, ни его базовые классы никогда не должны изменяться. Это то, что трудно гарантировать, и почему это вообще плохая идея, помимо того, что она противоречит всей философии COM. Ранние клиенты тесно связаны с vtable макетом интерфейса - каждое изменение в макете нарушит его. У клиентов с поздним связыванием эта проблема не возникает, поскольку методы разрешаются по имени, и они могут правильно обрабатывать отсутствующий метод.

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

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