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
Copyright (c) Microsoft Corporation. 全著作権所有。

グローバル アセンブリ キャッシュには、次のアセンブリが含まれています。
  System.Windows.Forms、バージョン = 2.0.0.0、カルチャ = ニュートラル、PublicKeyToken = b77a5c561934e089、processorArchitecture = 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、processorArchitecture = MSIL

アイテム数 = 4

C:\テスト> _

ただし、前述のように、何もハードコードしたくありません。C++ コードにハードコードされた .NET 情報は、上部のコメントに示されている C# ソース コードと、サポートされている最小の .NET バージョンを超えてはなりません。

ティア、

4

2 に答える 2

3

うーん、OK、私自身の質問に対する部分的な回答 (先に進むには十分です):

サポートされている .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) は、アセンブリの一般的なバージョンに依存しない ID の一部にすぎないと思いますが、コード内で動的に取得することも理想的です。

結局のところ、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 に答える