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

发布于 2024-09-28 09:48:56 字数 4524 浏览 0 评论 0原文

我的目标是从纯 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.,

My goal is to display a .NET Windows forms message box from a pure C++ native Windows API-level program (not managed C++ or C++/CLI).

That is, for learning purposes I want to implementing the C# code shown in the comment below, in pure 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;
}

The plan is, having loaded the assembly, proceed to MessageBox class and invoke Show. But this just may be a mistaken way of doing it. So I'm equally happy with an answer showing how to do that without finding the fully qualified name of the assembly (of course, without hardcoding that fully qualified name!).

The gacutil utility is evidently able to find fully qualified names:

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

However, as mentioned I don't want to hardcode anything: the .NET info hardcoded in the C++ code should not be more than in the C# source code shown in the comment at the top, plus the minimum .NET version supported.

TIA.,

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

拔了角的鹿 2024-10-05 09:48:56

嗯,好吧,部分回答我自己的问题(足以继续):

我提供了一个构造的全名,并将版本设置为支持的 .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# 源代码中指定——可以做到吗?

干杯,

Hm, OK, partial answer to my own question (enough to go forward):

I supply a constructed full-name with the version set as the minimum version of .NET supported, and the .NET "Fusion" (but is this documented?) finds an apparently compatible assembly, although the specified assembly is not in the 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;

with output

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

I think the UUID (here b77a5c561934e089) is just part of the general version-independent identity of the assembly, but ideally I'd pick up also that dynamically in the code.

After all, it doesn't need to be specified in the C# source code -- can it be done?

Cheers,

淡看悲欢离合 2024-10-05 09:48:56

嗯,这不是通常的做法。初始化 CLR 后,您接下来将加载一个不在 GAC 中、由托管编译器生成的程序集。通常是带有 Main() 方法的 EXE,但当然这里还有其他选择。

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

当您使用托管编译器创建的程序集时,这不是问题。项目中的程序集引用选择所需的版本。我不得不建议你朝那个方向走。

Hmya, this is not the way it is normally done. After initializing the CLR, you'd next load an assembly that is not in the GAC, produced by a managed compiler. Typically an EXE with a Main() method but you've got alternate options here of course.

If you want to pursue this then you really do have to produce a fully qualified name, Fusion requires it to know which assembly to pick from the GAC. The equivalent of gacutil /l is the Fusion API, you'd use CreateAssemblyEnum() to iterate it. Gets you an IAssemblyEnum, its GetNextAssembly() method gets you an IAssemblyName. There has to be some hard-coded selection algorithm to decide exactly which version you'd prefer.

This is not a problem when you use an assembly created by a managed compiler. The assembly references in the project select the desired version. I'd have to recommend you head that way.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文