【Python】【Windows】PythonからWindows DLLを呼び出そうとしたら案外大変だった話
「大変だったのでムカつきがてら記事にする」シリーズも、第3弾になりました。
仕事でネット上の情報、知識に助けられることも多いいおぶろぐですが、
お前ら本当に自分でやってみて確認したのか?
コピペ記事ちゃうんかこれ?
と文句付けたい記事も多いですね…。
まあ、いおぶろぐが「やりたいこと」が、そういう記事で済まない「一般的でないこと」なのが原因であるのがいけないような気もしますが…。
今回は、Windowsサーバ上で、PythonからWindows DLLを呼び出す話です。
前提
- すでにC#のWebアプリは動いている
- 新規機能追加として、Python3を使ったWebAPIを作る(サーバはWindows)
- Pythonでも 1.の既存アプリで使っているユーザ認証用DLLを使うべき
- ただしそのDLLは10年近く使われており、ソースどころか仕様書もよくわからない
- そのDLLを 1.の既存アプリが呼び出して使う部分のC#ソースは(当然)存在する
やったこと1
- ctypesを使って呼び出してみる
- “function not found" と言われる
- じゃあDLLの中にそんな関数があるのか調べよう。とDUMPBINを使ってみる
- DUMPBIN /EXPORTS しても、情報が表示されない
- DUMPBIN /CLRHEADER したら、情報が表示された
やったこと1の解説
「python dll」とかでGoogle検索して出てくるのは 「DLLの呼び出しには ctypes を使え」という記事ばかり。
なのでその通りにやってみると、"function not found" のエラーメッセージが。
“function not found" なので、こちらの呼び出し方(function名の指定方法とか)が間違っているのかもしれない。DLLの中にどんな関数名があるのか調べてみよう。
でもどうやって調べるの?
→ Visual Studio から使える “DUMPBIN" ってコマンドがあるらしい。
ってことで早速、Visual Studio の Developer Command Prompt より
DUMPBIN /EXPORTS (DLLパス)
としてみましたが…表示されない。
DUMPBIN って、いわゆるC言語で作られたDLLの情報を表示してくれるらしい。
逆に言えば、C#などでビルドされた、いわゆるCOMのDLLの情報は表示されない。
つまりこのDLLはC#ビルドのDLLなのでは?
というわけで今度は
DUMPBIN /CLRHEADER (DLLパス)
してみる。
CLRとは “Common Language Runtime" のことで…つまりは .NET のことですな(ざっくり過ぎ)。
“clr Header:" 以下に情報が表示されました。
このDLLはC#(or .NET の何か)でビルドされたものであることが確定しました。
なぜそれが重要かというと、ctypes では、C#ビルドのDLLを呼び出すことができないからなんです…。
やったこと2
- Python3.8未満をWindowsにインストール
- pythonnet を pip install する
- clr(pythonnet)を使ってDLL呼び出し出来た
やったこと2の解説
C#でビルドされたDLLは、pythonから “ctypes" では呼び出せない。
→ pythonnet ってやつをインストールすれば出来るらしい。ので、python上で
pip install pythonnet
する。
ただし、pythonnet は python3.8 以上ではインストールできないようです。ですのでいおぶろぐも 3.7.9 をインストールしました。
Python3.7.9 で pip install pythonnet した後、ソースを動かしてみると、DLL呼び出し出来ました!
ただ、pythonソース上は “clr" なんですよね、pythonnetって。ちょっとややこしい。
サンプルは以下です。
import clr
clr.AddReference(".\dll\Auth\Person.AuthCheck")
import Person.AuthCheck
dll = Person.AuthCheck.Authentication()
# 認証キー取得
key, errMessage = dll.GetAuthKey("PersonA", "User01", None)
print(key)
# 認証キー検証
res, errMessage = dll.ChkAuthKey(key, "PersonA", "User01", None)
print(res == 0)
そして注意点。
clr.AddReference() の引数にDLLのパスを指定する必要がありますが、末尾の “.dll" を付与してはいけないようです。(3行目)
その後の import には、対象DLLをVisualStudioで参照設定した場合に、C#側「メタデータより」に表示される namespace の値を設定します。途中にピリオドが入っていたりしてもそのまま。(4行目)
インスタンス化する場合は、namespace名.class名 としましょう。(6行目)
また、ref がある場合は、タプルで受けます(引数はNoneとする)。(9、13行目)
代替案
ひとつだけ問題なのは、pythonnetが2022年半ばを過ぎてもpython3.8以降でインストールすら出来ない状態だということです。
すでにpythonバージョンが3.10である現在、dllを起動するためだけに、古いpythonを使い続けるのはセキュリティ的に問題があると思いますし、上司やお客様の合意が得られるとは思えません。
VisualStudioを持っているのであれば、dllをラップするexeを作り、それをpythonから、
subprocess.run()
で起動するのが、一番簡単かつ確実である気がします。
ディスカッション
コメント一覧
まだ、コメントがありません