Почему я не могу привести мой COM-объект к интерфейсу, который он реализует в C#?

У меня есть этот интерфейс в DLL (этот код показан в Visual Studio из метаданных):

#region Assembly XCapture.dll, v2.0.50727
// d:\svn\dashboard\trunk\Source\MockDiagnosticsServer\lib\XCapture.dll
#endregion

using System;
using System.Runtime.InteropServices;

namespace XCapture
{
    [TypeLibType(4160)]
    [Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]
    public interface IDiagnostics
    {
        [DispId(1)]
        void GetStatusInfo(int index, ref object data);
    }
}

Поэтому я создал COM-сервер с таким классом:

[ComVisible(true)]
[Guid(SimpleDiagnosticsMock.CLSID)]
[ComDefaultInterface(typeof(IDiagnostics))]
[ClassInterface(ClassInterfaceType.None)]
public class SimpleDiagnosticsMock : ReferenceCountedObject, IDiagnostics
{
    public const string CLSID = "281C897B-A81F-4C61-8472-79B61B99A6BC";

    // These routines perform the additional COM registration needed by 
    // the service. ---- stripped from example

    void IDiagnostics.GetStatusInfo(int index, ref object data)
    {
        Log.Info("GetStatusInfo called with index={0}, data={1}", index, data);

        data = index.ToString();
    }
}

Сервер, кажется, работает нормально, и я могу использовать объект из VBScript. Но затем я пытаюсь использовать его из другого клиента C#:

    [STAThread]
    static void Main(string[] args)
    {
        Guid mockClsId = new Guid("281C897B-A81F-4C61-8472-79B61B99A6BC");
        Type mockType = Type.GetTypeFromCLSID(mockClsId, true);
        IDiagnostics mock = (IDiagnostics)Activator.CreateInstance(mockType);

        //var diag = mock as IDiagnostics;

        object s = null;
        mock.GetStatusInfo(3, ref s);

        Console.WriteLine(s);
        Console.ReadKey();
    }

И это не с

Невозможно привести объект COM типа "System.__ComObject" к типу интерфейса "XCapture.IDiagnostics". Эта операция завершилась неудачно, поскольку вызов QueryInterface для компонента COM для интерфейса с IID '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}' завершился ошибкой из-за следующей ошибки: такой интерфейс не поддерживается (Исключение из HRESULT: 0x80004002 (E_NOINTERFACE)),

Что я делаю неправильно?

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

РЕДАКТИРОВАТЬ: добавил атрибут STAThread в мою основную процедуру. Это не решает проблему, но вам действительно следует использовать STAThread с COM, если вы абсолютно не уверены, что вам это не нужно. Смотрите ответ Ханса Пассанта ниже.

2 ответа

Решение

Итак, проблема была в том, что моя DLL с интерфейсом IDiagnostics была сгенерирована из TLB, и что TLB так и не был зарегистрирован.

Поскольку DLL была импортирована из TLB, RegAsm.exe отказывается регистрировать библиотеку. Поэтому я использовал инструмент regtlibv12.exe для регистрации самого TLB:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\regtlibv12.exe "$(ProjectDir)\lib\Diagnostics.tlb"

Тогда все волшебным образом начало работать.

Поскольку regtlibv12 не поддерживается, я до сих пор не знаю, как это сделать правильно.

Это исключение может быть проблемой DLL Ада. Но самое простое объяснение - это то, чего не хватает в вашем фрагменте. В вашем методе Main() отсутствует атрибут [STAThread].

Это важный атрибут, который имеет значение, когда вы используете COM-объекты в вашем коде. Большинство из них не являются поточно-ориентированными, и для них требуется поток, который является гостеприимным домом для кода, который не может поддерживать многопоточность. Атрибут задает состояние потока, которое вы можете явно указать с помощью Thread.SetApartmentState(). Что нельзя сделать для основного потока приложения, поскольку Windows запускает его, поэтому атрибут используется для его настройки.

Если вы пропустите его, то вы присоединитесь к многопоточной квартире MTA. Затем COM вынужден создать новый поток, чтобы обеспечить безопасность компонента. Что требует маршалинга всех вызовов из вашего основного потока в этот вспомогательный поток. Ошибка E_NOINTERFACE возникает, когда COM не может найти способ сделать это, ему нужен помощник, который знает, как сериализовать аргументы метода. Об этом должен позаботиться разработчик COM, он этого не делал. Небрежно, но не необычно.

Требование потока STA состоит в том, что он также прокачивает цикл сообщений. Тип, который вы получаете в приложении Winforms или WPF из Application.Run (). У вас его нет в вашем коде. Вам может это сойти с рук, поскольку вы на самом деле не делаете никаких вызовов из рабочего потока. Но компоненты COM, как правило, полагаются на цикл сообщений, чтобы быть доступными для собственного использования. Вы заметите это по неправильному поведению, а не по возникновению события или блокировке.

Так что начните исправлять это, применяя атрибут в первую очередь:

[STAThread]
static void Main(string[] args)
{
    // etc..
}

Который решит это исключение. Если у вас есть описанные проблемы с возникновением событий или взаимоблокировками, вам нужно изменить тип приложения. Winforms обычно легко начать.

В противном случае я не могу нанести удар по насмешливой неудаче. С COM связаны значительные детали развертывания, необходимо записать ключи реестра, чтобы COM мог обнаруживать компоненты. Вы должны получить правильные направляющие и интерфейсы должны быть точно совпадают. Regasm.exe требуется для регистрации компонента.NET, который [ComVisible]. Если вы попытаетесь смоделировать существующий COM-компонент и получите его правильно, то вы уничтожите регистрацию реального компонента. Не уверен, что это стоит того;) И у вас возникнет серьезная проблема с добавлением ссылки на сборку [ComVisible], в IDE отказывается разрешить программе.NET использовать сборку.NET через COM. Только поздняя привязка может обмануть машину. Судя по исключению из COM, вы еще не приблизились к насмешкам. Лучше всего использовать COM-компонент как есть, а также настоящий тест.

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