首页 文章

以编程方式查找完全限定的.NET程序集名称(从简单名称,对于给定的.NET版本)?

提问于
浏览
3

我的目标是从纯C本机Windows API级程序(不是托管C或C / CLI)显示.NET Windows窗体消息框 .

也就是说,出于学习目的,我想在纯C中实现下面注释中显示的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;
}

计划是,在加载程序集后,继续 MessageBox 类并调用 Show . 但这可能是一种错误的做法 . 所以我同样很满意答案显示如何在没有找到程序集的完全限定名称的情况下执行此操作(当然,没有硬编码完全限定名称!) .

gacutil 实用程序显然能够找到完全限定的名称:

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> _

但是,如上所述,我不想硬编码任何东西:C代码中硬编码的.NET信息不应超过顶部注释中显示的C#源代码,加上支持的最小.NET版本 .

TIA,

2 回答

  • 1

    嗯,好的,部分回答我自己的问题(足够前进):

    我提供了一个构造的全名,其版本设置为支持的.NET的最低版本,而.NET "Fusion"(但这是否记录了?)找到了一个明显兼容的程序集,尽管指定的程序集不在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;
    

    与输出

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

    我认为UUID(这里是b77a5c561934e089)只是程序集的一般版本独立标识的一部分,但理想情况下我也会在代码中动态地获取它 .

    毕竟,它不需要在C#源代码中指定 - 它可以完成吗?

    干杯,

  • 3

    Hmya,这不是通常的做法 . 在初始化CLR之后,接下来加载由托管编译器生成的不在GAC中的程序集 . 通常是带有Main()方法的EXE,但当然你在这里有备用选项 .

    如果您想要追求这一点,那么您确实需要生成一个完全限定的名称,Fusion要求它知道从GAC中选择哪个程序集 . 相当于gacutil / l的是Fusion API,您可以使用CreateAssemblyEnum()来迭代它 . 获取IAssemblyEnum,其GetNextAssembly()方法为您提供IAssemblyName . 必须有一些硬编码选择算法来确定您更喜欢哪个版本 .

    使用托管编译器创建的程序集时,这不是问题 . 项目中的程序集引用选择所需的版本 . 我不得不建议你这样做 .

相关问题