Comment le compilateur C # détecte-t-il les types COM?

168

EDIT: J'ai écrit les résultats sous forme de blog .


Le compilateur C # traite les types COM de manière un peu magique. Par exemple, cette déclaration semble normale ...

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

... jusqu'à ce que vous vous rendiez compte que Applicationc'est une interface. Appel d'un constructeur sur une interface? Yoiks! Cela se traduit en fait par un appel à Type.GetTypeFromCLSID()et un autre à Activator.CreateInstance.

De plus, en C # 4, vous pouvez utiliser des arguments non-ref pour les refparamètres, et le compilateur ajoute simplement une variable locale à passer par référence, en ignorant les résultats:

// FileName parameter is *really* a ref parameter
app.ActiveDocument.SaveAs(FileName: "test.doc");

(Ouais, il manque un tas d'arguments. Les paramètres optionnels ne sont-ils pas sympas? :)

J'essaie d'enquêter sur le comportement du compilateur et je n'arrive pas à simuler la première partie. Je peux faire la deuxième partie sans problème:

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);
    }
}

J'aimerais pouvoir écrire:

Dummy dummy = new Dummy();

bien que. Évidemment, ça va aller au moment de l'exécution, mais ce n'est pas grave. J'expérimente juste.

Les autres attributs ajoutés par le compilateur pour les PIA COM liés ( CompilerGeneratedet TypeIdentifier) ne semblent pas faire l'affaire ... quelle est la sauce magique?

Jon Skeet
la source
11
Les paramètres optionnels ne sont-ils pas agréables? OMI, non, ils ne sont pas gentils. Microsoft tente de corriger la faille dans les interfaces COM d'Office en ajoutant un gonflement à C #.
Mehrdad Afshari
18
@Mehrdad: Les paramètres facultatifs sont utiles au-delà de COM, bien sûr. Vous devez faire attention aux valeurs par défaut, mais entre elles et les arguments nommés, il est beaucoup plus facile de créer un type immuable utilisable.
Jon Skeet
1
Vrai. Plus précisément, des paramètres nommés peuvent être pratiquement requis pour l'interopérabilité avec certains environnements dynamiques. Bien sûr, sans aucun doute, c'est une fonctionnalité utile, mais cela ne signifie pas qu'elle est gratuite. Cela coûte de la simplicité (un objectif de conception explicitement déclaré). Personnellement, je pense que C # est incroyable pour les fonctionnalités que l'équipe a laissées (sinon, cela aurait pu être un clone C ++). L'équipe C # est géniale, mais un environnement d'entreprise peut difficilement être sans politique. Je suppose que Anders lui - même pas très heureux à ce sujet comme il le dit dans son discours de PDC'08: « nous a fallu dix ans pour revenir là où nous étions. »
Mehrdad Afshari
7
Je conviens que l'équipe devra garder un œil attentif sur la complexité. Le truc dynamique ajoute beaucoup de complexité pour peu de valeur pour la plupart des développeurs, mais une valeur élevée pour certains développeurs.
Jon Skeet
1
J'ai vu des développeurs de framework commencer à discuter de ses utilisations dans de nombreux endroits. OMI, il est juste temps avant que nous trouvions une bonne utilisation pour dynamic... nous sommes trop habitués au typage statique / fort pour voir pourquoi cela importerait en dehors de COM.
chakrit le

Réponses:

145

Je ne suis en aucun cas un expert en la matière, mais je suis récemment tombé sur ce que je pense que vous voulez: la classe d'attributs CoClass .

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

Une coclasse fournit des implémentations concrètes d'une ou plusieurs interfaces. Dans COM, de telles implémentations concrètes peuvent être écrites dans n'importe quel langage de programmation prenant en charge le développement de composants COM, par exemple Delphi, C ++, Visual Basic, etc.

Voir ma réponse à une question similaire sur l'API Microsoft Speech , où vous pouvez «instancier» l'interface SpVoice(mais en réalité, vous instanciez SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Michael Petrotta
la source
2
Très intéressant - j'essaierai plus tard. Les types PIA liés n'ont cependant pas de CoClass. Peut-être que c'est quelque chose à voir avec le processus de liaison - je vais jeter un œil dans le PIA original ...
Jon Skeet
64
+1 pour avoir été génial en écrivant la réponse acceptée quand Eric Lippert et Jon Skeet ont également répondu;) Non, vraiment, +1 pour avoir mentionné CoClass.
OregonGhost
61

Entre vous et Michael, vous avez presque assemblé les morceaux. Je pense que c'est ainsi que cela fonctionne. (Je n'ai pas écrit le code, donc je le dis peut-être légèrement, mais je suis presque sûr que c'est ainsi que ça se passe.)

Si:

  • vous êtes "nouveau" dans un type d'interface, et
  • le type d'interface a une coclasse connue, et
  • vous utilisez la fonction "no pia" pour cette interface

puis le code est généré comme (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE))

Si:

  • vous êtes "nouveau" dans un type d'interface, et
  • le type d'interface a une coclasse connue, et
  • vous N'UTILISEZ PAS la fonction "no pia" pour cette interface

alors le code est généré comme si vous aviez dit "new COCLASSTYPE ()".

Jon, n'hésitez pas à me déranger ou à me déranger directement si vous avez des questions à ce sujet. FYI, Sam est l'expert de cette fonctionnalité.

Eric Lippert
la source
36

D'accord, c'est juste pour donner un peu plus de chair à la réponse de Michael (il est invité à l'ajouter s'il le souhaite, auquel cas je supprimerai celle-ci).

En regardant le PIA d'origine pour Word.Application, il existe trois types impliqués (en ignorant les événements):

[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
{
}

Il y a deux interfaces pour des raisons dont Eric Lippert parle dans une autre réponse . Et là, comme vous l'avez dit, se trouve le CoClass- à la fois en termes de classe elle-même et d'attribut sur l' Applicationinterface.

Maintenant, si nous utilisons la liaison PIA en C # 4, une partie de cela est intégrée dans le binaire résultant ... mais pas tout. Une application qui crée simplement une instance de Applicationse termine avec ces types:

[ComImport, TypeIdentifier, Guid("..."), CompilerGenerated]
public interface _Application

[ComImport, Guid("..."), CompilerGenerated, TypeIdentifier]
public interface Application : _Application

Non ApplicationClass- probablement parce que cela sera chargé dynamiquement à partir du type COM réel au moment de l'exécution.

Une autre chose intéressante est la différence de code entre la version liée et la version non liée. Si vous décompilez la ligne

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

dans la version référencée , il se termine par:

Application application = new ApplicationClass();

alors que dans la version liée , il se termine par

Application application = (Application) 
    Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("...")));

Il semble donc que le "vrai" PIA ait besoin de l' CoClassattribut, mais pas la version liée car il n'y a pas deCoClass référence que le compilateur puisse réellement référencer. Il doit le faire de manière dynamique.

Je pourrais essayer de simuler une interface COM en utilisant ces informations et voir si je peux demander au compilateur de la lier ...

Jon Skeet
la source
27

Juste pour ajouter un peu de confirmation à la réponse de Michael:

Le code suivant se compile et s'exécute:

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();
    }
}

Vous avez besoin à la fois du ComImportAttributeet du GuidAttributepour que cela fonctionne.

Notez également les informations lorsque vous passez la souris sur le new IFoo(): Intellisense récupère correctement les informations: Nice!

Rasmus Faber
la source
merci, j'essayais mais il me manquait l' attribut ComImport , mais quand je vais au code source, je travaillais en utilisant F12 ne montre que CoClass et Guid , pourquoi?
Ehsan Sajjad