例題9-2:ハードウェアに付属のDLLを使う(2)

A: さて、B君もだいぶC言語を思いだしたようだし、解説を続けよう。

B: …(言葉も出ない)

A: 今、以下のような関数をctypesで呼び出したいとする。引数の型は HANDLE、ULONG、ULONG、PADSMPLCHREQ、LPVOIDとなっているが、HANDLEとULONGの実体は整数なのでctypesがよきにはからってくれる。 問題は4番目の引数PADSMPLCHREQだ。 これは実はADコンバータとともに配布されているライブラリで定義されている構造体で、当然ctypesはこれをどのように扱えばいいのかを知らない。 だから、ctypesにこの構造体がどのようなものかを教えてやる必要がある。

INT AdInputAD(
  HANDLE hDeviceHandle,
  ULONG ulCh,
  ULONG ulSingleDiff,
  PADSMPLCHREQ lpAdSmplChReq,
  LPVOID lpData
);

B: 教えてやるって、どうやって教えるんですか。

A: 構造体の定義をそっくり写し取ったクラスを作ってやるんだ。 PADSMPLCHREQの定義を確認すると以下のようになっていて、PADSMPLCHREQってのはポインタであって実体はADSMPLCHREQという構造体であることがわかる。

typedef struct {
    ULONG                   ulChNo;
    ULONG                   ulRange;
} ADSMPLCHREQ, *PADSMPLCHREQ;

A: ctypesのクラスStructureを基底として、ADSMPLCHREQ構造体に対応するクラスをpython上で定義する。 ポイントは2行目から5行目。_fields_という属性に、構造体のメンバ名と型を対にしたタプルを並べたリストを指定する。

class ADSMPLCHREQ(ctypes.Structure):
    _fields_ = [
        ("ulChNo",ctypes.c_ulong),
        ("ulRange",ctypes.c_ulong)
    ]

B: 面倒くさいですねえ。

A: Cコンパイラはこの構造体の定義を知った上でプログラムをコンパイルするんだからね。 それをpythonで動かそうってのに構造体の定義をpythonに教えてやらんのでは不公平だろう。 これでAdInputAD()を呼び出せる…と言いたいところなんだが、あともう一つ解説が必要だ。いや、二つかな。 まず前回のC言語のプログラムでは、構造体の配列が使われている。

ADSMPLCHREQ chcnf[2];

A: 構造体の配列を使うには、構造体に要素数を乗じて構造体配列の型を生成する。 例えば要素数が変数channelsに格納されているならば、以下のようにする。ちょっとトリッキーだね。

>>> ADSMPLCHREQARRAY = ADSMPLCHREQ * channels
>>> chcnf = ADSMPLCHREQARRAY()

B: 要素数が異なる配列を定義する度に型を作んなきゃいけないんですか。

A: その通り。…というか、私はそうする方法しか知らない。 ctypesのチュートリアルにこの方法が 推奨されている し、この方法で困ったこともないし。

B: はあ、ずいぶん弱気ですね。

2013/11/08 追記: 同じサイズの配列オブジェクトを繰り返し使用するのでなければ、 いちいち名前を付けなくても chcnf=(ADSMPLCHREQ*channels)() と書くことができます。詳しくは 例題21-1 をご覧ください。

A: 自分の用途に十分な程度までしか勉強していないからね。さて、これでやっとAdInputAD()関数を呼ぶ準備ができた。 5番目の引数LPVOIDはデータを格納するバッファへのポインタで、AD変換するチャンネル数だけの要素数を持つunsigned short型の配列を準備してやる必要がある。 C言語のunsigned short型に対応するクラスctypes.c_ushortもADSMPLCHREQと同じpythonのクラスなんだから以下のように作成すればいい。

>>> BUFFER = c_ushort * channels
>>> buff = BUFFER()

2013/11/08 追記: ここも buff=(c_ushort*channels)() と書くことができます。

A: 最後に、chcnfに必要な値を設定して前回説明した参照渡しの方法でAdInputAD()に渡してやればOKだ。

>>> hDeviceHandle = obj.AdOpen("FBIAD1")
>>> chchf[0].ulChNo = 1
>>> chchf[0].ulRange = AD_10V
>>> chchf[1].ulChNo = 2
>>> chchf[1].ulRange = AD_10V
>>> obj.AdInputAD(hDeviceHandle, 2, AD_INPUT_SINGLE, byref(chcnf), byref(buff))

B: ん? AD_10VとかAD_INPUT_SINGLEってなんですか?

A: 元のC言語のプログラムでは#defineで定義されている定数だね。ヘッダファイルに以下のように定義されている。

#define AD_INPUT_SINGLE                             1       // Single-ended input
/*中略*/
#define AD_10V                              0x00080000      // Voltage: bipolar +/- 10 V

A: これに対応するように、pythonでも以下のように定義しておく。

