2010-10-23 43 views
3

Mon objectif est d'afficher une boîte de message Windows Forms .NET à partir d'un programme de niveau API Windows C++ pur (non géré C++ ou C++/CLI).Trouvez un nom complet de l'assembly .NET par programme (à partir du nom simple, pour la version .NET donnée)?

C'est, à des fins d'apprentissage, je veux mettre en œuvre le code C# indiqué dans le commentaire ci-dessous, dans le plus pur C++:

/* 
    // C# code that this C++ program should implement: 
    using System.Windows.Forms; 

    namespace hello 
    { 
     class Startup 
     { 
      static void Main(string[] args) 
      { 
       MessageBox.Show(
        "Hello, world!", 
        ".NET app:", 
        MessageBoxButtons.OK, 
        MessageBoxIcon.Information 
        ); 
      } 
     } 
    } 
*/ 

#include <stdexcept> 
#include <string> 
#include <iostream> 
#include <stdlib.h>   // EXIT_SUCCESS, EXIT_FAILURE 

#undef UNICODE 
#define UNICODE 
#include <windows.h> 

#include <Mscoree.h> 
#include <comdef.h> 
_COM_SMARTPTR_TYPEDEF(ICorRuntimeHost, IID_ICorRuntimeHost);  // ICorRuntimeHostPtr 

// #import is an MS extension, generates a header file. Will be replaced with #include. 
#import "C:\\WINDOWS\\Microsoft.NET\\Framework\\v1.1.4322\\mscorlib.tlb" \ 
    raw_interfaces_only rename("ReportEvent", "reportEvent") 
typedef mscorlib::_AppDomainPtr  AppDomainPtr; 
typedef mscorlib::_ObjectHandlePtr ObjectHandlePtr; 
typedef mscorlib::_AssemblyPtr  AssemblyPtr; 

bool throwX(std::string const& s) { throw std::runtime_error(s); } 

template< class Predicate > 
struct Is: Predicate 
{}; 

template< class Type, class Predicate > 
bool operator>>(Type const& v, Is<Predicate> const& check) 
{ 
    return check(v); 
} 

struct HrSuccess 
{ 
    bool operator()(HRESULT hr) const 
    { 
     ::SetLastError(hr); 
     return SUCCEEDED(hr); 
    } 
}; 

void cppMain() 
{ 
    ICorRuntimeHostPtr pCorRuntimeHost; 
    CorBindToRuntimeEx(
     L"v1.1.4322",   // LPWSTR pwszVersion, // RELEVANT .NET VERSION. 
     L"wks",     // LPWSTR pwszBuildFlavor, // "wks" or "svr" 
     0,      // DWORD flags, 
     CLSID_CorRuntimeHost, // REFCLSID rclsid, 
     IID_ICorRuntimeHost, // REFIID riid, 
     reinterpret_cast<void**>(&pCorRuntimeHost) 
     ) 
     >> Is<HrSuccess>() 
     || throwX("CorBindToRuntimeEx failed"); 

    pCorRuntimeHost->Start() // Without this GetDefaultDomain fails. 
     >> Is<HrSuccess>() 
     || throwX("CorRuntimeHost::Start failed"); 

    IUnknownPtr  pAppDomainIUnknown; 
    pCorRuntimeHost->GetDefaultDomain(&pAppDomainIUnknown) 
     >> Is<HrSuccess>() 
     || throwX("CorRuntimeHost::GetDefaultDomain failed"); 

    AppDomainPtr pAppDomain = pAppDomainIUnknown; 
    (pAppDomain != 0) 
     || throwX("Obtaining _AppDomain interface failed"); 

    // This fails because Load requires a fully qualified assembly name. 
    // I want to load the assembly given only name below + relevant .NET version. 
    AssemblyPtr  pFormsAssembly; 
    pAppDomain->Load_2(_bstr_t("System.Windows.Forms"), &pFormsAssembly) 
     >> Is<HrSuccess>() 
     || throwX("Loading System.Windows.Forms assembly failed");  

    // ... more code here, not yet written. 
} 

int main() 
{ 
    try 
    { 
     cppMain(); 
     return EXIT_SUCCESS; 
    } 
    catch(std::exception const& x) 
    { 
     std::cerr << "!" << x.what() << std::endl; 
    } 
    return EXIT_FAILURE; 
} 

Le plan est, après avoir chargé l'assemblée, procéder à MessageBox classe et invoquez Show . Mais cela peut être une fausse façon de le faire. Donc, je suis également heureux avec une réponse montrant comment faire cela sans trouver le nom qualifié complet de l'assemblée (bien sûr, sans coder en dur ce nom qualifié complet!).

