Outlook 2010 の C++ アドインをビルドする

概要: この記事では、Microsoft Outlook 2010 ユーザー インターフェイスをカスタマイズするアンマネージ アドインを C++ で作成する方法について説明します。ここでは、Microsoft Office Fluent ユーザー インターフェイスのリボン コンポーネントをカスタマイズする方法と、Outlook 2010 にカスタム フォーム領域を追加する方法を理解します。

適用対象: Office 2010 | Outlook 2010

この記事の内容
Outlook のアドインのビルドの概要
C++ アドインの作成
リボンとリボン ボタンの追加
フォーム領域
Bing 検索機能の追加
まとめ
その他の技術情報

クリックしてコードを取得Outlook 2010 Sample: Building a C++ Add-In (英語)

目次

  • Outlook のアドインのビルドの概要

  • C++ アドインの作成

  • リボンとリボン ボタンの追加

  • フォーム領域

  • Bing 検索機能の追加

  • まとめ

  • その他の技術情報

Outlook のアドインのビルドの概要

Microsoft Outlook には、Outlook でのユーザーの日常の操作性を大きく向上できる、アドインをビルドするための多機能なモデルがあります。Outlook アドインをビルドするには、Microsoft Visual Studio 2010 の Microsoft Office 開発ツール (または、Visual Studio の以前のバージョンの場合は Microsoft Visual Studio Tools for Office) を使用します。これは、Outlook オブジェクト モデルと Microsoft Office オブジェクト モデルの上に構築されたマネージ ライブラリのセットです。または、アンマネージ コード (つまり C++) を使用することもできます。

注意

Microsoft Visual C++ は、アンマネージ C++ でのプロジェクト開発と、共通言語ランタイム (CLR) を使用したマネージ C++ でのプロジェクト開発をサポートしています。この記事では、以後、C++ は言語のアンマネージ バージョンを表し、マネージ C++ は CLR の上で使用される Visual C++ を表します。

以下のリストは、アドインをビルドするのに C# や Visual Basic ではなく C++ を使用するいくつかの理由を示しています (Visual Studio の Office 開発ツールには 2 つの言語オプションがあります)。

  • パフォーマンス

    アンマネージ アドインは、通常、マネージ言語で作成されたアドインよりパフォーマンスに優れています。使用されるコードとアルゴリズムによっては、コードは高速に実行されます。さらに、アンマネージ アドインは、動作するのに CLR を読み込む必要がないので、読み込みが高速に行われます。アドインと Outlook の間で多くのデータがやり取りされる場合、アンマネージ バージョンでは、呼び出しごとにデータがネイティブとマネージの境界を超える (マーシャリングといいます) 必要がないので、やはりパフォーマンスに優れています。

  • 配布パッケージ サイズと少ない依存関係

    アンマネージ アドインの場合、ユーザーは、CLR のような必須コンポーネントをインストールする必要がありません。

    注意

    アドインが、Outlook の 32 ビット バージョンと 64 ビット バージョンの両方をサポートする場合は、バージョンごとに個別のビルドを作成する必要があります。

  • メッセージング API (MAPI) のサポート

    アドインで MAPI を直接使用する必要がある場合は、C++ を使用する必要があります。マネージ コードからの MAPI の直接呼び出しはサポートされていません。

  • バージョン管理

    .NET Framework ではアセンブリの複数のバージョンを使用できますが、Visual Studio Tools for Office ランタイムでは、Visual Studio Tools for Office または Visual Studio 2010 の Office 開発ツールのバージョンごとに 1 つのアセンブリに制限されています。アンマネージ コードを使用すると、より柔軟にバージョンを管理できます。

注意

この記事は、C++ でアプリケーションを開発した経験を持つ中級および上級の開発者が対象です。この記事には、Visual Studio の一般的な手順が必ずしも詳細に記載されていません。

この記事では、カスタム ユーザー インターフェイスに機能を追加するための例としてのみ Bing API を使用します。Outlook インスペクター リボン内のカスタム ボタンを、Outlook オブジェクト モデルなどの API に接続できます。アプリケーション内で Bing API を使用する場合は、現在サポートされている Bing のバージョンにコードが準拠することを確認してください。詳細については、「Bing Developer Center (英語)」を参照してください。

C++ アドインの作成

C++ で Outlook アドインをビルドする場合は、最初に、以下の手順で C++ ライブラリ アプリケーションを作成します。

注意

この記事に付随するコード サンプルをダウンロードするには、「Outlook 2010 Sample: Building a C++ Add-In (英語)」を参照してください。

