Почему я не могу передать свой COM-объект интерфейсу, который он реализует на С#?
У меня есть этот интерфейс в 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. Но потом я пытаюсь использовать его у другого клиента С#:
[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 datastrong > .
EDIT: добавлен атрибут STAThread для моей основной процедуры. Это не решает проблему, но вы действительно должны использовать STAThread с COM, если вы не уверены, что вам это не нужно. См. Ответ Hans Passant ниже.
Ответы
Ответ 1
Итак, проблема заключалась в том, что моя 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 не поддерживается, я до сих пор не знаю, как это сделать.
Ответ 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-компонент as-is, а также настоящий тест.