MRが楽しい

MRやVRについて学習したことを書き残す

PythonからDLL(ダイナミックリンクライブラリ)を利用する

本日は Python の技術調査枠です。
Python から DLL を利用する手順について記事にします。
f:id:bluebirdofoz:20191116215552j:plain

DLLファイルの作成

最初に Python から呼び出す DLL ファイルを Visual Studio 2017 を利用して作成します。
[ダイナミック リンク ライブラリ]のプロジェクトを作成します。
今回、プロジェクト名を TestLib としました。
f:id:bluebirdofoz:20191116215603j:plain

プロジェクトが新規作成されました。
外部から参照する関数のエクスポート定義を行う必要がありますので、ヘッダーファイルを作成します。
f:id:bluebirdofoz:20191116215612j:plain

作成されたヘッダーファイルを TestLib.h と名付け、以下の定義を追加しました。

#ifdef TESTLIB_EXPORTS
#define TESTLIB_API extern "C" __declspec(dllexport)
#else
#define TESTLIB_API extern "C" __declspec(dllimport)
#endif

「TESTLIB」の部分は英大文字のプロジェクト名で定義します。
プロジェクトを DLL としてコンパイルする際に Visual Studio が[TESTLIB_EXPORTS]を定義します。
これにより、コンパイル時に __declspec(dllexport) の定義が選択され、DLL 外部から対象の関数が参照できるようになります。
f:id:bluebirdofoz:20191116215623j:plain

今回は Python 側で ctypes を使って外部関数を呼び出します。
この場合、使用する関数が C リンケージに登録されている必要があります。
デフォルトでは C++ リンケージに登録されるため、シンボル名が変更されてしまいます。
これに対処するため、extern "C" を利用して C リンケージにも関数を登録しています。
qiita.com

この TESTLIB_API のエクスポート定義を利用して2つの int 値を足し算する SumInt 関数を宣言しました。

TESTLIB_API int SumInt(int, int);

f:id:bluebirdofoz:20191116215633j:plain

次に SumInt 関数の実装を行います。
プロジェクト作成時に生成された TestLib.cpp を開き、以下のコードを追加しました。

#include "TestLib.h"

int SumInt(int x, int y)
{
    return x + y;
}

f:id:bluebirdofoz:20191116215926j:plain

これで DLL の実装は完了です。ビルド設定を[Release][x64]に変更します。
ビルドのビット設定は Python のビット数に合わせる必要があります。
著者環境では 64 bit の Python を利用するため、[x64]を選択しています。
f:id:bluebirdofoz:20191116215952j:plain

メニューから ビルド -> ソリューションのビルド を実行します。
f:id:bluebirdofoz:20191116220003j:plain

ビルドを完了すると TestLib.dll が出力されます。
f:id:bluebirdofoz:20191116220012j:plain

Ptyonの実装

Python から DLL の外部関数を呼び出すため、ctypes を利用します。
docs.python.org

ctypes で DLL を読み込む場合、以下のようにスクリプトを実装します。

from ctypes import *
dll = cdll.LoadLibrary("TestLib.dll")

SumInt 関数を呼び出し、その結果を print 出力するスクリプトを作成しました。
・dllAccessTest.py

# ctypesインポート
from ctypes import *
# DLLをロードする
dll = cdll.LoadLibrary("TestLib.dll")
# SumInt関数を呼び出して結果を出力する
print(dll.SumInt(3, 2))

スクリプトと同じディレクトリに TestLib.dll を配置し、PowerShell を開きます。
f:id:bluebirdofoz:20191116220022j:plain

以下のコマンドを実行して、スクリプトを実行します。

> py .\dllAccessTest.py

DLL 内の SumInt 関数が呼び出され、5 が戻り値として print 出力されれば成功です。
f:id:bluebirdofoz:20191116220032j:plain

エラーの対処

以下のエラーが発生した場合の対処方法について記述しておきます。

有効なWin32アプリケーションではありません。

Python と DLL の bit が一致していない場合、本エラーが発生します。
ビルド時の[x86]または[x64]を変更して対処します。

OSError: [WinError 193] %1 は有効な Win32 アプリケーションではありません。

f:id:bluebirdofoz:20191116220115j:plain

AttributeError:function 'SumInt' not found

外部参照可能な同名の関数が見つからない場合、本エラーが発生します。
Python 側で呼び出す関数名が誤っていないか、C 側でエクスポート定義を行っているか確認します。
または C++ リンケージの影響でシンボル名が変更されている可能性があります。
extern "C" の定義を指定して C リンケージで関数を登録します。

AttributeError: function 'SumInt' not found

f:id:bluebirdofoz:20191116220125j:plain