L'utilitaire gacutil est évidemment en mesure de trouver des noms qualifiés:

 
C:\test> gacutil /l System.Windows.Forms 
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.1 
Copyright (c) Microsoft Corporation. All rights reserved. 

The Global Assembly Cache contains the following assemblies: 
    System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL 
    System.Windows.Forms, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 
    System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 
    System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL 

Number of items = 4 

C:\test> _ 

Cependant, comme mentionné, je ne veux pas hardcode quoi que ce soit: l'info .NET hardcoded dans le code C++ ne devrait pas être plus dans le code source C# affiché dans le commentaire en haut, plus la version minimale .NET pris en charge.

TIA.,

+0

C'est une utilisation très intéressante de 'operator >>'. –

+0

définitivement vérifier cette [proposition] (http://area51.stackexchange.com/proposals/11464/code-review?referrer = aWNm_PdciyFqjFW8CUacGw2 "revue de code"). Il est presque là, a juste besoin d'un peu plus de soutien. – greatwolf

Répondre

1

Hmya, ce n'est pas la façon dont il est fait normalement. Après l'initialisation du CLR, vous devez charger un assembly et non dans le GAC, produit par un compilateur géré. Typiquement un EXE avec une méthode Main() mais vous avez d'autres options ici bien sûr.

Si vous voulez poursuivre cela, alors vous devez vraiment produire un nom complet, Fusion l'exige pour savoir quel assemblage choisir parmi le GAC. L'équivalent de gacutil/l est l'API Fusion, vous utiliseriez CreateAssemblyEnum() pour l'itérer. Obtient un IAssemblyEnum, sa méthode GetNextAssembly() vous obtient un IAssemblyName. Il doit y avoir un algorithme de sélection codé en dur pour décider exactement quelle version vous préférez.

Ce n'est pas un problème lorsque vous utilisez un assembly créé par un compilateur géré. Les références d'assemblage dans le projet sélectionnent la version souhaitée. Je devrais te recommander comme ça.

+0

Merci, jusqu'à présent. Essentiellement (à moins que d'autres moyens d'atteindre l'objectif de niveau supérieur d'atteindre la classe 'MessageBox' soient trouvés), je veux probablement faire ce que fait ce compilateur pour la sélection d'assemblages. Si comme vous le dites, il suffit de sélectionner des références d'assemblage de projet, puis je souhaite que la sélection effectuée produise ces références d'assemblage. Bravo, –

+0

Le compilateur ne le fait pas non plus. Le programmeur fait quand il crée son projet. Ou peut-être plus précisément, le modèle de projet fait. Ce truc est construit dans l'IDE. Il n'y a pas de mécanisme de «sélection» comme vous l'approchez maintenant. –

+0

Merci encore, jusqu'à présent. Mais vraiment, nous les vieux dinosaures ne les utilisons pas. :-) Clavier! Ou, pour les vrais dinos, modifier les bits de la RAM en dirigeant les rayons cosmiques par des incantations magiques! Cheers, –

3

Hm, OK, réponse partielle à ma propre question (assez pour aller de l'avant):

I fournir une version minimale de .NET pris en charge le nom complet construit avec la version définie comme, et le .NET " Fusion »(? mais est-ce documenté) trouve un ensemble apparemment compatible, bien que le spécifié assemblage est pas dans le GAC:

AssemblyPtr  pFormsAssembly; 
pAppDomain->Load_2(_bstr_t("System.Windows.Forms, Version=1.1.4322.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"), &pFormsAssembly) 
    >> Is<HrSuccess>() 
    || throwX("Loading System.Windows.Forms assembly failed"); 
std::cout << "Loaded the assembly." << std::endl; 

_bstr_t  displayName; 
pFormsAssembly->get_ToString(displayName.GetAddress()) 
    >> Is<HrSuccess>() 
    || throwX("_Assembly::get_ToString failed"); 
std::cout << "\"" << displayName.operator char const*() << "\"" << std::endl; 

avec sortie

 
Loaded the assembly. 
"System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 

Je pense que l'UUID (ici b77a5c561934e089) fait juste partie de l'identité générale de l'assemblage, mais idéalement, je le reprendra aussi dynamiquement dans le code.

Après tout, il n'a pas besoin d'être spécifié dans le code source C# - peut-il être fait? Cheers,

+1

@downvoter: s'il vous plaît indiquer la raison pour downvote afin que les autres peuvent voir pourquoi c'est idiot. –