pythonからのCOMの利用 - オートメーションサーバ

オートメーションサーバ

pywin32は、win32com.serverパッケージに オートメーションサーバとしての機能を実装しています。 また、CLSIDなどの128ビットGUIDを生成する機能はpythoncomパッケージにあります。
pythonのオートメーションサーバは次の図のように、 dll、exe両対応のIDispatchインターフェースを公開します。
COMサーバは次の手順で実装します。
  • CLSIDの生成
  • pythonクラスの実装
  • サーバ登録部分の実装
  • CLSIDの生成

    サーバの実装にはCLSIDが必要です。 オートメーションではインターフェースとしてIDispatchを使いますので、 新しいIIDを生成する必要はありません。
    次のコマンドを実行すると、pythonでCLSIDを生成できます。
    C:\> python
    >>> import pythoncom
    >>> pythoncom.CreateGuid()
    

    pythonクラスの実装

    COMとして公開したいクラスは、通常のpythonクラスとして実装します。 実装の際にCOMの情報を追加しておくことで、 次の簡易サーバ登録関数が使えるようになります。
    例えば、文字列を引数にとり、それをログファイルに書き出すコードは次のようになります。
    class PyIchigo1:
        def __init__(self):
            self.filename = 'c:/tmp/pyichigo1.txt'
        def Write(self, logstr):
            with open(self.filename,'a') as f:
                f.write(logstr)
    
    win32com.server.register.RegisterClasses()は、 COM情報をクラス変数の形で与えると、その情報を登録します。 クラス変数として次の情報を与えることができます。 最低でもCLSID、ProgID、公開メソッドは与える必要があるでしょう。
    変数内容
    _public_methods_公開するメソッド名をpythonのシーケンスとして与えます。
    _reg_clsid_自分のクラスのために生成したCLSIDを指定します。必須です。
    _reg_progid_ProgIDとして使う文字列を与えます。必須です。
    _reg_verprogid_ProgIDをCOMサーバのバージョンごとに分けたい場合には、こちらも与えます。
    _reg_desc_desc デフォルトでは_reg_progid_と同じ
    _reg_icon_関連するアイコンのフルパスを文字列として与えます。
    _reg_threading_ ThreadingModelを変更したい場合、その文字列です。なければbothになります。
    _reg_catids_COMサーバのカテゴリIDを与えます。
    _reg_clsctx_サーバフラグ、デフォルトではpythoncom.CLSCTX_INPROC_SERVER | pythoncom.CLSCTX_LOCAL_SERVERです。
    _reg_typelib_filename_予め生成したタイプライブラリをこのCOMサーバと同時に登録したい場合、そのファイル名を指定します。
    例えば、PyIchigo1.Logwriterという名前でWrite()を公開したい場合は、 まずGUIDを生成して_reg_clsid_に与えたうえで、次の変数を追加します。
        _reg_clsid_ = '{7F0AAD75-E363-469A-9310-5B78E1BB3D9E}'
        _reg_progid_ = 'PyIchigo1.Logwriter'
        _public_methods_ = ['Write']
    

    サーバ登録部分の実装

    COMサーバとしてシステムに登録する処理は、次の1行でできます。 pythonで実装したCOMサーバは、 INPROCとLOCALの2つの形で登録され、どちらの形でも利用できます。
    win32com.server.register.RegisterClasses(PyIchigo1)
    
    これらをまとめたpython版COMオートメーションサーバのサンプルコードは、 次のようになります。
    
    import win32com.server.register
    
    class PyIchigo1:
        # > import pythoncom
        # > pythoncom.CreateGuid()
        _reg_clsid_ = '{7F0AAD75-E363-469A-9310-5B78E1BB3D9E}'
        _reg_progid_ = 'PyIchigo1.Logwriter'
        _public_methods_ = ['Write']
        def __init__(self):
            self.filename = 'c:/tmp/pyichigo1.txt'
        def Write(self, logstr):
            with open(self.filename,'a') as f:
                f.write(logstr)
    
    
    if __name__ == '__main__':
        # --unregister: unregister
        win32com.server.register.RegisterClasses(PyIchigo1)
    
    
    C++と違いIClassFactoryやIDispatchを自分で実装する必要がないため、簡単に実装できます。
    別のpythonスクリプトから呼び出す場合はimportを使うと楽ですが、 pythonのオートメーションクライアント機能を使って呼び出したい場合は、 次のように呼び出せます。
    import win32com.client
    
    obj = win32com.client.Dispatch("PyIchigo1.Logwriter")
    obj.Write("ichigopack python COM sample 1\n");
    
    
    pythonから呼び出してもあまりありがたみはありませんが、 COMサーバとして実装すれば、IDispatchを介して、 C#やJavaScriptなど、他の言語からも呼び出せるようになります。 また、手間はかかりますが、次の例のようにC++コードから呼び出すことも可能です。
    //
    // Copyright (c) 2018 The Ichigopack Project (http://ichigopack.net/).
    //
    
    #include <windows.h>
    #include <ole2.h>
    #include <iostream>
    #include <iomanip>
    
    
    extern "C" const GUID CLSID_PyIchigo1 =
        {0x7F0AAD75, 0xE363, 0x469A, {0x93,0x10,0x5B,0x78,0xE1,0xBB,0x3D,0x9E}};
    
    
    int main(int argc, char** argv)
    {
        HRESULT hr;
        hr = CoInitialize(NULL);
        if ( FAILED(hr) ) {
            std::cout << "CoInitialize() error: " << hr << std::endl;
            return 1;
        }
        IDispatch* pObj = NULL;
        hr = CoCreateInstance(
                 CLSID_PyIchigo1, NULL,
                 CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER |
                 CLSCTX_LOCAL_SERVER,
                 IID_IDispatch, (LPVOID*)&pObj);
        if ( SUCCEEDED(hr) ) {
            LCID lcid = GetSystemDefaultLCID();
            LPOLESTR szFuncNames[1] = {L"Write"};
            DISPID dispFunc[1] = {0};
            VARIANT v, vret;
    
            VariantInit(&v);
            VariantInit(&vret);
            hr = pObj->GetIDsOfNames(IID_NULL, szFuncNames, 1, lcid, dispFunc);
            if (SUCCEEDED(hr)) {
                v.bstrVal = SysAllocString(
                    L"ichigopack python sample 1 from c++\n");
                if (v.bstrVal != NULL) {
                    v.vt = VT_BSTR;
                } else {
                    hr = E_OUTOFMEMORY;
                }
            }
            if (SUCCEEDED(hr)) {
                DISPPARAMS dp;
                EXCEPINFO ei;
                UINT uArgErr = 0;
                dp.rgvarg = &v;
                dp.rgdispidNamedArgs = NULL;
                dp.cArgs = 1;
                dp.cNamedArgs = 0;
                hr = pObj->Invoke(dispFunc[0], IID_NULL, lcid, DISPATCH_METHOD,
                    &dp, &vret, &ei, &uArgErr);
            }
            VariantClear(&v);
            VariantClear(&vret);
    
            if (FAILED(hr)) {
                std::cout << "PyIchigo1 call error: "
                    << std::hex << std::setfill('0')
                    << std::setw(8) << hr << std::endl;
            }
            pObj->Release();
        } else {
            std::cout << "CoCreateInstance() error: "
                      << std::hex << std::setfill('0')
                      << std::setw(8) << hr << std::endl;
        }
        CoUninitialize();
        return 0;
    }
    
    

    解説一覧

  • COM/ActiveXの解説ページ 目次
  • セットアップ
  • オートメーションクライアント
  • オートメーションサーバ