手順 1: 新しい ATL プロジェクトを作成する

  1. Microsoft Visual Studio 2010 を開きます。

  2. [ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。

  3. [新しいプロジェクト] ダイアログ ボックスで、[Visual C++] ノードから [ATL プロジェクト] テンプレートを選択します。

  4. このプロジェクトの名前に「NativeAddIn」と入力します。ATL を使用することで、COM に基づく、Outlook アドイン モデルを使用するための適切なインフラストラクチャが実現します。

  5. [OK] をクリックして、ATL プロジェクト ウィザードを表示します。

  6. [完了] をクリックして、このプロジェクトの既定の設定を使用します。

手順 2: 新しいプロジェクトを追加する

  1. ソリューション エクスプローラーでプロジェクト ノードを右クリックし、[追加] をポイントし、[クラス] をクリックします。

  2. [クラスの追加] ダイアログ ボックスで、リストから [ATL シンプル オブジェクト] テンプレートを選択し、[追加] をクリックします。

  3. ATL シンプル オブジェクト ウィザードで、[短い名前] ボックスに「Connect」と入力します。これが、アドインが実装するクラスの名前になります。アドイン クラスをビルドするときには、"Connect" を使用するのが一般的です。

  4. [ProgID] ボックスに「NativeAddin.Connect」と入力し、すべての追加情報がウィザードによって入力されるようにします。

  5. [完了] をクリックします。

手順 3: インターフェイスを実装する

次に、ATL ウィザードを使用して、アドインに必要なインターフェイスを実装します。

  1. クラスを作成したところで、[クラス ビュー] タブに移動します (タブがまだ表示されていない場合は、[表示] メニューの [クラス ビュー] をクリックするか、Ctrl + Shift + C キーを押します)。

  2. [クラス ビュー] ウィンドウで、[NativeAddIn] ノードを展開します。

  3. 作成したクラスを表す [CConnect] ノードを右クリックし、[追加] をポイントし、[インターフェイスの実装] をクリックします。

    注意

    Visual Studio をいったん閉じて開かないと、[CConnect] ノードが表示されない場合があります。これは、Visual Studio 2010 の Beta 2 リリースにおける既知の問題です。

  4. [インターフェイス実装ウィザード] ダイアログ ボックスの [使用できるタイプ ライブラリ] ボックスで、[Microsoft Add-In Designer <1.0>] を選択します。

  5. [インターフェイス] ボックスの一覧で [_IDTExtensbility2] を選択し、[>] ボタンをクリックして、インターフェイスを [インターフェイスの実装] リストに追加します。

  6. [完了] をクリックします。

_IDTExtensibility2 は、すべての Office アドインが実装する必要があるインターフェイスです。このインターフェイスは、アドインとホスト、この例では Outlook との間のやり取りを定義します。アドインが最初に読み込まれるときに、主要なやり取りが行われます。つまり、Outlook は OnConnection メソッドを呼び出します。このメソッドで、Outlook オブジェクト モデルへのアクセスを取得し、そのインターフェイス ポインターへの参照を格納し、カスタマイズを追加します。

手順 4: Outlook へのアドインの読み込みを設定する

  1. ファイル Connect.h を開きます。

  2. E_NOTIMPL ではなく S_OK を返すように、すべてのメソッドを変更します。Outlook はこれらのすべてのメソッドを呼び出すので、成功の結果を返すことが重要です。

  3. OnConnection メソッドにコードを追加して、Outlook がアドインを読み込んだかどうかを確認できるようにします。以下のコードに示すように、MessageBoxW への呼び出しを追加します。

    STDMETHOD(OnConnection)(LPDISPATCH Application, 
        ext_ConnectMode ConnectMode, 
        LPDISPATCH AddInInst, 
        SAFEARRAY * * custom)
    {
        MessageBoxW(NULL,L"OnConnection",L"Native Addin",MB_OK);
        return S_OK;
    }
    

手始めとしては、これで十分です。ただし、Outlook が実際にアドインを読み込むためには、アドインの有無と、それを読み込む方法を理解するために、アドインに関する多少のメタデータ情報が必要です。この情報は、Windows レジストリに設定する必要があります。ATL プロジェクトにはレジストリ スクリプトが既に組み込まれているので (一般的な COM メタデータをレジストリに設定するため)、そのスクリプトに追加することで、Outlook 固有のメタデータもレジストリに追加できます。

手順 5: アドインの Outlook 固有のメタデータを Windows レジストリに挿入する

  1. ソリューション エクスプローラーを開きます。

  2. [リソース ファイル] フォルダーを展開し、Connect.rgs ファイルを開きます。

  3. 以下のレジストリ スクリプトをファイルに追加します。

    HKCU
    {
        NoRemove Software
        {
            NoRemove Microsoft
            {
                NoRemove Office
                {
                    NoRemove Outlook
                    {
                        NoRemove Addins
                        {
                            NativeAddin.Connect
                            {
                                val Description = s 'Sample Addin'
                                val FriendlyName = s 'Sample Addin'
                                val LoadBehavior = d 3
                            }
                        }
                    }
                }
            }
        }
    }
    

このレジストリ エントリは、Outlook に対してこのアドインを読み込む必要があることを通知するものであり、LoadBehavior キーは自動的に読み込むことを指定します。この時点で、プロジェクトをビルドすると、正しいレジストリ キーがレジストリに設定され、アドインをほぼ読み込める状態になります。

手順 6: アドインの適切なビルドを決定する

次に、Outlook の 32 ビット バージョンと 64 ビット バージョンのどちらを実行しているのかを判断する必要があります。Office インストールは、32 ビット Windows を実行している場合は <ドライブ>:\Program Files\Microsoft Office ディレクトリ、または、64 ビット Windows で 32 ビット Outlook を実行している場合は <ドライブ>:\Program Files (x86)\Microsoft Office ディレクトリにあります。32 ビット バージョンの Outlook を実行している場合は、これで終了です。アドインをビルドし、Outlook を開きます。

64 ビット バージョンの Outlook を実行している場合は、プロジェクトを変更し、64 ビット コンパイラを使用してアドインをビルドする必要があります。64 ビット バージョンの Outlook は、32 ビットのアドインを読み込みません。

64 ビット Outlook 用のアドインをビルドするには、以下の手順に従います。

  1. ソリューション エクスプローラーでソリューション ノードを右クリックし、[構成マネージャー] をクリックして、[構成マネージャー] を開きます。

  2. [構成マネージャー] で、[アクティブ ソリューション プラットフォーム] ボックスから [新規] を選択して、NativeAddin プロジェクトの新しいプラットフォーム設定を作成します。

  3. [新しいソリューション プラットフォーム] ダイアログ ボックスで、新しいプラットフォームとして [x64] を選択し、[OK] をクリックします。

  4. [構成マネージャー] ダイアログ ボックスで、[閉じる] をクリックします。

  5. Outlook を開く前に、ソリューションを再びビルドします。

Outlook を開くと、OnConnection メソッド内の以下のコード行によって生成されるメッセージ ボックスが表示されます。

MessageBoxW(NULL, L"OnConnection", L"Native_Addin", MB_OK);
    return S_OK;

図 1 は、このメッセージ ボックスを示しています。

図 1. 起動時にメッセージ ボックスが表示される NativeAddIn

起動時にメッセージ ボックスを表示する NativeAddIn

デバッグを簡単にするためには、プロジェクトのデバッグ時に Outlook を読み込むようにプロジェクト プロパティを変更します。

手順 7 (オプション): デバッグを簡単にするために Outlook を読み込むようにプロジェクト プロパティを変更する

  1. ソリューション エクスプローラーでプロジェクトを右クリックし、[プロパティ] をクリックします。

  2. 左側のウィンドウのツリー表示で、[構成プロパティ] を展開し、[デバッグ] ノードを選択します。

  3. 右側のウィンドウの [コマンド] プロパティについて、プロパティの右をクリックすると表示されるボックスで、[参照] をクリックします。

  4. コンピューター上の Outlook.exe ファイルを参照します (オペレーティング システムと、Outlook の 32 ビット バージョンと 64 ビット バージョンのどちらを実行しているかによってパスは異なる場合があります)。

操作を続ける前に、CConnect 実装における OnConnection メソッド内の MessageBoxW 呼び出しは削除 (またはコメント アウト) したほうがよいでしょう。ビルドしてデバッグするときに、実行のたびにダイアログ ボックスを閉じる必要があるのは煩わしくなります。呼び出しのコメント アウトは、後で読み込みやデバッグで問題が生じても、コメントを削除することで、アドインが読み込まれている (または読み込まれていない) ことを確認できるので、この方法をお勧めします。

リボンとリボン ボタンの追加

アドインをビルドし、読み込みの準備が整ったところで、いくつかの機能の追加が求められる場合があります。よくあるカスタマイズとして、Outlook リボンへの新しいタブの追加、および新しいタブへのタブ グループとボタンの追加があります。

これを実現するには、IRibbonExtensibility という別のインターフェイスを実装する必要があります。IRibbonExtensibility は、GetCustomUI という 1 つのメソッドのみを持つ非常に単純なインターフェイスです。GetCustomUI は、出力パラメーターとして BSTR を使用します。これには、Microsoft Office Fluent ユーザー インターフェイス スキーマを使用して書式設定された XML が格納されます。この XML を使用して、ホスト (この例では Outlook) 内にリボン要素が追加されます。

IRibbonExtensibility は、前に追加したタイプ ライブラリとは別のタイプ ライブラリにあるため、目的のタイプ ライブラリをプロジェクトに指定する必要があります。実際、この時点で StdAfx.h ヘッダー ファイルを整理して、異なるプラットフォームやコンピューターに対するプロジェクトの堅牢性を多少高めることをお勧めします。

StdAfx.h ファイル (図 2 を参照) を開くと、ATL インターフェイス実装ウィザードによって、#import ステートメントに絶対パスが設定されています (これにより、プロジェクトのコンパイル時にタイプ ライブラリが C++/COM ヘッダー ファイルに変換されます)。使用しているコンピューターではこれで問題ありませんが、別の開発者とプロジェクトを共有する場合は、パスが異なる可能性があります。また、32 ビット システムと 64 ビット システムでもパスが異なる可能性があります。

図 2. ウィザードによる #import ステートメントが含まれる StdAfx.h ファイル

ウィザードの #import ステートメントを含む StdAfx.h ファイル

パスではなく、タイプ ライブラリの LIBID (ライブラリ識別子、または GUID) を参照することによって、#import ステートメントの堅牢性を高めることができます。Office タイプ ライブラリ (IRibbonExtensibility インターフェイスを含むタイプ ライブラリ) 用の、2 番目の import ステートメントも含める必要があります。これによって、#import ステートメントの堅牢性がさらに高まり、rename_namespace を追加することで、タイプ ライブラリのインポート時に名前の競合が発生しても問題に対処できます。

以下の手順では、リボンおよびリボン ボタンを追加する方法について説明します。

手順 1: StdAfx.h ファイルを整理する

  1. StdAfx.h ファイルを開きます。

  2. #import ステートメントを削除し、以下のコードに置き換えます。

    #import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4"\
    auto_rename auto_search raw_interfaces_only rename_namespace("AddinDesign")
    
    // Office type library (i.e., mso.dll).
    #import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52"\
        auto_rename auto_search raw_interfaces_only rename_namespace("Office")
    using namespace AddinDesign;
    using namespace Office;
    

CConnect クラスにいくつかの変更を手動で加えて、リボンの操作をサポートします。

手順 2: CConnect クラスを変更してリボンの操作をサポートする

  1. Connect.h ファイルを開きます。

    使用する異なるインターフェイスに応じていくつかの typedef 宣言を追加して、この後の作業を簡素化します。

  2. CConnect クラスで _IDTExtensibility2 の IDispatchImpl 宣言を見つけ (図 3 を参照)、それを IDTImpl という名前の typedef にします。

    図 3. ウィザードによって生成された IDispatchImpl

    ウィザードで生成された IDispatchImpl

  3. IRibbonExtensibility についても RibbonImpl という名前の同様の typedef を追加し、それを CConnect の基本クラスのリストに追加します。操作が終了すると、クラスは以下のコードのようになります。

    typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &__uuidof(__AddInDesignerObjects), /* wMajor = */ 1>
    IDTImpl;
    
    typedef IDispatchImpl<IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), /* wMajor = */ 2, /* wMinor = */ 5>
    RibbonImpl;
    
    class ATL_NO_VTABLE CConnect :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CConnect, &CLSID_Connect>,
    public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_NativeAddinLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDTImpl,
    public RibbonImpl
    
  4. ATL COM MAP に以下のインターフェイスを追加します。

    COM_INTERFACE_ENTRY(IRibbonExtensibility)
    
  5. E_NOT_IMPL ではなく S_OK を返すように、実際の機能をまだ追加していないものも含めて、すべてのメソッドを変更します。

