Top Page/ Interface 研究室/

3.C++ から COM 経由で Internet Explorer を操作する(Late Binding 版) [2005/05/05]

Prev Index

今回は、前回と同じことを、Late Bindingで、完全に動的に行ってみます。

今回のサンプル作成およびコンパイルには、Visual Studio.Net 2003 を使い、コンソールアプリケーションとしてコンパイルしました。
なお、OS環境は、Windows 2000 Professional です。 今回のサンプルのダウンロード: browseie-late.zip
C++ [comutil.h]
#ifndef __INC_COMUTIL_H
#define __INC_COMUTIL_H

#include <atlbase.h>

HRESULT GetProperty(IDispatch* pDisp, OLECHAR* wszName, VARIANT* pvResult);
HRESULT PutProperty(IDispatch* pDisp, OLECHAR* pwszName, const VARIANT* pvValue);
HRESULT Invoke(IDispatch* pDisp, OLECHAR* wszMethod, VARIANT* pvResult, int nArgs, ...);

#endif
C++ [comutil.cpp]
#include "comutil.h"
#include <stdarg.h>

HRESULT GetIDOfName(IDispatch* pDisp, OLECHAR* wszName, DISPID* pdispID)
{
    HRESULT hr;
    
    hr = pDisp->GetIDsOfNames(
            IID_NULL,
            &wszName, 
            1,
            LOCALE_USER_DEFAULT,
            pdispID);
    return hr;
}

HRESULT GetProperty(IDispatch* pDisp, OLECHAR* wszName, VARIANT* pvResult)
{
    HRESULT hr;
    DISPID dispID;
    DISPPARAMS dispParams = {NULL, NULL, 0, 0};

    hr = GetIDOfName(pDisp, wszName, &dispID);
    if (FAILED(hr)){ return hr; }

    hr = pDisp->Invoke(
                dispID, IID_NULL,
                LOCALE_USER_DEFAULT,
                DISPATCH_PROPERTYGET,
                &dispParams, pvResult,
                NULL, NULL);

    return hr;
}

HRESULT PutProperty(IDispatch* pDisp, OLECHAR* pwszName, const VARIANT* pvValue)
{
    HRESULT hr;
    DISPID dispID;
    DISPID dispIDNamedArgs[1] = { DISPID_PROPERTYPUT };

    // DISPID を取得
    hr = GetIDOfName(pDisp, pwszName, &dispID);
    if (FAILED(hr)) { return hr; }

    VARIANTARG vArgs[1];
    VariantInit(&vArgs[0]);
    VariantCopy(&vArgs[0], const_cast<VARIANT*>(pvValue));

    DISPPARAMS dispParams;
    dispParams.rgvarg = vArgs;
    dispParams.rgdispidNamedArgs = dispIDNamedArgs;
    dispParams.cArgs = 1;
    dispParams.cNamedArgs = 1;

    hr = pDisp->Invoke(
                dispID, IID_NULL,
                LOCALE_USER_DEFAULT,
                DISPATCH_PROPERTYPUT,
                &dispParams, NULL,
                NULL, NULL);
    VariantClear(&vArgs[0]);

    return hr;
}

HRESULT Invoke(IDispatch* pDisp, OLECHAR* wszMethod, VARIANT* pvResult, int nArgs, ...)
{
    HRESULT hr;
    DISPID dispID;
    va_list ap;
    int i;

    hr = GetIDOfName(pDisp, wszMethod, &dispID);
    if (FAILED(hr)) { return hr; }

    // 引数を VARAINT 配列に設定
    VARIANTARG *pvArgs = NULL;
    if(nArgs > 0){
        pvArgs = new VARIANTARG[nArgs];

        va_start(ap, nArgs);
        for(i=0;i<nArgs;i++){
            VariantInit(&pvArgs[i]);
            VariantCopy(&pvArgs[i], &va_arg(ap, VARIANT));
        }
        va_end(ap);
    }

    // DISPPARAMS の設定
    DISPPARAMS dispParams;
    dispParams.rgvarg = pvArgs;
    dispParams.rgdispidNamedArgs = NULL;
    dispParams.cArgs = nArgs;
    dispParams.cNamedArgs = 0;

    hr = pDisp->Invoke(
                dispID, IID_NULL,
                LOCALE_USER_DEFAULT,
                DISPATCH_METHOD,
                &dispParams, pvResult,
                NULL, NULL);

    // VARIANT 配列の後始末
    for(i=0;i<nArgs;i++){
        VariantClear(&pvArgs[i]);
    }

    if(pvArgs != NULL){
        delete[] pvArgs;
        pvArgs = NULL;
    }
    return hr;
}
C++ [browseie-late.cpp]
#include <windows.h>
#include <atlbase.h>
#include <cstdio>
#include <cassert>
using namespace std;

