ctypes で実装する注:このドキュメントは Thomas Heller 氏の ctypes モジュールドキュメント を翻訳したものです.オリジナルのドキュメントは ctypes のページ にあります.
概要 :: チュートリアル :: リファレンス :: faq
( 作業中: COM :: COM サンプル )
注意: 作業中です
このウォークスルーでは,単純な exe 形式のサーバ内の COM オブジェクトを 実装する方法を,すでに型ライブラリを作成しているものとして説明します.
この記事の全てのコードは ctypes 配布物の ctypes\com\samples\server
サブディレクトリで入手することができます.
Note: 現在, ctypes.com ではローカルサーバだけを実装することが
できますが,これは将来変更される予定です.
この例は idl ファイルから始まります.これはバイナリ形式の型 ライブラリにコンパイルされるもので,自分でコンパイルするには MIDL コンパイラが必要です.
以下に idl ファイル sum.idl を示します:
/* A TypeLibrary, compiled to sum.tlb */
[
uuid(90810cb9-d427-48b6-81ff-92d4a2098b45),
version(1.0),
helpstring("Sum 1.0 Type Library")
]
library SumLib
{
importlib("stdole2.tlb");
/* a dual interface, derived from IDispatch */
[
object,
dual,
uuid(6edc65bf-0cb7-4b0d-9e43-11c655e51ae9),
helpstring("IDualSum Interface"),
pointer_default(unique)
]
interface IDualSum: IDispatch {
[id(100)] HRESULT Add(double a, double b, [out, retval] double *result);
};
[
uuid(2e0504a1-1a23-443f-939d-869a6c731521),
helpstring("CSum Class")
]
/* a coclass, implementing this interface */
coclass CSum
{
[default] interface IDualSum;
}
};
この型ライブラリは CSum と言う名前の COM オブジェクトを記述して
います. CSum は IDualSum という名の dual インタフェースを実装
しています.このインタフェースはメソッド Add 一つだけを持ちます.
このメソッドは二つの浮動小数点パラメタを受理し,その結果を三つ目の
引数で返します.この三つ目の引数は double へのポインタでなければ
なりません.
メソッドの戻り値は HRESULT で,オートメーションインタフェースでは
典型的なものです. IDispatch から導出されたインタフェースで必要と
されている,このメソッドの dispid は 100 です.
ISum インタフェースは dual インタフェースで,動的ディスパッチ
と直接的な vtable 呼び出しの両方を行うことができます.
midl コンパイラを midl sum.idl /tlb sum.tlb として動作させると,
型ライブラリ sum.tlb が生成されます.
次のステップでは, IDualSum インタフェースへの Python ラッパ
を生成します.これは手作業で行うこともできますが,幸運なことに,
ctypes\com\tools ディレクトリには readtlb.py ユーティリティ
ツールがあります.
readtlb.py は型ライブラリのファイル名をコマンドライン上で指定して
走らせ,現状では Python ソースコードを標準出力に書き出します.従って
ファイルに出力をリダイレクトしなければなりません.
生成されたコードが接尾辞 _gen.py を使うという慣習から,コマンドライン
は以下のようになります:
python ctypes\com\tools\readtlb.py sum.tlb > sum_gen.py
この操作は sum_gen.py ファイルを生成します.以下にその内容を
簡単のために不要な部分を削って示します:
from ctypes import *
from ctypes.com import IUnknown, GUID, STDMETHOD, HRESULT
from ctypes.com.automation import IDispatch, BSTR, VARIANT
class COMObject:
# later this class will be used to create COM objects.
pass
##############################################################################
# The Type Library
class SumLib:
'Sum 1.0 Type Library'
guid = GUID('{90810CB9-D427-48B6-81FF-92D4A2098B45}')
version = (1, 0)
flags = 0x8
path = 'C:\\sf\\ctypes_head\\win32\\com\\samples\\server\\sum.tlb'
##############################################################################
class IDualSum(IDispatch):
"""IDualSum Interface"""
_iid_ = GUID('{6EDC65BF-0CB7-4B0D-9E43-11C655E51AE9}')
IDualSum._methods_ = IDispatch._methods_ + [
(STDMETHOD(HRESULT, "Add", c_double, c_double, POINTER(c_double))),
]
##############################################################################
class CSum(COMObject):
"""CSum Class"""
_reg_clsid_ = '{2E0504A1-1A23-443F-939D-869A6C731521}'
_com_interfaces_ = [IDualSum]
型ライブラリ自体のクラス SumLib ,インタフェースの IDualSum ,
コクラス CSum が生成されているのが分かります.
通常, SumLib や CSum といったクラスがインスタンス化して使われる
ことはなく,単にいくつかの属性を運ぶためのオブジェクトに過ぎません.
インタフェース IDualSum は CSum COM オブジェクトが使われる際に
インスタンス化されます.
XXX _methods_ について記述する.
XXX dispids の記述はどこに?
インタフェースへのラッパを手に入れたので,今度はこのインタフェースを 実装している COM オブジェクトを書く番です.
我々のオブジェクトは dual な COM インタフェースを実装しているので,
ctypes.com.automation モジュールで提供されている DualObjImpl
基底クラスを使います:
from sum_gen import IDualSum, CSum, SumLib
class SumObject(DualObjImpl):
_com_interfaces_ = [IDualSum]
_typelib_ = SumLib
_reg_progid_ = "ctypes.SumObject"
_reg_desc_ = "Sum Object"
_reg_clsid_ = CSum._reg_clsid_
...
XXX なぜ CSum を mixin クラスとして使わないのか?
_com_interfaces_ 属性は我々のオブジェクトが実装している COM
インタフェースからなる配列で,最初のものがデフォルトのインタフェース
となります.全てのインタフェースは ctypes.com.IUnknown のサブクラス
でなければなりません.他のインタフェースをリストに付け加えることも
できますが,今回は行いません.
_typelib_ は DualObjImpl から導出するクラスで必要な属性です.
この属性は sum_gen.SumLib の属性を持っているオブジェクトで
なければなりません. readtlb.py は生成されたモジュール内に
型ライブラリファイルの絶対パス名を書き込むので,ファイルをどこか
別の場所に移動する場合にはこのパス名を変更しなくてはなりません.
型ライブラリへのパスは型ライブラリの登録に使われ, guid , version ,
および flags 属性を使って型ライブラリを実行時にレジストリから
読み出して,dual インタフェースの IDispatch 部分を実装します.
_reg_progid_ および _reg_desc_ は文字列の属性で,COM オブジェクト
の名前を与えます.後者の方はオプションです.
_reg_clsid_ 属性は文字列で guid が入っており,上のコードでは
型ライブラリから値が取り出されます.
現時点では Add メソッドの実装がありません.このメソッド無しには
COM オブジェクトは何も行うことができないので,以下のコードを追加します:
def IDualSum_Add(self, this, a, b, presult):
presult[0] = a + b
return 0
メソッドの名前は雛型 <interface_name>_<method_name> に合致
しなければなりません.ここで interface_name はこのメソッドが属している
インタフェースの名前で, method_name はメソッドのラッパモジュール内での
名前と同じものです.現状では method_name だけの Add を使うことも
できますが,おそらくユーザ定義のインタフェースではこれはお勧めできません.
しかし,基底インタフェースである IUnknown や IDispatch で使うことは
できます.ただし異論はあるかもしれませんね (YMMV).
パラメタはどうなっているのでしょうか? self はコメントする必要は
ないでしょう. a , b ,および presult はインタフェースメソッドで
使われるパラメタで, a および b は Python の float 型です.これらは
ctypes によって自動的に c_double に変換されます. presult は
c_double への ポインタ です.ここでは二つの数字を加算して,その
結果を presult に記憶します.式 "presult[0] = a + b" が加算結果
を presult が指す場所に記憶することを思い出してください.同様の
C のコードは以下のようになるでしょう:
/* double a, b, *presult */
*presult = a + b;
これは以下のように書くこともできます:
/* double a, b, *presult */
presult[0] = a + b;
this パラメタは整数で, COM の this ポインタを表します.この値は
全ての ctypes COM メソッド実装に渡されます.この値は時に便利なことが
ありますが,ほとんどの場合無視されるだけです.
COM オブジェクトの実装は完成しましたが, main プログラムがありません.
Mark Hammond の win32com.server と非常によく似ていますが,
UseCommandLine メソッドが全ての作業を行います:
if __name__ == '__main__':
from ctypes.com.server import UseCommandLine
UseCommandLine(SumObject)
UseCommandLine はコマンドラインスイッチ /regserver ,
/unregserver ,および /embedding に反応します.後者は
COM によって起動された際に exe サーバによって自動的に与えられます.
全てのスイッチは大小文字の区別があり, "/" の代わりに "-" で
はじめることができます.
/regserver および /unregserver は COM オブジェクト
と型ライブラリ を Windows レジストリに登録したり登録解除したります.
準備はととのいました.最初のステップとして,コマンドラインから COM オブジェクトを登録します:
python sum.py -regserver
うまく行ったなら,以下の内容が出力されるはずです:
LocalServer32 C:\Python22\python.exe C:\sf\ctypes_head\win32\com\samples\server\sum.py
Registered Typelib C:\sf\ctypes_head\win32\com\samples\server\sum.tlb
Registered COM class __main__.SumObject
登録解除も同様にうまくいくはずです:
python sum.py -unregserver
以下の内容が出力されます:
deleted LocalServer32
deleted ProgID
しかしこれからこのオブジェクトを使いたいので,再度登録してくださいね.
oleview プログラムを持っているなら,Object Classes -> All Objects
の下にレジストリエントリ "Sum Object" を, Type Libraries の下に
型ライブラリ "Sum 1.0 Type Library (Ver 1.0)" を, Interfaces の
下にインタフェース "IDualSum" を見つけることができるはずです.
oleview を使うことで,COM オブジェクトのインスタンスを生成する
こともできます.メニューエントリ Object -> CoCreateInstanceFlags -> CLSCTX_LOCAL_SERVER がチェックされていることを確かめ, "Sum Object" エントリ
をダブルクリックしてください.
何も表示されない dos 窓がポップアップし (python.exe は pythonw.exe
と違ってローカルサーバとして登録されているため), oleview が
オブジェクトに全ての既知のインタフェースをリクエストするまで
すこしかかった後,実装されているインタフェースが Sum Object の下に
表示されます. IUnknown , IDispatch ,および IDualSum は
期待通りの結果ですが,他のインタフェースも表示されます.これらは
COM 自体によって実装されているもので,おそらく内部的な使用のための
ものです: 私は Windows XP を使っていますが, IClientSecurity , IMarshal ,
IMultiQI ,および IProxyManager があります.
Sum Object コンテキストメニューからインスタンスを開放すると
COM オブジェクトは再び破壊され,dos 窓は閉じます.
Sum オブジェクトを win32com で利用すると,動的ディスパッチで動作 します.また makepy を起動した後でも使うことができます (私の知る限り, このプログラムはディスパッチインタフェースを呼ぶはずですが, そうではなくて vtable メソッドを直接呼ぶのかな?):
from win32com.client import Dispatch
d = Dispatch("ctypes.SumObject")
print d.Add(3.14, 3.14)
ctypes からのオブジェクト利用は, sum_gen ラッパモジュールが
利用可能なためさほど難しくありません. vtable のカスタムインタフェースを
直接呼び出すことを思い出すだけです.そこで,結果を受け取るための
c_double を生成して,そのポインタを渡します:
from sum_gen import CSum
from ctypes.com import CreateInstance
from ctypes import c_double, byref
sum = CreateInstance(CSum)
result = c_double()
sum.Add(3.14, 3.14, byref(result))
print result
this パラメタは使われず,クライアント側のコードで要求されることも
ないので注意してください.
CreateInstance 関数は COM オブジェクトを生成し, デフォルトの
COM インタフェースへのポインタを取得します.COM インタフェースを
2番目のパラメタとして渡して,他のインタフェースポインタを取得することも
できます.
XXX CreateInstance をもっと詳細に?
py2exe は
Python スクリプトを exe ファイルに変換することができます.これには
python22.dll , _ctypes.pyd , _sre.pyd ,および _winreg.pyd
だけが必要です.生成される exe ファイル内に型ライブラリをリソースとして
埋め込むためには --typelib sum.tlb コマンドラインオプションを
付けて py2exe を起動しなければなりません.
exe ファイルを --windows フラグ付きでビルドして,DOS ウィンドウ
を消すことができますが,トレースバックを見るためには
sys.stdout および sys.stderr をどこか別の場所にリダイレクト
する必要があります.
出力をログファイルにリダイレクトすることができます.また以下の
コード断片を使って Windows の OutputDebugString 関数を呼び出す
こともできます.この出力は例えば,デバッガに出力されるか,
Sysinternals DebugView ユーティリティ
に表示されます:
from ctypes import windll
class Output:
def write(self, text):
windll.kernel32.OutputDebugStringA(text)
import sys
sys.stdout = sys.stderr = Output()
このドキュメントを書いている時点では,まだリリースされていない py2exe のバージョン,少なくとも 0.3.4 以降が必要です.