手順 3: IRibbonExtensibility を実装する

IRibbonExtensibility の実装を追加します。以下のコードのようになります。

public:
STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
{
    if(!RibbonXml)
        return E_POINTER;
    *RibbonXml = CComBSTR("XML GOES HERE");  
    return S_OK;
}

リボン XML を格納するかどうかを決定する必要があります。XML をコードに直接埋め込むという方法もありますが、XML をリソースとしてプロジェクトに追加する方が管理しやすくなります。

手順 4: リボン XML をリソースとしてプロジェクトに対して設定する

  1. ソリューション エクスプローラーで [NativeAddIn] プロジェクトを右クリックし、[追加] をポイントし、[新しいアイテム] をクリックします。

  2. [新しいアイテムの追加] ダイアログ ボックスで、[XML ファイル] テンプレートを選択し、名前に「RibbonManifest.xml」と入力します。

  3. [追加] をクリックしてプロジェクトに追加します。

リボン XML は非常に単純です。ribbon 要素と、複数の tab 要素から成る tab が含まれています。リボン内で、タブはグループを持ち、各グループはボタンを持ちます。図 4 は、これらによって構成される Outlook リボン ユーザー インターフェイス (UI) を示しています。赤い四角形で示されているのがグループで、青い楕円形で示されているのがボタンです。

図 4. Outlook リボン上のタブ、グループ、およびボタン

Outlook のリボンのタブ、グループおよびボタン

次に、新しいタブ、グループ、およびボタンを作成する必要があります。

手順 5: 新しいタブ、グループ、およびボタンの XML を指定する

RibbonManifest.xml ファイルを開き、以下の XML を追加します。

<customUI xmlns="https://schemas.microsoft.com/office/2006/01/customui">
    <ribbon>
        <tabs>
            <tab id="Bing" label="Bing">
                <group id="BingGroup" label="Bing Group">
                    <button id="NewCustomButton" 
                        imageMso="WebPagePreview" 
                        size="large" 
                        label="Search Bing" 
                        onAction="ButtonClicked"/>
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>

手順 4. の設定により、RibbonManifest.xml ファイルはリソースとして格納されており、IRibbonExtensibility::GetCustomUI メソッドから XML を返すことができます。

手順 6: IRibbonExtensibility::GetCustomUI を実装して XML を返す

  1. [表示] メニュー の [その他のウィンドウ] をポイントし、[リソース ビュー] をクリックして、このプロジェクトのリソース ビューに移動します。

  2. [NativeAddIn] ノードを展開し、[NativeAddIn.rc] ノードを右クリックして、[リソースの追加] をクリックします。

  3. [リソースの追加] ダイアログ ボックスで、[インポート] ボタンをクリックします。

  4. ダイアログ ボックスの右下でファイルの種類を [すべてのファイル] に変更し、ファイル リストで RibbonManifest.xml ファイルを選択し、[開く] をクリックします。

  5. [カスタム リソース の種類] ダイアログ ボックスで、リソースの種類の名前に「XML」と入力し、[OK] をクリックします。

  6. XML リソースを実際に処理するコードが必要です。自分で実装するか、以下のコードを CConnect クラスに追加します。

    HRESULT HrGetResource(int nId, 
        LPCTSTR lpType, 
        LPVOID* ppvResourceData,       
        DWORD* pdwSizeInBytes)
    {
        HMODULE hModule = _AtlBaseModule.GetModuleInstance();
        if (!hModule)
            return E_UNEXPECTED;
        HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(nId), lpType);
        if (!hRsrc)
            return HRESULT_FROM_WIN32(GetLastError());
        HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
        if (!hGlobal)
            return HRESULT_FROM_WIN32(GetLastError());
        *pdwSizeInBytes = SizeofResource(hModule, hRsrc);
        *ppvResourceData = LockResource(hGlobal);
        return S_OK;
    }
    
    BSTR GetXMLResource(int nId)
    {
        LPVOID pResourceData = NULL;
        DWORD dwSizeInBytes = 0;
        HRESULT hr = HrGetResource(nId, TEXT("XML"), 
            &pResourceData, &dwSizeInBytes);
        if (FAILED(hr))
            return NULL;
        // Assumes that the data is not stored in Unicode.
        CComBSTR cbstr(dwSizeInBytes, reinterpret_cast<LPCSTR>(pResourceData));
        return cbstr.Detach();
    }
    
    SAFEARRAY* GetOFSResource(int nId)
    {
        LPVOID pResourceData = NULL;
        DWORD dwSizeInBytes = 0;
        if (FAILED(HrGetResource(nId, TEXT("OFS"), 
            &pResourceData, &dwSizeInBytes)))
            return NULL;
        SAFEARRAY* psa;
        SAFEARRAYBOUND dim = {dwSizeInBytes, 0};
        psa = SafeArrayCreate(VT_UI1, 1, &dim);
        if (psa == NULL)
            return NULL;
        BYTE* pSafeArrayData;
        SafeArrayAccessData(psa, (void**)&pSafeArrayData);
        memcpy((void*)pSafeArrayData, pResourceData, dwSizeInBytes);
        SafeArrayUnaccessData(psa);
        return psa;
    }
    
  7. 以下のコードを挿入して IRibbonExtensibility::GetCustomUI の実装を完了し、実際の XML を返します。

    STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
    {
        if(!RibbonXml)
            return E_POINTER;
        *RibbonXml = GetXMLResource(IDR_XML1);
        return S_OK;
    }
    

アドインをビルドして Outlook を実行すると、図 5 に示すように、追加したタブ、グループ、およびボタンが表示されます。

図 5. Outlook リボン上の追加されたタブ、グループ、およびボタン

Outlook のリボンに追加されたタブ、グループおよびボタン

次に、ユーザーが [Search Bing] ボタンをクリックしたときに応答するコードを追加する必要があります (実際の Bing 検索 API コードは後で実装します)。

他の多くのコールバック環境とは異なり、Outlook を含めて Office は、接続ポイントを使用するカスタム インターフェイスではなく、IDispatch を、IRibbonExtensibility 実装へのコールバックの手段として使用します。オブジェクトは、リボン XML から onAction 属性値 (この例では ButtonClicked) が渡された場合、GetIDsofName から DISPID を返す必要があり、IDispatch::Invoke メソッドが DISPID に対して適切に応答する必要があります。メソッドは、IDispatch ポインターである入力パラメーターを使用する必要もあります。

通常、単純な COM アドインを実装する必要がある場合より下位レベルの ATL になるため、コードが多少複雑になっています。

最初に、ButtonClicked メソッドが含まれる COM インターフェイスを作成する必要があります。

手順 7: ButtonClicked メソッドが含まれる COM インターフェイスを宣言する

  1. プロジェクトの IDL ファイル (NativeAddIn.idl) を開きます。

  2. インターフェイスを追加します。インターフェイスはデュアル インターフェイスである必要があり、id 属性に対して一意の DISPID 値を選択する必要があります。これによって、後で DISPID が競合することを回避できます。NativeAddIn.idl ファイル内の宣言は、以下のコードのようになります。

    [
        object,
        uuid(CE895442-9981-4315-AA85-4B9A5C7739D8),
        dual,
        nonextensible,
        helpstring("IRibbonCallback Interface"),
        pointer_default(unique)
    ]
    interface IRibbonCallback : IDispatch{
        [id(42),helpstring("Button Callback")]
        HRESULT ButtonClicked([in]IDispatch* ribbonControl);
    };
    
  3. IConnect に代わって、IRibbonCallback をクラスの既定のインターフェイスにします。

    coclass Connect
    {
        [default] interface IRibbonCallback;
    };
    