>>> AD_INPUT_SINGLE=  1
>>> AD_10V=   0x00080000

B: うーん、ますます面倒くさいなあ。これ、C言語のヘッダファイルを見ながらちまちま自分で書かないといけないんですよね。 いっそのことC言語で書いた方が早い ような…。

A: 今回のサンプルのような単純で短いものなら間違いなくそうだろうね 。 でも、すでにpythonで複雑な実験プログラムを作っていて、そこにAD変換ボードで外部からのアナログ信号を取り込む機能を追加したい、というような時には絶大な威力を発揮する。 短いプログラムならさておき、ある程度以上の長さのプログラムを他の言語に移植するのはかなりの苦行だからね。 C言語はずいぶん古い言語になってしまったけど、未だにC言語とその発展であるC++言語が利用され続けるのは、こういうハードウェアを叩かないといけない時にC/C++言語ならなんとか出来るというのが大きい。 私もそれでC/C++言語からなかなか離れられなかったんだけど、pythonがctypesという突破口を持っているのを知って、じゃあpythonで行ってみるかなあという気になったのさ。 同様のC言語用ライブラリの利用機能を持つ言語は他にも、フリーで使えて、心理実験に使えそうなライブラリもそろっていて…となると、選択肢は限られてくる。

B: ふーん。よくわかりませんが苦労してるんですねえ。

A: ちなみに私がC言語以外の「何か」を探したきっかけはC/C++言語で視覚刺激を描くのがめんどくさかったからなんだが、 現在は Psychlopsというとても良いC++用のライブラリが配布されている 。 もう少しPsychlopsが出てくるのが早ければそっちに行ったかもしれないな。貴重なプロジェクトなので、ぜひ多くの人に利用してほしい。

B: へえ。じゃあ次回から 「C++で心理実験」 に改名して一からやりなおしたらどうですか。 pythonよりC++の方がつぶしがきくって先輩から聞いてますし、興味あります。

A: そんな元気あるかいな。ここまでpythonで引っ張ったから、もう少しpythonで踏ん張るっての。

B: 自分で「利用してほしい」とか言っといて…。ぶつぶつ。

A: さて、話をctypesに戻すぞ。今回のような共有ライブラリの関数を呼ぶpythonプログラムを頻繁に書くのなら、 構造体や定数などをひっくるめてクラスを作っておくといい。pythonの文字列処理能力をもってすればヘッダファイルをごっそり自動変換なんてことも出来る。

B: そこまでやりますか。根性あるなあ。

A: 後で楽するために今苦労しておくんだよ。「プログラマの3つの美徳」だな。怠慢、短気、傲慢。

B: なんですかそれ。全然美徳じゃないような。

A: 面白いから後で検索してみな。最後に今回のサンプルでもう一つだけ補足しておこう。10行目のAdBmGetSamplingConfig()と12行目のAdBmSetSamplingConfig()という関数なんだが、 この関数は引数にADBMSMPLREQという構造体へのポインタをとる。で、このADBMSMPLREQ構造体の定義を見ると、以下のようにADSMPLCHREQという別の構造体の配列がメンバとなっている。

typedef struct {
    ULONG                   ulChCount;
    ADSMPLCHREQ             SmplChReq[256];  /* 構造体の配列がメンバになっている */
    /* 中略 */
} ADBMSMPLREQ, *PADBMSMPLREQ;

A: このような場合は、まずADSMPLCHREQに対応するクラスを定義して、 そのクラスを使って以下のように定義すればいい。

class ADSMPLCHREQ(ctypes.Structure):
    _fields_ = [
        ("ulChCount",ctypes.c_ulong),
        ("SmplChReq",ADSMPLCHREQ * 256),
# 以下省略

B: …なんというか、今回はさっぱり分かった感じがしません。消化不良な感じ。

A: 上級編だからな。まあ上級編って言っても本職のプログラマから見たら初歩的な内容なんだろうけど、本職でも何でもない心理学者にはちょっとハードルが高い。 ある程度C言語を理解していることを前提としているし、共用ライブラリとかDLLとかいう用語も解説していない。 そういった内容の解説は「pythonの解説」という範囲も「心理学実験のプログラミング」という範囲も大きく超えたものだと思うし、正直私には荷が重すぎる。 世の中には良質なテキストがたくさんあるのでぜひそちらで勉強してほしい。

B: またいつものセリフが出てきましたね。そろそろまとめようとしてますか?

A: ん。このあたりの技術は私自身もまだまだ勉強中のところだ。 また何か自分の仕事の中でこういう共用ライブラリを活用するプログラムを書くことがあれば、例題9-3、9-4、と追加するかもしれない。でも、今回はここまで。 今追われている仕事からの逃避で一気に書き上げたので疲れた。帰って寝る。んじゃ、また明日ね。

B: お疲れさまー