Как компилятор C # обнаруживает типы COM?

168

РЕДАКТИРОВАТЬ: я написал результаты в виде сообщения в блоге .


Компилятор C # обрабатывает COM-типы магическим образом. Например, это утверждение выглядит нормально ...

Word.Application app = new Word.Application();

... пока не поймешь, что Applicationэто интерфейс. Вызов конструктора для интерфейса? Йойку! Это на самом деле переводится в вызов Type.GetTypeFromCLSID()и другой Activator.CreateInstance.

Кроме того, в C # 4 вы можете использовать не-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), похоже, не работают ... что за волшебный соус?

Джон Скит
источник
11
Разве необязательные параметры хороши? ИМО, нет они не милые. Microsoft пытается исправить ошибку в интерфейсах Office COM, добавив в C # раздувание.
Мехрдад Афшари
18
@ Mehrdad: Дополнительные параметры полезны помимо COM, конечно. Вы должны быть осторожны со значениями по умолчанию, но между ними и именованными аргументами гораздо проще создать пригодный для использования неизменяемый тип.
Джон Скит
1
Правда. В частности, именованные параметры могут практически потребоваться для взаимодействия с некоторыми динамическими средами. Несомненно, это полезная функция, но это не значит, что она бесплатна. Это стоит простоты (четко заявленная цель дизайна). Лично я думаю, что C # удивителен для функций, которые команда остановила (иначе, это мог быть клон C ++). Команда C # великолепна, но корпоративная среда вряд ли может быть свободной от политики. Я думаю, что сам Андерс был не очень доволен этим, поскольку он заявил в своем выступлении на PDC'08: «Нам потребовалось десять лет, чтобы вернуться туда, где мы были».
Мехрдад Афшари
7
Я согласен, что команде нужно будет внимательно следить за сложностью. Динамический материал добавляет много сложности для небольшого значения для большинства разработчиков, но высокую ценность для некоторых разработчиков.
Джон Скит
1
Я видел, как разработчики фреймворка начали обсуждать его использование во многих местах. IMO, это просто время, когда мы найдем хорошее применение для dynamic... мы просто слишком привыкли к статической / строгой типизации, чтобы понять, почему это важно вне COM.
чакрит

Ответы:

145

Я ни в коем случае не эксперт в этом, но недавно я наткнулся на то, что, как мне кажется, вы хотите: класс атрибутов CoClass .

[System.Runtime.InteropServices.CoClass(typeof(Test))]
public interface Dummy { }

Coclass предоставляет конкретные реализации одного или нескольких интерфейсов. В COM такие конкретные реализации могут быть написаны на любом языке программирования, который поддерживает разработку COM-компонентов, например, Delphi, C ++, Visual Basic и т. Д.

Посмотрите мой ответ на похожий вопрос о Microsoft Speech API , где вы можете «создать экземпляр» интерфейса SpVoice(но на самом деле вы его создаете SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Майкл Петротта
источник
2
Очень интересно - попробую позже. Связанные типы PIA не имеют CoClass. Может быть, это как-то связано с процессом связывания - я посмотрю в оригинальной PIA ...
Джон Скит
64
+1 за то, что был крут, написав принятый ответ, когда Эрик Липперт и Джон Скит также ответили;) Нет, действительно, +1 за упоминание CoClass.
OregonGhost
61

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

Если:

  • вы «новый» тип интерфейса, и
  • тип интерфейса имеет известный Coclass, и
  • Вы используете функцию «без пиа» для этого интерфейса

затем код генерируется как (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE))

Если:

  • вы «новый» тип интерфейса, и
  • тип интерфейса имеет известный Coclass, и
  • Вы НЕ используете функцию «без пиа» для этого интерфейса

тогда код генерируется так, как если бы вы сказали «новый COCLASSTYPE ()».

Джон, не стесняйся задавать мне или Сэму напрямую, если у тебя есть вопросы по этому поводу. К вашему сведению, Сэм - эксперт по этой функции.

Эрик Липперт
источник
36

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

Глядя на оригинальную 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 в C # 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-интерфейс, используя эту информацию, и посмотреть, смогу ли я заставить компилятор связать его ...

Джон Скит
источник
27

Просто добавлю немного подтверждения к ответу Майкла:

Следующий код компилируется и запускается:

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!

Расмус Фабер
источник
спасибо, я пытался, но мне не хватало атрибута ComImport , но когда я перехожу к исходному коду, я работал, используя F12, показывает только CoClass и Guid , почему это так?
Эхсан Саджад