インターフェイスを宣言し、既定のインターフェイスを設定したので、CConnect クラスでインターフェイスを実装できます。

手順 8: インターフェイスを実装する

  1. Connect.h ファイルの先頭に以下の宣言を追加します。

    typedef IDispatchImpl<IRibbonCallback, &__uuidof(IRibbonCallback)> 
    CallbackImpl;
    

    typedef の追加は、後で IDispatch::Invoke メソッドを実装するのに役立ちます。

  2. CConnect クラスの基本階層にインターフェイスを追加します。クラスは以下のコードのようになります。

    class ATL_NO_VTABLE CConnect :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CConnect, &CLSID_Connect>,
    public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_NativeAddInLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDTImpl,
    public RibbonImpl,
    public CallbackImpl
    
  3. ATL COM MAP にインターフェイスを追加して、COM_INTERFACE_ENTRY2 エントリ内の _IDTExtensibility2 インターフェイスを置き換えます。COM MAP は以下のようになります。

    BEGIN_COM_MAP(CConnect)
        COM_INTERFACE_ENTRY2(IDispatch, IRibbonCallback)
        COM_INTERFACE_ENTRY(IConnect)
        COM_INTERFACE_ENTRY(_IDTExtensibility2)
        COM_INTERFACE_ENTRY(IRibbonExtensibility)
        COM_INTERFACE_ENTRY(IRibbonCallback)
    END_COM_MAP()
    
  4. IRibbonCallback::ButtonClicked メソッドを実装します。以下のコードを追加して、メッセージ ボックスを開いてコールバックが正しく動作していることを確認できるようにします。

    STDMETHOD(ButtonClicked)(IDispatch* ribbon)
    {
        MessageBoxW(NULL,L"Button Clicked!",L"NativeAddin",MB_OK);
        return S_OK;
    }
    

このコールバックが動作するためには、もう 1 つの手順が必要です。つまり、IDispatch::Invoke の実装をオーバーライドする必要があります。これにはいくつかの方法がありますが、以下の手順でこれを実現できます。ただしこれは、IDispatch を通じて複数のカスタム インターフェイスを公開するための汎用的な手段ではありません。

手順 9: IDispatch::Invoke の実装をオーバーライドする

  1. CConnect クラスに以下のコードを追加します。

    STDMETHOD(Invoke)(DISPID dispidMember, 
        const IID &riid, 
        LCID lcid, 
        WORD wFlags, 
        DISPPARAMS *pdispparams, 
        VARIANT *pvarResult, 
        EXCEPINFO *pexceptinfo, 
        UINT *puArgErr)
    {
        HRESULT hr=DISP_E_MEMBERNOTFOUND;
        if(dispidMember==42)
        {
            hr  = CallbackImpl::Invoke(dispidMember, 
                riid, 
                lcid, 
                wFlags,       
                pdispparams, 
                pvarResult, 
                pexceptinfo, 
                puArgErr);
        }
        if (DISP_E_MEMBERNOTFOUND == hr)
            hr = IDTImpl::Invoke(dispidMember, 
                riid, 
                lcid, 
                wFlags, 
                pdispparams, 
                pvarResult, 
                pexceptinfo, 
                puArgErr);
        return hr;
    }
    

    このコードでは、dispidMember パラメーターがコールバックにとって一意であるかどうかを判断します (この例では 42)。一意の場合、CallbackImpl::Invoke メソッドが呼び出されます。それ以外の場合は、IDTImpl::Invoke メソッドが呼び出されます。このメソッドが、この時点で必要な唯一の他方の IDispatch インターフェイスだからです。

この時点でアドインをビルドできます。新しいリボン上のカスタム ボタンをクリックすると、メッセージ ボックスが表示されます。

フォーム領域

カスタム フォーム領域を追加することによっても、Outlook に UI を追加できます。カスタム フォーム領域を使用すると、Outlook インスペクターにコントロールのパネルを配置できます。これらのフォーム領域は、インスペクター内のさまざまな場所に配置でき、ダイアログ ボックス全体を置き換えることもできます。

このアドインを完了するには、メールアイテムを表示するためのフォームにフォーム領域を追加します。フォーム領域には、Web ブラウザー コントロールがあります。アドインへのフォーム領域のビルドは、リボン UI を追加する手順に似ています。以下の手順を使用して、カスタム フォーム領域を追加します。

最初に、必要なタイプ ライブラリを指定する必要があります。この例では、3 つのタイプ ライブラリを指定します。1 つは Microsoft Word 用 (Outlook は Word を使用して電子メール メッセージを編集するため)、1 つは Outlook 用 (Outlook オブジェクト モデルに対してプログラミングを可能とするため)、および 1 つはフォーム領域用です。

手順 1: 必要なタイプ ライブラリをインポートする

StdAfx.h ファイルに以下の #import ステートメントを追加します。

#import "libid:00020905-0000-0000-C000-000000000046"\
auto_rename auto_search raw_interfaces_only rename_namespace("Word")

// Outlook type library (i.e., msoutl.olb).
#import "libid:00062FFF-0000-0000-C000-000000000046"\
auto_rename auto_search raw_interfaces_only rename_namespace("Outlook")

// Forms type library (i.e., fm20.dll).
#import "libid:0D452EE1-E08F-101A-852E-02608C4D0BB4"\
auto_rename auto_search raw_interfaces_only  rename_namespace("Forms")
using namespace Word;
using namespace Outlook;

using namespace Forms;

次に、_FormRegionStartup インターフェイスを実装します。

手順 2: _FormRegionStartup インターフェイスを実装する

  1. Connect.h ファイルの先頭に、以下のような IDispatchImpl の typedef を追加します。

    typedef IDispatchImpl<_FormRegionStartup, &__uuidof(_FormRegionStartup), &__uuidof(__Outlook), /* wMajor = */ 9, /* wMinor = */ 4>
    FormImpl;
    
  2. CConnect クラスに FormImpl 定義を追加します。最終的な CConnect 宣言は以下のようになります。

    class ATL_NO_VTABLE CConnect :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CConnect, &CLSID_Connect>,
    public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_NativeAddinLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDTImpl,
    public RibbonImpl,
    public CallbackImpl,
    public FormImpl
    
  3. 前のセクションで IDispatch::Invoke をオーバーライドしたので、以下のコードをそのメソッドに追加して、新しいインターフェイスに対処する必要があります。

    STDMETHOD(Invoke)(DISPID dispidMember, 
        const IID &riid, 
        LCID lcid, 
        WORD wFlags, 
        DISPPARAMS *pdispparams, 
        VARIANT *pvarResult,  
        EXCEPINFO *pexceptinfo, 
        UINT *puArgErr)
    {
        HRESULT hr=DISP_E_MEMBERNOTFOUND;
        if(dispidMember==42)
        {
            hr  = CallbackImpl::Invoke(dispidMember, 
                riid, 
                lcid, 
                wFlags, 
                pdispparams, 
                pvarResult, 
                pexceptinfo, 
                puArgErr);
        }
        if (DISP_E_MEMBERNOTFOUND == hr)
            hr = IDTImpl::Invoke(dispidMember, 
                riid, 
                lcid, 
                wFlags, 
                pdispparams, 
                pvarResult, 
                pexceptinfo, 
                puArgErr);
        if (DISP_E_MEMBERNOTFOUND == hr)
            hr = FormImpl::Invoke(dispidMember, 
                riid, 
                lcid, 
                wFlags, 
                pdispparams, 
                pvarResult, 
                pexceptinfo, 
                puArgErr);
        return hr;
    }
    
  4. ATL COM インターフェイス マップに _FormRegionStartup を追加します。

    BEGIN_COM_MAP(CConnect)
        COM_INTERFACE_ENTRY2(IDispatch, IRibbonCallback)
        COM_INTERFACE_ENTRY(IConnect)
        COM_INTERFACE_ENTRY(_IDTExtensibility2)
        COM_INTERFACE_ENTRY(IRibbonExtensibility)
        COM_INTERFACE_ENTRY(IRibbonCallback)
        COM_INTERFACE_ENTRY(_FormRegionStartup)
    END_COM_MAP()
    
  5. _FormRegionStartup の実装を追加します。

    public:
    STDMETHOD(GetFormRegionStorage)(BSTR FormRegionName, 
        LPDISPATCH Item, 
        long LCID, 
        OlFormRegionMode FormRegionMode, 
        OlFormRegionSize FormRegionSize, 
        VARIANT * Storage)
    {
        V_VT(Storage) = VT_ARRAY | VT_UI1;
        V_ARRAY(Storage) = GetOFSResource(IDR_OFS1);
        return S_OK;
    }
    
    STDMETHOD(BeforeFormRegionShow)(_FormRegion * FormRegion)
    {
        if(m_pFormRegionWrapper)
            delete m_pFormRegionWrapper;
        m_pFormRegionWrapper = new FormRegionWrapper();
        if (!m_pFormRegionWrapper)
            return E_OUTOFMEMORY;
        return m_pFormRegionWrapper->HrInit(FormRegion);
    }
    
    STDMETHOD(GetFormRegionManifest)(BSTR FormRegionName, 
        long LCID, 
        VARIANT * Manifest)
    {
        V_VT(Manifest) = VT_BSTR;
        BSTR bstrManifest = GetXMLResource(IDR_XML2);
        V_BSTR(Manifest) = bstrManifest;
        return S_OK;
    }
    
    STDMETHOD(GetFormRegionIcon)(BSTR FormRegionName, 
        long LCID, 
        OlFormRegionIcon Icon, 
        VARIANT * Result)
    {
        return S_OK;
    }
    
    private:
    FormRegionWrapper* m_pFormRegionWrapper;
    
  6. FinalConstruct を変更して m_pFormRegionWrapper を初期化します。

    HRESULT FinalConstruct()
    {
        m_pFormRegionWrapper = NULL;
        return S_OK;
    }
    

