Manager ができたので、これを使ってダイアログボックスに一覧表示するのだが、
プラグインには ISaver2 を実装していないものも存在する。

ISaver2 を実装している場合は Name プロパティを使えば良いが、
ISaver しか実装していない場合は、クラスの簡易名を使うことにしよう。
簡易名とは、名前空間を含まないクラスの名前部分のみを示す。

例えば、HelloSaver は ISaver2 を実装していないため、
クラス名である「HelloSaver」を名前として表示することになる。

設定ダイアログは Application::ConfigureProc で処理されているので、
WM_INITDIALOG メッセージを拾ってコンボボックスに設定する。

現在の Application はアンマネージクラスなのだが、
Manager クラスはマネージクラスだ。
これを ConfigureProc メソッド内で使用すると、
ConfigureProc メソッド全体がマネージコードになってしまう。

そこで、コンボボックスにロードする部分を、
LoadPlugins という名前のヘルパメソッドに分離する。

========== Application.hpp ==========

……
class Application {
……
private:
    void LoadPlugins(HWND combobox);
……
};
……

========== Application.hpp ==========

ソースファイルでは、プラグマ managed と unmanaged を使うことで、
部分的にマネージコードを使うことが可能だ。

========== Application.cpp ==========

#include "config.hpp"
#include "Application.hpp"
#include "Dispatcher.hpp"
#include "Manager.hpp"
#include "resid.hpp"

#using 
#using 
#using "Loafer.ScreenSaver.dll"

#pragma unmanaged

namespace Loafer {
namespace ScreenSaver {
namespace Host {

……

BOOL Application::ConfigureProc(
        HWND hdlg, UINT message,
        WPARAM wParam, LPARAM lParam) {

    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);

    switch (message)  {

    case WM_INITDIALOG:

        try {

            // プラグイン一覧をロード
            LoadPlugins(GetDlgItem(hdlg, IDC_PLUGINS));

        } catch (...) { // 例外は握りつぶす

            // 上と同じだが、ダイアログの戻り値はプロセスの終了コードになるので
            // 異常を示す 0 以外を返しておく
            EndDialog(hdlg, 1);

        }
        break;

    case WM_COMMAND:

……

}

// ここから C++/CLI
#pragma managed

using namespace System;
using namespace Loafer::ScreenSaver;
using namespace System::Runtime::InteropServices;

void Application::LoadPlugins(HWND combobox) {

    Manager ^manager = gcnew Manager();

    for each (Type ^type in manager) {

        String ^name;

        // ISaver2 を実装してるなら名前を
        // 実装してなければ簡易クラス名を使う
        if (ISaver2::typeid->IsAssignableFrom(type)) {
            Object ^instance = Activator::CreateInstance(type);
            ISaver2 ^saver = safe_cast(instance);
            name = saver->Name;
        } else {
            name = type->Name;
        }

        // System::String から char * に
        IntPtr ptr = Marshal::StringToCoTaskMemAnsi(name);
        try {
            SendMessage(combobox, CB_ADDSTRING, 0, (LPARAM)ptr.ToPointer());
        } finally {
            Marshal::FreeCoTaskMem(ptr);
        }

    }

}

}}} // Loafer::ScreenSaver::Host

========== end of Application.cpp ==========

LoadPlugins() で、コンボボックスへの追加を行っている。
まず、Manager を作成し、for each 構文で ISaver 実装型を列挙。
Manager クラスは、型をインデックス順に列挙するので、
for each を使っても特に問題はない。

Type の IsAssignableFrom() を使って ISaver2 を実装しているか調べ、
実装されていれば、Activator::CreateInstance() でインスタンスを作成し、
ISaver にキャストした上で Name プロパティを取得する。
実装されていなければ、Type の Name プロパティで簡易名を取得する。

コンボボックスに項目を追加するためには、
CB_ADDSTRING メッセージを送って、一覧の末尾に文字列を追加すれば良いが、
このメッセージは、C 言語の文字列(char * または wchar_t *)を受け取る。

System::String から char * に変換するためには、
Marshal::StringToCoTaskMemAnsi() を使えば良い。

このメソッドは、COM の標準メモリアロケータを使ってメモリを確保し、
System::String を ACP 文字列に変換してコピーしたものを返す。
(システムの ANSI コードページ。日本語環境では Shift_JIS)
文字列を使い終わったら、忘れずに Marshal::FreeCoTaskMem() で解放すること。

.Net のメソッドなので、戻り値は IntPtr だが、
ToPointer() から char * にキャストすることができる。
SendMessage を使う場合、キャスト式が複雑になるので、
少々手抜きで旧スタイルのキャストで済ますことにした。

これで、一覧が得られるようになった。