#include "comutil.h"

int main(int argc, char** argv)
{
    HRESULT hr;

    // COM の初期化
    hr = CoInitialize(NULL);
    if(FAILED(hr)){
        printf("CoInitialize 失敗\n");
        exit(1);
    }

    // CLSID の取得
    CLSID clsid;
    hr = CLSIDFromProgID(L"InternetExplorer.Application", &clsid);
    if(FAILED(hr)){
        printf("CLSIDFromProgID 失敗\n");
        exit(1);
    }

    // インスタンス生成
    IDispatch* pDispIE = NULL;
    hr = ::CoCreateInstance(
                clsid, NULL,
                CLSCTX_ALL,
                IID_IDispatch, (void**)&pDispIE);
    if(FAILED(hr)){
        printf("CoCreateInstance 失敗\n");
        exit(1);
    }

    // objIE.Visible = True
    VARIANT vTrue;
    
    VariantInit(&vTrue);
    vTrue.boolVal = VARIANT_TRUE;
    vTrue.vt = VT_BOOL;

    hr = PutProperty(pDispIE, L"Visible", &vTrue);
    VariantClear(&vTrue);
    if(FAILED(hr)){
        printf("objIE.Visible = True 失敗\n");
        exit(1);
    }

    // objIE.Navigate("http://www.yahoo.co.jp/")
    VARIANT vResult, vStr;
    BSTR bstrURL;

    bstrURL = SysAllocString(L"http://www.yahoo.co.jp");

    VariantInit(&vStr);
    vStr.bstrVal = bstrURL;
    vStr.vt = VT_BSTR;

    VariantInit(&vResult);
    hr = Invoke(pDispIE, L"Navigate",  &vResult, 1, vStr);
    if(FAILED(hr)){
        printf("objIE.Navigate 失敗\n");
        exit(1);
    }

    SysFreeString(bstrURL);
    VariantClear(&vResult);
    VariantClear(&vStr);

    // Busy の間待つ
    do{
        hr = GetProperty(pDispIE, L"Busy", &vResult);
        if(FAILED(hr)){
            printf("objIE.Busy の取得失敗\n");
            exit(1);
        }
        assert(vResult.vt == VT_BOOL);
    }while(vResult.boolVal == VARIANT_TRUE);

    VariantClear(&vResult);

    // Document を取得
    IDispatch* pDispDoc;
    hr = GetProperty(pDispIE, L"Document", &vResult);
    if(FAILED(hr)){
        printf("objIE.Document の取得失敗\n");
        exit(1);
    }
    assert(vResult.vt == VT_DISPATCH);
    pDispDoc = vResult.pdispVal;    
    pDispDoc->AddRef();        // (*) VariantClear で Document が解放されないよう、参照カウントを 1 増やす。
    VariantClear(&vResult);

    // タイトルをメッセージボックスで表示
    LPCTSTR lpTitle;
    
    USES_CONVERSION;                 //     OLE2CT に必要
    hr = GetProperty(pDispDoc, L"Title", &vResult);
    if(FAILED(hr)){
        printf("objIE.Document.Title の取得失敗\n");
        exit(1);
    }
    assert(vResult.vt = VT_BSTR);

    lpTitle = OLE2CT(vResult.bstrVal);
    MessageBox(NULL, lpTitle, lpTitle, MB_OK);
    
    // IE とドキュメントの解放
    pDispIE->Release();
    pDispDoc->Release();

    CoUninitialize();

    return 0;
}

[解説]

長いですね。今回おさえておくポイントは以下の4つです。
  1. "InternetExplorer.Application" から CLSID を取得し、CoCreateInstance でインスタンスを生成する方法
  2. IDispatch に対して、プロパティを取得・設定したり、メソッドを呼んだりする方法
  3. VARIANT 型の扱い方
  4. 文字列の扱い

インスタンスの生成