実装には、IRibbonExtensibility::GetCustomUI に似た _FormRegionStartup::GetFormRegionManifest というメソッドが含まれていることに注意してください。GetFormRegionManifest メソッドは、Outlook 固有の XML を返すことで、追加するフォーム領域について Outlook に通知する必要もあります。

手順 3: XML でフォーム領域の表示を指定する

  1. 別の XML ファイルをプロジェクトに追加します。名前に「FormManifest.xml」と入力します。

  2. そのファイルに以下の XML を追加します。

    <FormRegion xmlns="https://schemas.microsoft.com/office/outlook/12/formregion.xsd">
        <name>Native</name>
        <formRegionType>adjoining</formRegionType>
        <formRegionName>Search Bing</formRegionName>
        <hidden>true</hidden>
        <ribbonAccelerator>I</ribbonAccelerator>
        <showInspectorCompose>true</showInspectorCompose>
        <showInspectorRead>true</showInspectorRead>
        <showReadingPane>false</showReadingPane>
        <addin>NativeAddin.Connect</addin>
    </FormRegion>
    
  3. このプロジェクトの [リソース ビュー] に移動します ([表示] メニューの [その他のウィンドウ] をポイントし、[リソース ビュー] をクリックします)。

  4. [NativeAddIn] ノードを展開し、[NativeAddIn.rc] ノードを右クリックして、[リソースの追加] をクリックします。

  5. [リソースの追加] ダイアログ ボックスで、[インポート] ボタンをクリックします。

  6. ダイアログ ボックスの右下でファイルの種類を [すべてのファイル] に変更し、ファイル リストで FormManifest.xml ファイルを選択し、[開く] をクリックします。

  7. [カスタム リソース の種類] ダイアログ ボックスで、リソースの種類の名前に「XML」と入力し、[OK] をクリックします。

この実装を完了するには、ヘルパー クラス FormRegionWrapper を作成する必要があります。_FormRegionStartup インターフェイスは、これを使用して、フォーム領域を管理します (および Bing API を呼び出します)。

手順 4: ヘルパー クラスを作成して、フォーム領域を管理し、Bing API を呼び出す

  1. StdAfx.h ヘッダー ファイルに以下の #include ステートメントを追加します。

    #include <msxml2.h>//msxml for parsing the Bing results
    #include <Mshtml.h>//mshtml for controlling the web browser control
    #include <Winhttp.h>//for making the HTTP calls to Bing
    #include <atlstr.h>//for CString
    #include <atlsafe.h>//for the ATL Safearray helper classes
    
  2. リンカーの依存関係に Winhttp.lib と Msxml2.lib を追加します。このためには、プロジェクトを右クリックし、[プロパティ] をクリックします。[プロパティ] ダイアログ ボックスで、[リンカー] ノードを展開し、[入力] をクリックします。[追加の依存関係] の横のドロップダウン ボックスをクリックし、[編集] をクリックします。[追加の依存関係] ダイアログ ボックスで、それぞれ別の行に「winhttp.lib」および「msxml2.lib」と入力し、[OK] をクリックします。

  3. FormRegionWrapper.h という名前の新しいヘッダー ファイルを作成し、以下のコードを追加します。

    #pragma once
    #include "stdafx.h"
    #include <vector>
    #include <algorithm>
    
    /*!-----------------------------------------------------------------------
    FormRegionWrapper - Used to help track all the form regions and to 
    listen to some basic events such as the send button click and close.
    -----------------------------------------------------------------------!*/
    class FormRegionWrapper;
    typedef 
    IDispEventSimpleImpl<2, FormRegionWrapper, &__uuidof(FormRegionEvents)>
    FormRegionEventSink;
    const DWORD dispidEventOnClose = 0xF004;
    class FormRegionWrapper : public FormRegionEventSink
    {
        public:
            HRESULT HrInit(_FormRegion* pFormRegion);
            void Show();
            void SearchSelection();
            void Search(BSTR term);
        private:
            static _ATL_FUNC_INFO VoidFuncInfo; 
        public:
            BEGIN_SINK_MAP(FormRegionWrapper)
            SINK_ENTRY_INFO(2, __uuidof(FormRegionEvents), 
                dispidEventOnClose,    
                OnFormRegionClose, 
                &VoidFuncInfo)
            END_SINK_MAP()
            void __stdcall OnFormRegionClose();
        private:
            CComPtr<_FormRegion> m_spFormRegion;
            CComPtr<_MailItem> m_spMailItem;
            CComPtr<IWebBrowser> m_spWebBrowser;
    };
    
  4. FormRegionWrapper.cpp という名前の C++ (.cpp) ファイルを作成し、以下のコードを追加します。

    #include "stdafx.h"
    #include "FormRegionWrapper.h"
    using namespace ATL;
    
    #define ReturnOnFailureHr(h) { hr = (h); ATLASSERT(SUCCEEDED((hr))); if (FAILED(hr)) return hr; }
    
    // Macro that calls a COM method that returns an HRESULT value.
    #define CHK_HR(stmt)        do { hr=(stmt); if (FAILED(hr)) ; } while(0)
    // Macro to verify memory allocation.
    #define CHK_ALLOC(p)        do { if (!(p)) { hr = E_OUTOFMEMORY; ; } } while(0)
    // Macro that releases a COM object if not NULL.
    #define SAFE_RELEASE(p)     do { if ((p)) { (p)->Release(); (p) = NULL; } } while(0)
    /*!-----------------------------------------------------------------------
    FormRegionWrapper implementation
    -----------------------------------------------------------------------!*/
    _ATL_FUNC_INFO FormRegionWrapper::VoidFuncInfo = {CC_STDCALL, VT_EMPTY, 0, 0}; 
    
    HRESULT FormRegionWrapper::HrInit(_FormRegion* pFormRegion)
    {
        HRESULT hr = S_OK;
        m_spFormRegion = pFormRegion;
        FormRegionEventSink::DispEventAdvise(m_spFormRegion);
        CComPtr<IDispatch> spDispatch;
        ReturnOnFailureHr(pFormRegion->get_Form(&spDispatch));
        CComPtr<Forms::_UserForm> spForm;
        ReturnOnFailureHr(spDispatch->QueryInterface(&spForm));
        CComPtr<Forms::Controls> spControls;
        ReturnOnFailureHr(spForm->get_Controls(&spControls));
        CComPtr<Forms::IControl> spControl;
        CComBSTR bstrWBName(L"_webBrowser");
        spControls->_GetItemByName(bstrWBName.Detach(),&spControl);
        ReturnOnFailureHr(spControl->QueryInterface<IWebBrowser>
            (&m_spWebBrowser));
        spControl.Release();
        CComPtr<IDispatch> spDispItem;
        ReturnOnFailureHr(pFormRegion->get_Item(&spDispItem));
        ReturnOnFailureHr(spDispItem->QueryInterface(&m_spMailItem));
        return hr;
    }
    
    void FormRegionWrapper::Show()
    {
        if(m_spFormRegion)
        {
            m_spFormRegion->Select();
        }
    }
    
    void FormRegionWrapper::SearchSelection()
    {
        if (m_spMailItem)
        {
            CComPtr<_Inspector> pInspector;
            m_spMailItem->get_GetInspector(&pInspector);
            CComPtr<IDispatch> pWordDispatch;
            pInspector->get_WordEditor(&pWordDispatch);
            CComQIPtr<Word::_Document> pWordDoc(pWordDispatch);
            CComPtr<Word::_Application> pWordApp;
            pWordDoc->get_Application(&pWordApp);
            CComPtr<Word::Selection> pSelection;
            pWordApp->get_Selection(&pSelection);
            if(pSelection)
            {
                CComBSTR text;
                pSelection->get_Text(&text);
                Search(text);
            }
        }
    }
    
    void FormRegionWrapper::Search(BSTR term)
    {
        if(m_spWebBrowser)
        {
            CComBSTR html("<html><body><H1>You searched for:");
            html.AppendBSTR(term);
            html.Append("</H1></body></html>");
            CComPtr<IDispatch> docDispatch;
            m_spWebBrowser->get_Document(&docDispatch);
            if(docDispatch==NULL)
            {
                VARIANT vDummy;
                vDummy.vt=VT_EMPTY;
                m_spWebBrowser->Navigate
                    (L"about:blank",&vDummy,&vDummy,&vDummy,&vDummy);
                m_spWebBrowser->get_Document(&docDispatch);
            }
            if(docDispatch!=NULL)
            {
                CComPtr<IHTMLDocument2> doc;
                HRESULT hr = 
                    docDispatch.QueryInterface<IHTMLDocument2>(&doc);
                if(hr==S_OK)
                {
                    SAFEARRAY *psaStrings = 
                        SafeArrayCreateVector(VT_VARIANT, 0, 1);
                    VARIANT *param;
                    HRESULT hr = 
                        SafeArrayAccessData(psaStrings, (LPVOID*)&param);
                    param->vt = VT_BSTR;
                    param->bstrVal = html.Detach();
                    hr = SafeArrayUnaccessData(psaStrings);
                    doc->write(psaStrings);
                    SafeArrayDestroy(psaStrings);
                }
            }
        }
    }
    
    void FormRegionWrapper::OnFormRegionClose()
    {
        if (m_spMailItem)
        {
            m_spMailItem.Release();
        }
        if (m_spFormRegion)
        {
            FormRegionEventSink::DispEventUnadvise(m_spFormRegion);
            m_spFormRegion.Release();
        }
    }
    

    注意

    簡単にするために、このファイルに複数のクラスを含めていますが、実際のアプリケーションでは、各クラスを独自のファイルに配置します。

  5. Connect.h ヘッダー ファイルに FormRegionWrapper.h の #include ステートメントを追加します。

  6. ButtonClicked コールバック メソッドを変更し、以下のコードを追加することによって、FormRegion メンバー変数 (m_pFormRegion) の SearchSelection メソッドを呼び出します。

    STDMETHOD(ButtonClicked)(IDispatch* ribbon)
    {
        if(m_pFormRegionWrapper)
        {
            m_pFormRegionWrapper->SearchSelection();
        }
        return S_OK;
    }
    

