Как компилятор С# обнаруживает типы COM?
EDIT: Я написал результаты в виде сообщения .
Компилятор С# обрабатывает типы COM несколько волшебным образом. Например, это утверждение выглядит нормально...
Word.Application app = new Word.Application();
... пока вы не поймете, что Application
- это интерфейс. Вызов конструктора на интерфейсе? Йойку! Это фактически переводится в вызов Type.GetTypeFromCLSID()
, а другой - Activator.CreateInstance
.
Кроме того, в С# 4 вы можете использовать аргументы non-ref для параметров ref
, а компилятор просто добавляет локальную переменную для передачи по ссылке, отбрасывая результаты:
// FileName parameter is *really* a ref parameter
app.ActiveDocument.SaveAs(FileName: "test.doc");
(Да, есть недостаток аргументов. Не являются ли дополнительные параметры приятными?:)
Я пытаюсь исследовать поведение компилятора, и я не могу подделать первую часть. Я могу сделать вторую часть без проблем:
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
[ComImport, GuidAttribute("00012345-0000-0000-0000-000000000011")]
public interface Dummy
{
void Foo(ref int x);
}
class Test
{
static void Main()
{
Dummy dummy = null;
dummy.Foo(10);
}
}
Я бы хотел написать:
Dummy dummy = new Dummy();
хотя. Очевидно, что это пойдет во время исполнения, но все в порядке. Я просто экспериментирую.
Другие атрибуты, добавленные компилятором для связанных COM PIA (CompilerGenerated
и TypeIdentifier
), похоже, не делают трюк... какой волшебный соус?
Ответы
Ответ 1
Ни в коем случае я не специалист в этом, но недавно я наткнулся на то, что, я думаю, вы хотите: CoClass класс атрибута.
[System.Runtime.InteropServices.CoClass(typeof(Test))]
public interface Dummy { }
Компонент поставляет бетон реализация одного или нескольких интерфейсы. В COM такой бетон реализации могут быть записаны в любом язык программирования, поддерживающий COM компонентов, например. Delphi, С++, Visual Basic и т.д.
См. мой ответ на аналогичный вопрос о Microsoft Speech API, где вы можете "создать экземпляр" интерфейса SpVoice
(но на самом деле, повторное создание SPVoiceClass
).
[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Ответ 2
Между вами и Майклом вы почти собрали кусочки. Я думаю, что так оно и работает. (Я не писал код, поэтому я мог бы немного неправильно его сформулировать, но я уверен, что так оно и есть.)
Если:
- вы "новый" тип интерфейса и
- тип интерфейса имеет известный класс, а
- Вы используете функцию "no pia" для этого интерфейса.
тогда код генерируется как (IPIAINTERFACE) Activator.CreateInstance(Type.GetTypeFromClsid(GUID OF COCLASSTYPE))
Если:
- вы "новый" тип интерфейса и
- тип интерфейса имеет известный класс, а
- Вы НЕ используете функцию "no pia" для этого интерфейса.
тогда код генерируется так, как если бы вы сказали "новый COCLASSTYPE()".
Джон, не стесняйтесь меня или Сэма, если у вас есть вопросы по этому поводу. FYI, Сэм является экспертом в этой функции.
Ответ 3
Хорошо, это всего лишь наложить немного больше на ответ Майкла (он может добавить его, если захочет, и в этом случае я удалю этот).
Глядя на исходный PIA для Word.Application, задействованы три типа (игнорирование событий):
[ComImport, TypeLibType(...), Guid("..."), DefaultMember("Name")]
public interface _Application
{
...
}
[ComImport, Guid("..."), CoClass(typeof(ApplicationClass))]
public interface Application : _Application
{
}
[ComImport, ClassInterface(...), ComSourceInterfaces("..."), Guid("..."),
TypeLibType((short) 2), DefaultMember("Name")]
public class ApplicationClass : _Application, Application
{
}
Есть два интерфейса по причинам, о которых говорит Эрик Липпер в другом ответе. И там, как вы сказали, есть CoClass
- как с точки зрения самого класса, так и с атрибутом на интерфейсе Application
.
Теперь, если мы используем связывание PIA в С# 4, некоторые из них встроены в результирующий двоичный файл... но не все. Приложение, которое просто создает экземпляр Application
, заканчивается этими типами:
[ComImport, TypeIdentifier, Guid("..."), CompilerGenerated]
public interface _Application
[ComImport, Guid("..."), CompilerGenerated, TypeIdentifier]
public interface Application : _Application
Нет ApplicationClass
- предположительно, потому что он будет динамически загружаться из реального COM-типа во время выполнения.
Еще одна интересная разница в коде между связанной версией и несвязанной версией. Если вы декомпилируете строку
Word.Application application = new Word.Application();
в указанной версии он заканчивается как:
Application application = new ApplicationClass();
тогда как в связанной версии он заканчивается как
Application application = (Application)
Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("...")));
Итак, похоже, что для "реального" PIA нужен атрибут CoClass
, но связанная версия не потому, что не существует CoClass
, который может ссылаться на компилятор. Он должен делать это динамически.
Я могу попытаться подделать COM-интерфейс, используя эту информацию, и посмотреть, могу ли я заставить компилятор связать его...
Ответ 4
Просто добавьте немного подтверждения Майклу:
Следующий код компилируется и запускается:
public class Program
{
public class Foo : IFoo
{
}
[Guid("00000000-0000-0000-0000-000000000000")]
[CoClass(typeof(Foo))]
[ComImport]
public interface IFoo
{
}
static void Main(string[] args)
{
IFoo foo = new IFoo();
}
}
Для этого вам нужны как ComImportAttribute
, так и GuidAttribute
.
Также обратите внимание на информацию при наведении указателя мыши на new IFoo()
: Intellisense правильно подбирает информацию: Nice!