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 全局程序集缓存实用程序。版本 4.0.30319.1
版权所有 (c) 微软公司。版权所有。

全局程序集缓存包含以下程序集:
  System.Windows.Forms,版本=2.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089,处理器架构=MSIL
  System.Windows.Forms,版本=1.0.3300.0,文化=中性,PublicKeyToken=b77a5c561934e089
  System.Windows.Forms,版本=1.0.5000.0,文化=中性,PublicKeyToken=b77a5c561934e089
  System.Windows.Forms,版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089,处理器架构=MSIL

项目数 = 4

C:\测试> _

但是,如前所述,我不想硬编码任何内容:C++ 代码中硬编码的 .NET 信息不应超过顶部注释中显示的 C# 源代码中的内容,以及支持的最低 .NET 版本。

TIA.,

4

2 回答 2

3

嗯,好的,我自己的问题的部分答案(足以继续前进):

我提供了一个构建的全名,其版本设置为支持的 .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;

带输出

加载程序集。
“System.Windows.Forms,版本=1.0.5000.0,文化=中性,PublicKeyToken=b77a5c561934e089”

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

毕竟不需要在C#源代码中指定——可以吗?

干杯,

于 2010-10-23T05:34:09.403 回答
1

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

如果你想追求这个,那么你真的必须产生一个完全限定的名称,Fusion 要求它知道从 GAC 中选择哪个程序集。gacutil /l 的等价物是 Fusion API,您可以使用 CreateAssemblyEnum() 对其进行迭代。给你一个 IAssemblyEnum,它的 GetNextAssembly() 方法给你一个 IAssemblyName。必须有一些硬编码的选择算法来确定您更喜欢哪个版本。

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

于 2010-10-23T03:45:26.007 回答