フォーム領域を表示する Outlook 用の XML と、フォーム領域を実行するコードが作成されました。次に、フォーム領域自体を作成します。

手順 5: フォーム領域を作成し、Outlook にコントロールを追加する

  1. Outlook で、リボンの [開発] タブを有効にします。[ファイル] タブをクリックし、[オプション] をクリックし、[リボンのユーザー設定] をクリックします。[リボンのユーザー設定] で、[開発] チェック ボックスをオンにして [開発] タブを有効にし、[OK] をクリックします。

  2. [開発] タブを有効にしたら、それを開き、[フォームのデザイン] ボタンをクリックします。

  3. [メッセージ] フォームを選択し、[開く] をクリックします。

    このダイアログ ボックスからフォーム領域をエクスポートすることが目的なので、この例では、[フォームのデザイン] ダイアログ ボックスでどのフォームを選択しても関係ありません。

  4. フォーム デザイナーが表示されたら、[新しいフォーム領域] ボタンをクリックします。新しいフォーム領域が作成され、フォーム デザイナーの前面に表示されます。

  5. [コントロール ツールボックス] ボタンをクリックして、フォームに追加できるコントロールを表示します。

  6. カスタム コントロールを追加します。[ツールボックス] で、[コントロール] タブの任意の場所を右クリックし、[カスタム コントロール] をクリックします。[利用可能なコントロール] で、[Microsoft Web Browser] を選択し、[OK] をクリックします。

    WebBrowser コントロールは、Bing 検索結果のリストを簡単に表示できるので、この例に適しています。コントロールにハイパーリンクを挿入することもできます。Outlook エンド ユーザーがコントロール内のリンクをクリックすると、ブラウザー内でそのページに移動します。

  7. [ツールボックス] からフォーム領域にドラッグすることによって、WebBrowser コントロールをフォームに追加します。フォーム領域の全体を占めるように、コントロールのサイズを調整します。

  8. 新しく追加したコントロールの名前を、_webBrowser に変更します。このためには、コントロールを右クリックし、[プロパティ] をクリックします。FormRegionWrapper クラス内のコードは名前 (単なる文字列) でコントロールを取得するので、コントロールの名前は正確に指定することが重要です。

  9. 外観を整えるために、WebBrowser コントロールのプロパティの [ レイアウト] タブに移動し、[] と [] のプロパティを [フォームに合わせて拡大/縮小] に設定します。フォームは図 6 のようになります。

    図 6. コントロールが追加されたフォーム領域

    コントロールを追加した [フォーム] 領域

  10. フォーム領域を .ofs ファイルとして保存します (.ofs ファイル形式は、Outlook がフォーム領域用に使用するバイナリ ファイルです)。このためには、[領域の保存] ボタンをクリックし、[フォーム領域に名前を付けて保存] をクリックします。ファイル名に「WebBrowser.ofs」と入力し、NativeAddIn のプロジェクト ディレクトリに保存します。

手順 6: フォーム領域を NativeAddIn に接続する

  1. Visual Studio 2010 に戻り、[リソース ビュー] で [NativeAddIn] ノードを展開します。[NativeAddIn.rc] ノードを右クリックし、[リソースの追加] をクリックします。

  2. [リソースの追加] ダイアログ ボックスで、[インポート] ボタンをクリックします。

  3. ダイアログ ボックスの右下でファイルの種類を [すべてのファイル] に変更し、ファイル リストで WebBrowser.ofs ファイルを選択し、[開く] をクリックします。

  4. リソースの種類に「OFS」と入力し、[OK] をクリックします。

Connect.h ファイル内で _FormRegionStartup::GetFormRegionStorage メソッドが呼び出されると, .ofs ファイル内のデータが Outlook に返されます。アドインをビルドして試行する前に、いくつかのエントリを Connect.rgs ファイルに追加する必要があります。また、Windows レジストリにフォーム領域を登録する必要もあります。

手順 7: レジストリにエントリを追加する

Connect.rgs ファイルに以下のコードを挿入します。

