Как компилятор С# обнаруживает типы 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!