Почему иногда работает доступ к COM-объекту из .NET, не проходя через класс Interop?

Когда вы связываете COM-объект с кодом .NET, VS создает межобъектную DLL, с классами interop.

Пример:

У вас есть foo.dll реализует COM-библиотеку Foo, которая включает в себя реализацию COM-интерфейса "IBar". Вы добавляете ссылку на foo.dll в проект .NET. в /bin, вы увидите файл Interop.FooLib.dll. В обозревателе объектов вы увидите Interop.FooLib, под которым вы увидите FooLib, под которым вы увидите BarClass, под которым вы увидите Base Types, под этой панелью и IBar.

В вашем .NET-коде при объявлении переменной вы можете ввести FooLib, а intellisense предоставит вам параметры Bar или BarClass().

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

То есть, оба из них должны работать:

FooLib.BarClass theBar = new FooLib.BarClass();
FooLib.Bar theBar = new FooLib.BarClass();

Но это не должно работать:

FooLib.Bar theBar = new FooLib.Bar();

Вот проблема. Мы просто отслеживали нечетную ошибку, где код, который работал для некоторых клиентов, и работал в наших средах разработки и тестирования, не работал на одном клиентском сайте, оказался программистом, использующим конструктор Bar().

Итак, может ли кто-нибудь объяснить, в чем разница между двумя конструкторами: Bar() и BarClass()?

Может кто-нибудь объяснить, почему, кажется, работает конструктор Bar()?

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

- ADDED -

Было высказано предположение, что проблема была в нашей реализации COM. Это то, что мы делаем:

IDL:

[
    object,
    uuid(...),
    dual,
    helpstring("IBar Interface"),
    pointer_default(unique),
    nonextensible
]
interface IBar : IDispatch
{
    [id(1), helpstring("method barify")] 
        HRESULT barify([out, retval] VARIANT_BOOL *rVal);
    // ...
};
// ...
[
    uuid(...),
    version(1.0),
    helpstring("Foo 1.0 Type Library")
]
library FooLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");
    // ...
    [
        uuid(...),
        helpstring("Bar Class")
    ]
    coclass Bar
    {
        [default] interface IBar;
    };
    // ...
};

Реализация:

class ATL_NO_VTABLE CBar : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CBar, &CLSID_Bar>,
    public IDispatchImpl<IBar, &IID_IBar, &LIBID_FooLib>,
    public ISupportErrorInfoImpl <&IID_IBar>
{
public:
    CBar();

    DECLARE_REGISTRY_RESOURCEID(IDR_BAR)

    DECLARE_PROTECT_FINAL_CONSTRUCT()

    BEGIN_COM_MAP(CBar)
        COM_INTERFACE_ENTRY(IBar)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY(ISupportErrorInfo)
    END_COM_MAP()

    // ...
};

- ДОБАВЛЕНИЕ ПОЗЖЕ -

Декомпилировать *, через .NET Reflector:

[ComImport, CoClass(typeof(BarClass)), Guid("...")]
public interface Bar : IBar
{
}

* Мне все равно, что интерфейс Reflector UI вызывает это разборку - если он выводит HLL, он декомпилируется.

Ответ 1

Отличный вопрос. Многие удивляются, что это работает вообще, потому что Bar - это интерфейс и, конечно же, вы не сможете создать новый экземпляр интерфейса! Но хотя я не могу найти какой-либо специфики реализации, я помню, как читал в книге взаимодействия Адама Натана СОМ, что С# делает специальное исключение для COM-интерфейсов, отмеченных знаком CoClassAttribute, и вместо этого превращает вызов в экземпляр класса.

Но я не знаю, почему это иногда срабатывало, а иногда и не срабатывало.

Ответ 2

Конструктор Bar() в принципе должен явно возвращать интерфейс, а не объект класса. Я не могу понять, как .NET поддерживает построение интерфейса!?

В любом случае вы можете нажать на конструктор Bar() и нажать Shift-F12. Это покажет вам где-нибудь еще в коде, где используется этот конструктор. Я не могу придумать, как предотвратить непреднамеренный вызов этого конструктора.

Ответ 3

Вы прочитали обсуждение в этом вопросе? Он обсуждает именно эту проблему, я думаю.