HKCU
{
    NoRemove Software
    {
        NoRemove Microsoft
        {
            NoRemove Office
            {
                NoRemove Outlook
                {
                    NoRemove Addins
                    {
                        NativeAddin.Connect
                        {
                            val Description = s 'Sample Addin'
                            val FriendlyName = s 'Sample Addin'
                            val LoadBehavior = d 3
                        }
                    }
                    NoRemove FormRegions
                    {
                        IPM.Note
                        {
                            val TestNativeCOMAddin = s '=NativeAddin.Connect'
                        }
                        IPM.Note.NativeAddin
                        {
                            val TestNativeCOMAddin = s '=NativeAddin.Connect'
                        }
                    }
                }
            }
        }
    }
}

手順 8: アドインをビルドしてテストする

  1. プロジェクトをビルドします。

  2. Outlook を開き、新しいメール メッセージを作成します。

  3. メール メッセージ フォームの下部に、作成したフォーム領域が表示されます。メッセージ内のテキストを強調表示し、[Search Bing] ボタンをクリックします。WebBrowser コントロールが変更されて、検索用語が表示されます (図 7 を参照)。

    図 7. カスタム リボンとカスタム フォーム領域の表示

    表示されるカスタム リボンとフォーム領域

Bing 検索機能の追加

これまでに、アンマネージ Outlook アドインを C++ でビルドする方法について説明してきました。インフラストラクチャを配置したら、このインフラストラクチャの上に特定の機能をビルドできます。

動作する Outlook アドインはできましたが、さらに別の機能を追加してもよいでしょう。Outlook のアンマネージ アドインをビルドする方法に関するこの記事では必ずしも必要な手順ではありませんが、これは製品を越えた機能を適切に示すものです。必要に応じて、以下の手順を使用して、電子メール メッセージ内で選択された用語に基づく Bing 検索を含めることができます。

注意

この機能を追加するには、Bing Developer Center (英語) で AppID の登録を行う必要があります。

