スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

--.--.-- | スポンサー広告


ctypesモジュール(1)

今回はctypesモジュールです。

ctypesモジュールは「Pythonのための外部関数ライブラリです。このライブラリは Cと互換性のあるデータ型を提供し、動的リンク/共有ライブラリ内の関数呼び出しを可能にします。動的リンク/共有ライブラリを純粋なPythonでラップするために使うことができます。 」とドキュメントに記載されています。

要は、Pythonから、dllを呼び出せるよ。ってことですね。
ただし、C++形式のDLLは、クラスや派生などが絡むんで、呼び出しには苦労しそうなので、スルーし、C言語形式のDLLのみ試したいと思います。
(尚、COM形式の場合、Python for Windows extensions(PyWin32)に含まれるwin32comモジュールを使うのが良さそうです)


まず、使い方をctypesのチュートリアルを見て確認しましょう。
大体どの言語でも同じなのですが、
1. DLLをロード
2. ロードしたハンドルから、関数を取得
3. 関数実行
という流れが一般的です。

また、関数呼び出し規約は、cdeclは、Pascal、stdcall、fastcallなど、複数ありますが、Windows DLLで使用されるのは、cdeclとstdcallのどちらかです。

cdeclのDLLを呼び出す場合は、cdllオブジェクトを使用し、stdcallのDlLを呼び出す場合は、windllオブジェクトを使用します。
(COMなどoleベースのDLLなら、oledllオブジェクトが用意されています。)


とりあえず、簡単なコードを書いてみましょう。



from ctypes import *
import time

print('Start Sleep')
time.clock()
windll.kernel32.Sleep(1000) # msで指定
print('Finish Sleep time=%lf(s)' % time.clock())


たぶん、Windows APIの中で、もっともシンプルであろう、Sleep関数です。
この関数は、指定時間スレッドを停止させます。このソースだと1000ms==1秒後にSleep関数から抜けてきます。それをtime.clock()で時間を計って、本当にSleep(1000)で1秒抜けてこないか確認しています。

実際動かすと


Start Sleep
Finish Sleep time=0.999545(s)



となり、ちゃんと動いているようです。






では、
windll.kernel32.Sleep(1000)
を、詳しく調べてみましょう。

まず、今回の目的であるSleep()関数ですが、C言語で書くと

void __stdcall Sleep( unsigned long dwMilliseconds );

