web-dev-qa-db-fra.com

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

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


Le compilateur C # traite les types COM de façon un peu magique. Par exemple, cette déclaration semble normale ...

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

... jusqu'à ce que vous réalisiez que Application est une interface. Appeler 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 paramètres ref, 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 y a un tas d'arguments manquants. Les paramètres optionnels ne sont-ils pas agréables? :)

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 bang au moment de l'exécution, mais ça va. J'expérimente juste.

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

166
Jon Skeet

Je ne suis en aucun cas un expert dans ce domaine, mais je suis récemment tombé sur ce que je pense que vous voulez: la classe d'attribut 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, ces implémentations concrètes peuvent être écrites dans n'importe quel langage de programmation qui prend 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 vraiment, vous instanciez SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
143
Michael Petrotta

Entre vous et Michael, vous avez presque rassemblé les pièces. Je pense que c'est ainsi que cela fonctionne. (Je n'ai pas écrit le code, donc je pourrais peut-être le mal exprimer, mais je suis presque sûr que c'est comme ça que ça se passe.)

Si:

  • vous êtes "nouveau" avec 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é en tant que (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE))

Si:

  • vous êtes "nouveau" avec 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 à m'embêter ou à Sam directement si vous avez des questions à ce sujet. Pour info, Sam est l'expert de cette fonctionnalité.

60
Eric Lippert

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

En regardant le PIA d'origine pour Word.Application, il y a 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 ne autre réponse . Et comme vous l'avez dit, il y a CoClass - à la fois en termes de classe elle-même et d'attribut sur l'interface Application.

Maintenant, si nous utilisons la liaison PIA en C # 4, une partie de ceci est incorporée dans le binaire résultant ... mais pas tout. Une application qui crée simplement une instance de Application se retrouve avec ces types:

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

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

Non ApplicationClass - probablement parce qu'il 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 finit par:

Application application = new ApplicationClass();

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

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

Il semble donc que le "vrai" PIA ait besoin de l'attribut CoClass, mais la version liée ne le fait pas car il n'y a pas a CoClass que le compilateur peut réellement référencer. Il doit le faire dynamiquement.

Je pourrais essayer de simuler une interface COM en utilisant ces informations et voir si je peux obtenir le compilateur pour le lier ...

35
Jon Skeet

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 ComImportAttribute et du GuidAttribute pour que cela fonctionne.

Notez également les informations lorsque vous passez la souris sur la new IFoo(): Intellisense reprend correctement les informations: Nice!

27
Rasmus Faber