Bing 検索機能を追加するには

  1. FormsRegionWrapper.cpp ファイル内のコードを以下のコードに置き換えます。

    #include "stdafx.h"
    #include "FormRegionWrapper.h"
    using namespace  ATL;
    
    #define ReturnOnFailureHr(h) { hr = (h); ATLASSERT(SUCCEEDED((hr))); if (FAILED(hr)) return hr; }
    
    // Macro that calls a COM method that returns an HRESULT value.
    #define CHK_HR(stmt)        do { hr=(stmt); if (FAILED(hr)) ; } while(0)
    
    // Macro to verify memory allocation.
    #define CHK_ALLOC(p)        do { if (!(p)) { hr = E_OUTOFMEMORY; ; } } while(0)
    
    // Macro that releases a COM object if not NULL.
    #define SAFE_RELEASE(p)     do { if ((p)) { (p)->Release(); (p) = NULL; } } while(0)
    class AutoVariant : public VARIANT
    {
    public:
    
        AutoVariant()
        {
            VariantInit(this);
        }
    
        ~AutoVariant()
        {
            VariantClear(this);
        }
    
        HRESULT SetBSTRValue(LPCWSTR sourceString)
        {
            VariantClear(this);
            V_VT(this) = VT_BSTR;
            V_BSTR(this) = SysAllocString(sourceString);
            if (!V_BSTR(this))
            {
                return E_OUTOFMEMORY;
            }        
            return S_OK;
        }
    
        void SetObjectValue(IUnknown *sourceObject)
        {
            VariantClear(this);
            V_VT(this) = VT_UNKNOWN;
            V_UNKNOWN(this) = sourceObject;
            if (V_UNKNOWN(this))
            {
                V_UNKNOWN(this)->AddRef();
            }
        }
    };
    
    class BingHttpRequest
    {
    public:
    
        BingHttpRequest(){}
    
        void Complete()
        {  
            HRESULT hr = CoInitializeEx(NULL,COINIT_APARTMENTTHREADED); 
            m_buffer.Append(m_tempBuffer);
            loadDOM(m_buffer);
            CoUninitialize();
        }
    
        void DoRequestSync(LPWSTR request,IWebBrowser* pWebBrowser)
        {
            m_WebBrowser = pWebBrowser;
            DWORD err =0;
            DWORD dwSize = 0;
            DWORD dwDownloaded = 0;
            LPSTR pszOutBuffer;
            HINTERNET hSession = ::WinHttpOpen(0,
                WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                WINHTTP_NO_PROXY_NAME,
                WINHTTP_NO_PROXY_BYPASS,
                0);
            HINTERNET hConnection  = ::WinHttpConnect(hSession,               
                L"api.bing.net",
                INTERNET_DEFAULT_PORT,
                0);
            CString lpstrRequest =  L"xml.aspx?Sources=
                web&AppID=70FA6D77B407BA830359D291DD4531800EA4DA38";
            lpstrRequest += L"&query=";
            lpstrRequest += request;
            HINTERNET hRequest = ::WinHttpOpenRequest(hConnection,
                L"GET",
                (lpstrRequest.GetString()),
                0, // use HTTP version 1.1
                WINHTTP_NO_REFERER,
                WINHTTP_DEFAULT_ACCEPT_TYPES,
                0); // flags
            if (!::WinHttpSendRequest(hRequest,
                WINHTTP_NO_ADDITIONAL_HEADERS,
                0, // headers length
                WINHTTP_NO_REQUEST_DATA,
                0, // request data length
                0, // total length
                (DWORD_PTR)this)) // context
            {
                err = GetLastError();
            }
            else
            { 
                bool result = WinHttpReceiveResponse( hRequest, NULL);
                do 
                {
                    // Check for available data.
                    dwSize = 0;
                    if (!WinHttpQueryDataAvailable( hRequest, &dwSize))
                        printf( "Error %u in WinHttpQueryDataAvailable.\n",
                            GetLastError());
                        // Allocate space for the buffer.
                        pszOutBuffer = new char[dwSize+1];
                    if (!pszOutBuffer)
                    {
                        printf("Out of memory\n");
                        dwSize=0;
                    }
                    else
                    {
                        // Read the Data.
                        ZeroMemory(pszOutBuffer, dwSize+1);
                        if (!WinHttpReadData( hRequest, (LPVOID)pszOutBuffer, 
                            dwSize, &dwDownloaded))
                            printf("Error %u in WinHttpReadData.\n", 
                                GetLastError());
                        else
                        {
                            m_buffer.Append(pszOutBuffer);
                            printf("%s", pszOutBuffer);
                        }          
                        // Free the memory allocated to the buffer.
                        delete [] pszOutBuffer;
                    }
                } while (dwSize > 0);
                OutputDebugStringW(m_buffer);
                loadDOM(m_buffer);
            }
            WinHttpCloseHandle(hSession);
        }
    
        CComPtr<IWebBrowser> m_WebBrowser;
    
        HRESULT VariantFromString(PCWSTR wszValue, VARIANT &Variant)
        {
            HRESULT hr = S_OK;
            BSTR bstr = SysAllocString(wszValue);
            CHK_ALLOC(bstr);    
            V_VT(&Variant) = VT_BSTR;
            V_BSTR(&Variant) = bstr;
            return hr;
        }
    
        // Helper function to create a DOM instance. 
        HRESULT CreateAndInitDOM(IXMLDOMDocument2 **ppDoc)
        {
            HRESULT hr = CoCreateInstance(CLSID_DOMDocument, 
                NULL,     
                CLSCTX_INPROC_SERVER,  
                IID_IXMLDOMDocument2,
                (void**)ppDoc);
    
            return hr;
        }
    
        void loadDOM(BSTR xml)
        {
            OutputDebugStringW(xml);
            HRESULT hr = S_OK;
            IXMLDOMDocument2 *pXMLDom=NULL;
            IXMLDOMParseError *pXMLErr = NULL;
            BSTR bstrXML = NULL;
            BSTR bstrErr = NULL;
            VARIANT_BOOL varStatus;
            WCHAR ns[]= L"'xmlns:web=
                'https://schemas.microsoft.com/LiveSearch/2008/04/XML/web' " 
                L"'xmlns:search=
                'https://schemas.microsoft.com/LiveSearch/2008/04/XML/element' ";
            AutoVariant v;
            v.SetBSTRValue(L"xmlns:search=\
                "https://schemas.microsoft.com/LiveSearch/2008/04/XML/element\"
                ");
            CHK_HR(CreateAndInitDOM(&pXMLDom));    
            CHK_HR(pXMLDom->loadXML(xml, &varStatus));
            if (varStatus == VARIANT_TRUE)
            {
                CHK_HR(pXMLDom->get_xml(&bstrXML));
            }
            else
            {
                CHK_HR(pXMLDom->get_parseError(&pXMLErr));
                CHK_HR(pXMLErr->get_reason(&bstrErr));
                printf("Failed to load DOM from BING. %S\n", bstrErr);
            }
            CComPtr<IXMLDOMNodeList> resultNodes;
            pXMLDom->setProperty (L"SelectionNamespaces", 
            CComVariant(L"xmlns:search=\
                "https://schemas.microsoft.com/LiveSearch/2008/04/XML/element\"
                xmlns:web=\
                "https://schemas.microsoft.com/LiveSearch/2008/04/XML/web\""));
            pXMLDom->setProperty(L"SelectionLanguage", CComVariant("XPath"));
            CComPtr<IXMLDOMElement> doc;
            CHK_HR(pXMLDom->get_documentElement(&doc));
            hr =  doc->selectNodes(CComBSTR(L
                "//search:SearchResponse/web:Web/web:Results/web:WebResult"),
                &resultNodes);
            long length;
            if(resultNodes!=NULL)
            {
                resultNodes->get_length(&length);
                printf("Results node count %d",length);
                if(m_WebBrowser)
                {
                    CComBSTR html("<html><body><ul>");
                    CComPtr<IXMLDOMNode> pNode;
                    CComPtr<IXMLDOMNode> pTitleNode;
                    CComPtr<IXMLDOMNode> pUrlNode;
                    CComBSTR title;
                    CComBSTR url;
                    for(int i=0;i<length;i++)
                    {
                        resultNodes->get_item(i,&pNode);
                        pNode->get_firstChild(&pTitleNode);
                        pNode->selectSingleNode(CComBSTR(L"web:Url"),&pUrlNode);
                        if(pTitleNode!=NULL&&pUrlNode!=NULL)
                        {
                            html.Append("<li><a target=\"_blank\" href=\"");
                            pUrlNode->get_text(&url);
                            html.AppendBSTR(url);
                            html.Append("\">");
                            pTitleNode->get_text(&title);
                            html.AppendBSTR(title);
                            html.Append("</a></li>");
                        }
                        pNode.Release();
                        pUrlNode.Release();
                        pTitleNode.Release();
                    }
                    CComPtr<IDispatch> docDispatch;
                    m_WebBrowser->get_Document(&docDispatch);
                    if(docDispatch==NULL)
                    {
                        VARIANT vDummy;
                        vDummy.vt=VT_EMPTY;
                        m_WebBrowser->Navigate(L"about:blank",
                            &vDummy,&vDummy,&vDummy,&vDummy);
                        m_WebBrowser->get_Document(&docDispatch);
                        if(docDispatch!=NULL)
                        {
                            CComPtr<IHTMLDocument2> doc;
                            hr = docDispatch.QueryInterface
                                <IHTMLDocument2>(&doc);
                            if(hr==S_OK)
                            {
                                // Creates a new one-dimensional array.
                                SAFEARRAY *psaStrings =
                                    SafeArrayCreateVector(VT_VARIANT, 0, 1);
                                VARIANT *param;
                                HRESULT hr = SafeArrayAccessData(psaStrings,
                                    (LPVOID*)&param);
                                param->vt = VT_BSTR;
                                param->bstrVal = html.Detach();
                                hr = SafeArrayUnaccessData(psaStrings);
                                doc->write(psaStrings);
                                SafeArrayDestroy(psaStrings);
                            }
                            else
                            {
                                OutputDebugString(L
                                    "QI for IHtmlDocument2 failed");
                            }
                        }
                        else
                            OutputDebugString(L"DOC IS STILL NULL!!!!");
                    }
                }
                else
                {
                    printf("No nodes found");
                }
            }
        }
    
        LPSTR m_tempBuffer;
        CComBSTR m_buffer;
    };
    
    /*!-----------------------------------------------------------------------
        FormRegionWrapper implementation
    -----------------------------------------------------------------------!*/
    
    _ATL_FUNC_INFO FormRegionWrapper::VoidFuncInfo = {CC_STDCALL, VT_EMPTY, 0, 0}; 
    
    HRESULT FormRegionWrapper::HrInit(_FormRegion* pFormRegion)
    {
        HRESULT hr = S_OK;
        m_spFormRegion = pFormRegion;
        FormRegionEventSink::DispEventAdvise(m_spFormRegion);
        CComPtr<IDispatch> spDispatch;
        ReturnOnFailureHr(pFormRegion->get_Form(&spDispatch));
        CComPtr<Forms::_UserForm> spForm;
        ReturnOnFailureHr(spDispatch->QueryInterface(&spForm));
        CComPtr<Forms::Controls> spControls;
        ReturnOnFailureHr(spForm->get_Controls(&spControls));
        CComPtr<Forms::IControl> spControl;
        CComBSTR bstrWBName(L"_webBrowser");
        spControls->_GetItemByName(bstrWBName.Detach(),&spControl);
        ReturnOnFailureHr(spControl->QueryInterface
            <IWebBrowser>(&m_spWebBrowser));
        spControl.Release();
        CComPtr<IDispatch> spDispItem;
        ReturnOnFailureHr(pFormRegion->get_Item(&spDispItem));
        ReturnOnFailureHr(spDispItem->QueryInterface(&m_spMailItem));
        return hr;
    }
    
    void FormRegionWrapper::Show()
    {
        if(m_spFormRegion)
        {
            m_spFormRegion->Select();
        }
    }
    
    void FormRegionWrapper::SearchSelection()
    {
        if (m_spMailItem)
        {
            CComPtr<_Inspector> pInspector;
            m_spMailItem->get_GetInspector(&pInspector);
            CComPtr<IDispatch> pWordDispatch;
            pInspector->get_WordEditor(&pWordDispatch);
            CComQIPtr<_Document> pWordDoc(pWordDispatch);
            CComPtr<Word::_Application> pWordApp;
            pWordDoc->get_Application(&pWordApp);
            CComPtr<Word::Selection> pSelection;
            pWordApp->get_Selection(&pSelection);
            if(pSelection)
            {
                CComBSTR text;
                pSelection->get_Text(&text);
                Search(text);
            }
        }
    }
    
    void FormRegionWrapper::Search(BSTR term)
    {
        BingHttpRequest* r = new BingHttpRequest();
        r->DoRequestSync(term,m_spWebBrowser);
    }
    
    void FormRegionWrapper::OnFormRegionClose()
    {
        if (m_spMailItem)
        {
            m_spMailItem.Release();
        }
        if (m_spFormRegion)
        {
            FormRegionEventSink::DispEventUnadvise(m_spFormRegion);
            m_spFormRegion.Release();
        }
    }
    
  2. Outlook をデバッグまたは開始できます。新しいメール メッセージを開きます。テキストを入力し、そのテキストを選択し、[Search Bing] ボタンをクリックします。図 8 のように表示されます。

    図 8. Bing 検索が統合されたアドイン

    Bing 検索が統合されたアドイン

まとめ

_IDTExtensibility2 インターフェイスを実装する Outlook アドインを C++ でビルドする方法について説明しました。Outlook 環境にアドインを読み込むには、_IDTExtensibility2 と Outlook 固有のレジストリ エントリが重要です。

カスタム リボンおよびカスタム フォーム領域という 2 つの一般的な拡張メカニズムを使用することにより、アドインに機能を設定して Outlook UI をカスタマイズすることもできます。カスタム リボンを使用すると、ボタンなどの UI をリボンに追加し、アドインでコンテキスト ベースの UI イベントに応答できます。カスタム フォーム領域を使用すると、WebBrowser コントロールなどの UI 要素を Outlook フォームに追加できます。

この時点で、Outlook UI をカスタマイズするアドインのインフラストラクチャが配置されました。このインフラストラクチャを使用して、目的に合わせてアドインをさらに拡張できます (たとえば、Bing 検索機能を使用して)。

その他の技術情報

Outlook フォーム領域の詳細については、以下のリソースを参照してください。

Office Fluent リボンのカスタマイズの詳細については、以下のリソースを参照してください。