となり、kernel32.dllに含まれます。
(http://msdn.microsoft.com/ja-jp/library/cc429358.aspx)

kernel32.dllは、stdcall呼び出し規約ですので、windllオブジェクトを使用します。

つまり、
windll.kernel32.Sleep(1000)
は、
stdcall呼び出し規約で、kernel32(.dll)に入っている、Sleep関数を引数1000で呼び出す。
という意味になります。






せっかくなんで、cdeclの呼び出しのほうも試してみましょう。
msvcrt.dllは、Cランタイム関数が入っており、cdecl呼び出し規約です。


from ctypes import *
print(cdll.msvcrt.abs(-39))


abs()は絶対値を返す関数です。
ちゃんと動きました。








非常にわかりやすい記述方法でDLLが呼び出せることがわかりました。


でも、今まで学んできた、Pythonの実装(クラスの実装)から、この記述を理解しようとすると、どうしても理解できない点があります。(本Blogの[プログラミング基本]→[with文]までの学習内容での話)


それは、

・ctypes.windllオブジェクトは、kernel32.dllを知っているのか?
・ctypes.cdllオブジェクトは、msvcrt.dllを知っているのか?
・さらに、DLLが持つ関数を知っているのか?

という点です。


今までのクラス実装では、クラスの中にメソッドやメンバ変数をあらかじめ記述しておき、それを呼び出していました。その考え方からすると、kernel32というメンバがあらかじめwindllに入っていないといけない訳です。

もし、windllやcdllオブジェクトに、kernel32やmsvcrtなどのDLL情報が入っているとしたら、山ほどあるこれ以外のDLL情報は入っているのか? 自作DLLはどうしたらいいのか? などと心配になってしまいます。


そこで、cdllオブジェクトが持つ、メンバを確認してみました。

from ctypes import *
print(dir(cdll)) #dir()関数を使うとオブジェクトの中身が見れる



['LoadLibrary', '__class__', '__delattr__', '__dict__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', '_dlltype']



これを見ると、msvcrtオブジェクトは無いようです。
でも、じゃあ、何故 cdll.msvcrt と呼べたのでしょうか?


一度、cdll.msvcrt.abs(-39)を呼び出してから、cdllの中身を確認してみます。


from ctypes import *
cdll.msvcrt.abs(-39)
print(dir(cdll)) #dir()関数を使うとオブジェクトの中身が見れる



['LoadLibrary', '__class__', '__delattr__', '__dict__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', '_dlltype', 'msvcrt']




なんということでしょう。msvcrtオブジェクトが追加されています。
どうやら、
cdll.msvcrt
で、オブジェクトが追加されているようです。


ちょっと寄り道して、ctypesのソースを見てみます。
C:\Python32\Lib\ctypes\__init__.py
の中の、LibraryLoader.__getattr__(self, name)で、オブジェクトの追加が行われているようです。
(ctypes.windll, ctypes.cdllは、LibraryLoaderクラスのインスタンスです)


class LibraryLoader(object):
def __init__(self, dlltype):
self._dlltype = dlltype

def __getattr__(self, name):
if name[0] == '_':
raise AttributeError(name)
dll = self._dlltype(name)
setattr(self, name, dll) #ココで追加されてる
return dll

def __getitem__(self, name):
return getattr(self, name)

def LoadLibrary(self, name):
return self._dlltype(name)



クラスの__getattr__メソッドがどういうときに呼ばれるか調べたところ、

「属性値の検索を行った結果、通常の場所に属性値が見つからなかった場合 (すなわち、self のインスタンス属性でなく、かつクラスツリーにも見つからなかった場合) に呼び出されます。 」

とドキュメントに記載されています。
つまり、kernel32やmsvcrtオブジェクトは、windllやcdllには存在しなかったので、__getattr__メソッドが呼び出され、そこで、DLLのロードが行われ、WinDLLやCDLLのインスタンスがkernel32やmsvcrtオブジェクトとしてクラスに追加されたということになります。

このような動作は、CやC++から見ると、かなり新鮮です。
CやC++は、あらかじめ変数や関数をコンパイル前に宣言しておく必要があるため、動的に追加をすることが出来ません。近いことをやるとすれば、辞書型を持って、DLL名が指定されたらDLLをロードしてハンドルを追加、みたいなコードになると思います。

違う言語に触れると、自分の知らなかった考え方や実装方式が見れ、勉強になると実感しました。







話がだいぶ横道にそれてしまいました。
実装方法は、上記の書き方で問題ないのですが、LibraryLoaderクラスには、LoadLibrary(self,name)メソッドがあるので、一応試してみたいと思います。


from ctypes import *
#LoadLibraryを試す
dllobj = windll.LoadLibrary('kernel32')
dllobj.Sleep(1000)


試すと、ちゃんと動作しました。
当然ですね。





だいぶ長くなってしまったので、今回はココまで。
次回は、自作のDLLをPythonで呼び出してみたいと思います。



スポンサーサイト

2011.05.31 | コメント(0) | トラックバック(0) | ライブラリリファレンス





コメント




コメントの投稿


管理者にだけ表示を許可する


«  | ホーム |  »


FC2Ad

プロフィール

Green

Author:Green
WindowsをターゲットとしたSW開発者

メインはC#、C/C++、VB
Pythonは初めて。

ここでは、Python ver3系 (ver.3.1.3を使用中)で説明を行っています。ver.2系とは、結構違うのでご注意ください。

連絡先
連絡先

Python Official Website
Python Official Website

目次

はじめに セットアップ プログラミング基本 ライブラリ オリジナルソフトウェア その他

検索フォーム

QRコード

QR

書籍











その他


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。