Почему я не могу привести свой 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, и это вроде сработало, за исключением того, что мне не удалось получить параметр data, возвращенный ref.

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


person catbert    schedule 05.06.2013    source источник
comment
Я бы хотел использовать dynamic mock = Activator.CreateInstance(mockType);   -  person Matthew Watson    schedule 05.06.2013
comment
Вы зарегистрировали компонент COM на клиенте? (с использованием RegAsm)   -  person Kevin Gosse    schedule 05.06.2013
comment
@MatthewWatson - Это приводит к тому, что «System .__ ComObject» не содержит определения для «GetStatusInfo», когда я пытаюсь вызвать для него метод.   -  person catbert    schedule 05.06.2013
comment
@KooKiz - да, он зарегистрирован и объект фактически создается на сервере. Просто не могу закинуть на интерфейс.   -  person catbert    schedule 05.06.2013
comment
@MatthewWatson: Поскольку SimpleDiagnosticsMock использует явную реализацию интерфейса, это не сработает.   -  person Daniel Hilgarth    schedule 05.06.2013
comment
@DanielHilgarth Ах да, я не заметил эту деталь.   -  person Matthew Watson    schedule 05.06.2013
comment
Тот факт, что вы получаете обратно System .__ ComObject, означает, что COM все еще создает собственный COM-объект, а не .NET. Может быть, несовместимость битов между клиентом и сервером (32-битный или 64-битный реестр?)   -  person Simon Mourier    schedule 05.06.2013


Ответы (2)


Это исключение может быть проблемой DLL Hell. Но самое простое объяснение - это то, чего отсутствует в вашем фрагменте. В вашем методе 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-компонент как есть, тоже настоящий тест.

person Hans Passant    schedule 05.06.2013
comment
Спасибо за ответ, это очень помогло мне разобраться в проблеме. Однако ничего из того, что вы упомянули, не было причиной. Ну вот и COM для вас :-) - person catbert; 06.06.2013
comment
В моем вопросе Почему я не могу преобразовать MsftDiscFormat2Data в IBurnVerification Я испытываю именно эта проблема ... Но что помогло мне в работе с гипсом, так это удаление [STAThread] из Program.cs. Не могли бы вы дать мне совет, почему это могло быть так? - person horgh; 19.08.2013

Итак, проблема заключалась в том, что моя 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 не поддерживается, я до сих пор не знаю, как это сделать правильно.

person catbert    schedule 06.06.2013
comment
Хм, нет, вы нашли обходной путь для отсутствующего атрибута [STAThread]. После регистрации библиотеки типов, что обычно выполняется с помощью параметра Regasm.exe / tlb, стандартный маршаллер теперь выполняет маршалинг вызовов из одного потока в другой. Что может быть неплохим для вас, но основная проблема по-прежнему заключается в том, что код работает не в том потоке. Остерегайтесь других побочных эффектов, таких как накладные расходы на вызовы. Regtlibv12 не поддерживается, потому что это всегда неподходящий инструмент. Единственная причина, по которой он существует, - это то, что он нужен установщику .NET framework. - person Hans Passant; 06.06.2013
comment
Я хотел бы просто согласиться с вами, но в моем конкретном случае атрибут [STAThread] не решает проблему. Мне все еще нужно использовать regtlibv12, и я до сих пор не знаю, зачем мне его использовать. Я обновлю рассматриваемый код, чтобы отразить мое использование STAThread. - person catbert; 07.06.2013
comment
Каков контекст этого кода, кто его запускает? Участвует ли исполнитель модульных тестов? Затем этот определяет состояние квартиры потока. Дважды проверьте с помощью Thread.CurrentThread.GetApartmentState (). - person Hans Passant; 07.06.2013
comment
@HansPassant Ну, я запускаю его, нажав F5 в Visual Studio или запустив исполняемый файл из командной строки. GetApartmentState возвращает STA. - person catbert; 07.06.2013
comment
Вы получаете STA без использования [STAThread] ??? Или ты действительно воспользовался моим ответом? - person Hans Passant; 07.06.2013
comment
Я изменил свой код, чтобы использовать STAThread, как только увидел ваш ответ. Это просто не помогло. Однако регистрация TLB помогла. - person catbert; 07.06.2013
comment
Что ж, удалите [STAThread] еще раз и посмотрите, работает ли он по-прежнему. Если нет, то это неправильный ответ. Почему у вас было две проблемы, не ясно из вопроса. Вы уже знаете, что вы решили вторую проблему неправильно. Пожалуйста, не отмечайте неправильный ответ, это никому не поможет. - person Hans Passant; 07.06.2013
comment
Если я укажу STAThread, это не повлияет на результат. Код работает, если я зарегистрирую TLB, если я этого не сделаю, ничего не заработает. Регистрация TLB работает, а STAThread - нет. Извините, если вызвал путаницу. - person catbert; 07.06.2013
comment
позвольте нам продолжить обсуждение в чате - person catbert; 07.06.2013
comment
Да, последний комментарий о том, что проблема TLB находит отклик у меня. Я получаю такую ​​ошибку очень редко, от какого-то пользователя - где код, несомненно, правильный, так как он работает на сотнях машин. Тем не менее, для некоторых, где что-то пошло не так с реестром, я получаю это. - person PandaWood; 11.01.2016
comment
что делать, если у вас нет тлб? например на машине клиента? - person Steinfeld; 14.03.2016