VBScript でのオブジェクトに相当するものは、C++ では IDispatch* です。
IDispatch を作る手順は以下のとおりです。
  1. ProgID ("InternetExplorer.Application" などの文字列) から、CLSID を取得する
  2. CoCreateInstance で、インスタンスを生成する
    // CLSID の取得
    CLSID clsid;
    hr = CLSIDFromProgID(L"InternetExplorer.Application", &clsid);

    // インスタンス生成
    IDispatch* pDispIE = NULL;
    hr = ::CoCreateInstance(
                clsid, NULL,
                CLSCTX_ALL,
                IID_IDispatch, (void**)&pDispIE);
ここまでは簡単ですね。

IDispatch を使って、プロパティの取得・設定、メソッド呼び出し

IDispatch に対して、プロパティを取得・設定したり、メソッドを呼ぶには、以下の手順を踏みます。
  1. メソッド・プロパティ名から DISPID (Dispatch ID) を取得する(IDispatch::GetIDsOfNames)
  2. DISPPARAMS に、引数を設定する
  3. IDispatch::Invoke メソッドを呼び出す
上のソースコードの、comutil.h の、PutProperty, GetProperty, Invoke が、上記のことを行っています。
このまま定石として使っていいでしょう。

VARIANT 型の扱い

さて、メソッド呼び出しの際、 DISPPARAMS に設定する引数は、VARIANT 型でなければなりません。
MSDN によると、VARIANT は以下のようになっています。
typedef struct tagVARIANT {
    VARTYPE vt;
    unsigned short wReserved1;
    unsigned short wReserved2;
    unsigned short wReserved3;
    union {
        unsigned char bVal;
        short iVal;
        long lVal;
        float fltVal;.
        double dblVal;
        VARIANT_BOOL boolVal;
        SCODE scode;
        CY cyVal;
        DATE date;
        BSTR bstrVal;
        IUnknown FAR* punkVal;
        IDispatch FAR* pdispVal;
        SAFEARRAY FAR* parray;
        unsigned char FAR* pbVal;
        short FAR* piVal;
        long FAR* plVal;
        float FAR* pfltVal;
        double FAR* pdblVal;
        VARIANT_BOOL FAR* pboolVal;
        SCODE FAR* pscode;
        CY FAR* pcyVal;
        DATE FAR* pdate;
        BSTR FAR* pbstrVal;
        IUnknown FAR* FAR* ppunkVal;
        IDispatch FAR* FAR* ppdispVal;
        SAFEARRAY FAR* FAR* pparray;
        VARIANT FAR* pvarVal;
        void FAR* byref;
    };
};
typedef struct FARSTRUCT tagVARIANT VARIANT;
typedef struct FARSTRUCT tagVARIANT VARIANTARG;
これはいわゆる、タグつき Union ってやつです。
VARTYPE vt; の部分が、型を表していて、union が、値を表しています。

たとえば、VARIANT vValue; として、vValue.vt == VT_BOOL のときは、vValue.boolVal を見ればよく、 vValue.vt = VT_FLOAT のときは、vValue.fltVal を見ればよい、という寸法です。

文字列の扱い

あと気をつけないといけないのは、文字列の扱いでしょうか。 COM での文字列は、基本的に Unicode です。
データ型は、OLECHAR* や BSTR となります。以下の表のように対応しています。
キャラクタ型文字列型文字サイズ
Ccharchar*1 byte
wchar_twchar_t*2 bytes
Platform SDKTCHARLPTSTR1 byte または 2 bytes(UNICODE マクロが定義されているとき)
WCHARLPWSTR2 bytes
COM/OLEOLECHARBSTR2 bytes
BSTR 型の文字列を作るには、以下のようにします。
BSTR bstr = SysAllocString(L"http://www.yahoo.co.jp/");
SysFreeString(bstr);
char* から BSTR に変換、または、BSTR から char* に変換するには、以下のようにします。

[char* -> BSTR]

BSTR bstr = _com_util::ConvertStringToBSTR(psz);   // throw(_com_error)
SysFreeString(bstr);

[BSTR -> char*]

char* psz = _com_util::ConvertBSTRToString(bstr);   // throw(_com_error)
delete[] psz;
また、変換に関しては、OLE2CT などのマクロもあります。
OLE2CT などは、alloca でスタック上にメモリを確保しているようです( http://www.microsoft.com/japan/developer/library/vcmfc/_mfcnotes_tn059.htm)。

次回予告

今回は ポイントをおさえてしまえば、それほど難しくはないと思います。
次回は、作成したオブジェクトが、どんなプロパティやメソッドを持っているのかを調べる方法を書く予定です。

参考URL: http://www2.wbs.ne.jp/~kanegon/