MRが楽しい

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

Pythonの自作モジュールを作成してPythonにインストールする その2(C言語で作成する拡張モジュール)

本日は Python の技術調査枠です。
Python で自作モジュールを作成してPythonにインストールする手順を記事にします。
f:id:bluebirdofoz:20191124023005j:plain

今回はC言語を使って拡張モジュールを作成してみます。
前回記事の続きです。
bluebirdofoz.hatenablog.com

C言語で書いたPythonモジュールの作成

インストールを行うモジュールとして、以下のC言語プログラムを作成しました。
"Hello World"を標準出力する関数と、受け取った数値の足し算結果を返す関数、オプションで文字列を受け取る関数を持ちます。
・hello_module.c

// Python関連の構造体を利用するため、Python.h をインポート
#include <Python.h>

// "Hello World"を標準出力する関数
static PyObject* print_str (PyObject *self, PyObject *args) {
    // "Hello World"を標準出力する
    printf("Hello_world\n");
    // 戻り値がない場合は関数の最後に Py_RETURN_NONE を記述する
    Py_RETURN_NONE;
}

// 数値の引数を受け取り足し算結果を返す関数
static PyObject* sum_arg(PyObject *self, PyObject *args){
    // 数値を受け取るための変数を作成する
    int p_argx, p_argy;
    // 受け取った args を展開する今回は2つの数値(int)に分解する
    if(!PyArg_ParseTuple(args, "ii", &p_argx, &p_argy))
    {
        // 引数の取得に失敗した場合はエラー終了
        return NULL;
    }
    // 受け取った数値を足し算して int32 型に格納する
    int32_t result_val = p_argx + p_argy;
    // 足し算結果を返却する
    // 返却の際は必ず Python の型への変換を行う
    return PyLong_FromLong(result_val);
}

// 文字列の引数をオプションで受け取り標準出力する関数
static PyObject* print_arg(PyObject *self, PyObject *args){
    // 文字列を受け取るための変数を作成する
    // 引数はオプションとし、引数がない場合はデフォルト値を表示する
    const char *p_argstr = "Default";
    // 受け取った args を展開する今回は文字列(char[])に分解する
    // '|'以降のフォーマットはオプションとして扱われる
    if(!PyArg_ParseTuple(args, "|s", &p_argstr))
    {
        // 引数の取得に失敗した場合はエラー終了
        return NULL;
    }
    // 受け取った文字列を標準出力する
    printf(p_argstr);
    printf("\n");
    // 出力結果を文字列としても返却する
    // 返却の際は必ず Python の型への変換を行う
    return PyUnicode_FromString(p_argstr);
}

// 外部から参照できるメソッドの定義
static PyMethodDef HelloextMethods[] = {
    // 以下のフォーマットで Python から呼び出すことのできるメソッドを定義する
    // {(参照時の関数名), (C言語内の関数名), (引数の定義), (関数の説明文)}
    // 引数を受け取らない場合は引数の定義に METH_NOARGS を設定する
    {"printstrext", (PyCFunction)print_str, METH_NOARGS, "print_str description"},
    {"sumargext", (PyCFunction)sum_arg, METH_VARARGS, "sum_arg description"},
    {"printarg", (PyCFunction)print_arg, METH_VARARGS, "print_arg description"},
    // 終端を示す
    {NULL, NULL, 0, NULL}
};

//モジュールの定義
static struct PyModuleDef helloextmodule = {
    PyModuleDef_HEAD_INIT,  // 固定値(常に PyModuleDef_HEAD_INIT で初期化する)
    "helloext",             // モジュールの名前
    "helloext description", // モジュールの説明文
    -1,                     // メモリ確保の指定(-1は静的な領域を確保しない)
    HelloextMethods         // メソッドの定義への参照
};

// モジュールの初期化関数
PyMODINIT_FUNC PyInit_helloext (void) {
    // 構造体の初期化やモジュールの作成を行う
    // モジュールの作成
    return PyModule_Create(&helloextmodule);
}

上記のモジュールを登録する際は以下のような setup.py を用意します。
・setup.py

from distutils.core import setup, Extension

setup(
    name="helloext",              # モジュールの名前
    version="1.0.0",              # モジュールのバージョン
    author="holomon",             # モジュールの作者
    url="",                       # モジュールのホームページ
    description="description",    # モジュールの説明(help(hello)で確認可能)
    install_requires=[],          # 依存パッケージ([pip install -e .]実行時に一緒にインストール)
    extras_require={
        "develop" : [],           # 拡張依存パッケージ([pip install -e . develop]実行時に一緒にインストール)
    },
    ext_modules=[                 # パッケージのソースコード
        Extension('helloext', ['hello_module.c'])
    ]
)

C言語で拡張モジュールを作成する場合、Extension に記述された helloext がモジュールの名前となります。

モジュールの関数について

関数の戻り値は PyObject* 、引数は PyObject *self, PyObject *args である必要があります。

static PyObject* print_str (PyObject *self, PyObject *args)

受け取った引数は PyArg_ParseTuple でパースを行います。

PyArg_ParseTuple(args, "ii", &p_argx, &p_argy)

パースを行う際は第二引数で、受け取る引数のフォーマットを指定します。
フォーマット指定の識別子は以下のページに詳細な説明があります。
docs.python.org

以下に幾つかの凡例があります。
docs.daemon.ac

戻り値は必ず Python のオブジェクト型で返却を行います。
docs.python.org

例えば、数値は以下の整数型オブジェクトに変換して返却します。
docs.python.org

インストールの実行

作成した setup.py と hello_module.c を同じディレクトリに配置します。
f:id:bluebirdofoz:20191124023149j:plain

今回はテストのため、インストールを行う Python を直接指定します。
例えば以下のパスの Python.exe を利用して setup.py を実行すれば、実行した Python ディレクトリにモジュールがインストールされます。

D:\Python\SetupPythonTest\Python37\python.exe

setup.py を配置したディレクトリで、Shiftキー+右クリックから[PowerShellウィンドウをここで開く]を実行します。
f:id:bluebirdofoz:20191124023158j:plain

PowerShell が起動したら、以下のコマンドを実行して hello_module.c をビルドします。

python .\setup.py build

f:id:bluebirdofoz:20191124023206j:plain

ビルドが成功すると build ディレクトリが生成され、helloext.(ビルド環境).pyd ファイルが出力されます。
f:id:bluebirdofoz:20191124023217j:plain

続けて、以下のコマンドを実行するとモジュールのインストールが実行されます。

python .\setup.py install

f:id:bluebirdofoz:20191124023226j:plain

成功すると、実行した Python ディレクトリの以下のパスに pyd ファイルと helloext-(バージョン)-(ビルド環境).egg-info ファイルが追加されます。

(Pythonディレクトリ)\Lib\site-packages

f:id:bluebirdofoz:20191124023236j:plain

モジュールの実行確認

python を起動して以下のコマンドを実行します。

import helloext
print(helloext)

インストールしたモジュールへのパスが表示されればインストールは成功しています。
f:id:bluebirdofoz:20191124023245j:plain

続けて以下の通り、関数を実行してみます。

helloext.printstrext()
helloext.sumargext(2, 4)

それぞれ"Hello World"の出力と、足し算の結果が出力されれば成功です。
f:id:bluebirdofoz:20191124023255j:plain

更に文字列の関数も実行してみます。
引数を渡すパターンと、渡さないパターンでそれぞれ戻り値を確認してみます。

ret_str = helloext.printarg()
print(ret_str)
ret_str = helloext.printarg("Hello Test")
print(ret_str)

引数を渡さない場合は"Default"、引数を渡した場合はその文字列が出力されれば成功です。
f:id:bluebirdofoz:20191124224001j:plain

Pythonの自作モジュールを作成してPythonにインストールする その1(Pythonスクリプトのモジュール)

本日は Python の技術調査枠です。
Python で自作モジュールを作成してPythonにインストールする手順を記事にします。
f:id:bluebirdofoz:20191123093954j:plain

setup.py

setup.py は一般的に Python のモジュール定義を記述するスクリプトに利用されるファイル名です。
モジュールの各種設定を本スクリプトファイルに定義し、インストールの際に実行します。
インストールの定義は setup() 関数で定義します。

Pythonスクリプトで書いたモジュールの作成

インストールを行うモジュールとして、以下の Python スクリプトを作成しました。
"Hello World"を標準出力する関数と、数値の引数を受け取り足し算結果を返す関数を定義します。
・hello.py

# 標準出力テスト
def printstr():
    # "hello world"を出力
    print("hello world")
    return

# 引数戻り値テスト
def sumarg(arg_x=0, arg_y=0):
    # 引数を足し算して返却する
    return arg_x + arg_y

上記のスクリプトを登録する際は以下のような setup.py を用意します。
・setup.py

from setuptools import setup

setup(
    name="hello",                 # モジュールの名前
    version="1.0.0",              # モジュールのバージョン
    author="holomon",             # モジュールの作者
    url="",                       # モジュールのホームページ
    description="description",    # モジュールの説明(help(hello)で確認可能)
    install_requires=[],          # 依存パッケージ([pip install -e .]実行時に一緒にインストール)
    extras_require={
        "develop" : [],           # 拡張依存パッケージ([pip install -e . develop]実行時に一緒にインストール)
    }
)

モジュールの名前は対象スクリプトのファイル名から .py を除いた部分です。
今回は hello.py を対象とするので hello がモジュールの名前となります。

インストールの実行

作成した setup.py と hello.py を同じディレクトリに配置します。
f:id:bluebirdofoz:20191123094020j:plain

今回はテストのため、インストールを行う Python を直接指定します。
例えば以下のパスの Python.exe を利用して setup.py を実行した場合、実行した Python ディレクトリにモジュールがインストールされます。

D:\Python\SetupPythonTest\Python37\python.exe

setup.py を配置したディレクトリで、Shiftキー+右クリックから[PowerShellウィンドウをここで開く]を実行します。
f:id:bluebirdofoz:20191123094030j:plain

PowerShell が起動したら、以下のコマンドを実行して hello.py をインストールします。

python .\setup.py install

f:id:bluebirdofoz:20191123094041j:plain

成功すると、実行した Python ディレクトリの以下のパスに hello-(バージョン)-(ビルド環境).egg ファイルが追加されます。
初めて install を実施した際は、追加モジュールへのパスを示す easy-install.pth も追加されています。

(Pythonディレクトリ)\Lib\site-packages

f:id:bluebirdofoz:20191123094050j:plain

easy-install.pth には以下のようなモジュールへのパスが追加されています。

./hello-1.0.0-py3.7.egg

モジュールの実行確認

python を起動して以下のコマンドを実行します。

import hello
print(hello)

インストールしたモジュールへのパスが表示されればインストールは成功しています。
f:id:bluebirdofoz:20191123094058j:plain

続けて以下の通り、関数を実行してみます。

hello.printstr()
hello.sumarg(1,3)

それぞれ"Hello World"の出力と、足し算の結果が出力されれば成功です。
f:id:bluebirdofoz:20191123094106j:plain

次回はC言語を使って拡張モジュール関数を作成してみます。
bluebirdofoz.hatenablog.com

参考ページ

公式のマニュアルは以下のページ。
packaging.python.org

qiita.com
qiita.com
qiita.com

py-k4aを使ってPythonからAzureKinectを利用する その2(Pythonスクリプトでの実行)

本日はAzureKinectの調査枠です。
py-k4aを使ってPythonからAzureKinectを利用してみます。
今回はインストールした py-k4a を Python スクリプトから実行してみます。
f:id:bluebirdofoz:20191122093926j:plain

前回記事の続きです。
bluebirdofoz.hatenablog.com

サンプルスクリプトの実行

py-k4a には予め example.py という動作確認用のサンプルが付属しています。
展開ディレクトリで右クリックから[PowerShellウィンドウをここに開く]をクリックします。
f:id:bluebirdofoz:20191122093939j:plain

PowerShell で以下のコマンドを実行し、example.py を実行してみます。

python .\example.py

f:id:bluebirdofoz:20191122093947j:plain

実行が完了すると、ディレクトリに test.jpg が出力されます。
RGBカメラのキャプチャ画像が出力されていれば成功です。
f:id:bluebirdofoz:20191122093956j:plain

著者環境では実行時に以下のエラーが発生しました。

[2019-11-21 08:09:53.425] [error] [t=11032] D:\a\1\s\extern\Azure-Kinect-Sensor-SDK\src\allocator\allocator.c (118): k4a_capture_t_get_context(). Invalid k4a_capture_t 000002826A88A060
[2019-11-21 08:09:53.425] [error] [t=11032] D:\a\1\s\extern\Azure-Kinect-Sensor-SDK\src\allocator\allocator.c (295): Invalid argument to capture_dec_ref(). capture_handle (000002826A88A060) is not a valid handle of type k4a_capture_t
[2019-11-21 08:09:53.425] [error] [t=11032] D:\a\1\s\extern\Azure-Kinect-Sensor-SDK\src\image\image.c (51): k4a_image_t_get_context(). Invalid k4a_image_t 000002826A6FF140
[2019-11-21 08:09:53.426] [error] [t=11032] D:\a\1\s\extern\Azure-Kinect-Sensor-SDK\src\image\image.c (333): Invalid argument to image_dec_ref(). image_handle (000002826A6FF140) is not a valid handle of type k4a_image_t

本エラーが発生しても動作には問題ありませんが、現状エラー原因は不明です。

Depth画像の取得

サンプルスクリプトはRGBカメラの画像出力のみなので、試しに Depth 画像の出力も行ってみます。
Python スクリプト側で 16bit 深度の png 画像を出力する必要があるので、python-opencv をインストールします。

opencv-python は pip でのインストールが可能です。PowerShell を起動し、以下のコマンドを実行します。

python -m pip install -U opencv-python

Python にパスを通していない場合は Python
のインストールディレクトリで以下のコマンドを実行します。

.\python -m pip install -U opencv-python

f:id:bluebirdofoz:20191122094017j:plain

サンプルスクリプトとして、example.py を改変して以下の Python スクリプトを作成しました。
・example_depth.py

以下のコマンドを実行し、example_depth.py を実行してみます。

python .\example_depth.py

f:id:bluebirdofoz:20191122094032j:plain

実行が完了すると、ディレクトリに 16pngtest.jpg が出力されます。
深度の距離が浅いのでビューアソフトで開いても、ほとんど真っ暗な画像が表示されます。
f:id:bluebirdofoz:20191122094041j:plain

試しに以下の手順を流用し、Blender に読み込んでディスプレイスメントで凹凸として画像を読み込んでみました。
bluebirdofoz.hatenablog.com
凹凸が再現できていれば成功です。
f:id:bluebirdofoz:20191122094051j:plain

py-k4aを使ってPythonからAzureKinectを利用する その1(ビルドとインストール)

本日はAzureKinectの調査枠です。
py-k4aを使ってPythonからAzureKinectを利用してみます。
今回はビルドとインストール手順を記事にします。
f:id:bluebirdofoz:20191121091609j:plain

py-k4aはbrendandburnsさん(@brendandburns)が公開しているPythonモジュールです。
github.com
f:id:bluebirdofoz:20191121091743j:plain

今回は Windows 環境の64bit版でのインストール手順をまとめます。

必要な環境

事前に以下のツールの環境のインストールが必要です。
・AzureKinectSDK
Python
・Cコンパイラ(VC++)

それぞれ以下の記事に従ってインストールを実施します。
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com

今回、筆者環境では AzureKinectSDK 1.3.0, Python 3.7.4, VisualStudio2017のCコンパイラを利用しています。
VisualStudioのインストールでは VC++コンポーネントをインストールする必要があります。
f:id:bluebirdofoz:20191121091825j:plain

py-k4aのダウンロード

以下のページからpy-k4aのソースコードを取得します。
今回は Download ZIP でソースコードを取得しました。
github.com
f:id:bluebirdofoz:20191121091849j:plain

ダウンロードした py-k4a-master.zip ファイルを展開します。
これで py-k4a のダウンロードは完了です。
f:id:bluebirdofoz:20191121091907j:plain

Includeファイルのコピー

ダウンロードしたプログラムを Python でビルドするため、include ファイルの参照を追加します。
以下の k4a.h のヘッダファイルを含む k4a ディレクトリを Python 配下の include ディレクトリにコピーします。
・コピー元

C:\Program Files\Azure Kinect SDK v1.3.0\sdk\include\k4a

・コピー先

C:\Users\(ユーザ名)\AppData\Local\Programs\Python\Python37\include

f:id:bluebirdofoz:20191121091921j:plain

Libファイルのコピー

次に lib ファイルを追加します。
以下の k4a.lib のライブラリファイルを Python 配下の lib ディレクトリにコピーします。
・コピー元

C:\Program Files\Azure Kinect SDK v1.3.0\sdk\windows-desktop\amd64\release\lib\k4a.lib

・コピー先

C:\Users\(ユーザ名)\AppData\Local\Programs\Python\Python37\libs

f:id:bluebirdofoz:20191121091932j:plain

py-k4aのビルド

これで py-k4a のビルドの準備が整いました。
展開した py-k4a-master のディレクトリを開き、右クリックから[PowerShellウィンドウをここに開く]を実行します。
f:id:bluebirdofoz:20191121092359j:plain

以下のコマンドを実行し、py-k4a のビルドを実行します。

python .\setup.py build

f:id:bluebirdofoz:20191121091941j:plain

ビルドが成功すると、build ディレクトリが生成され、k4a.cp37-win_amd64.pyd ファイルが出力されます。
cp37-win_amd64 の部分はビルド環境の情報が反映されます。
f:id:bluebirdofoz:20191121091953j:plain

py-k4aのインストール

出力された pyd ファイルを Python にインストールします。
インストールディレクトリを整理したいので、今回は以下のインストール作業を手動で行いました。

1.Python ディレクトリの Lib\site-packages 配下に py-k4a ディレクトリを作成する。
f:id:bluebirdofoz:20191121092003j:plain

2.Lib\site-packages 配下に py-k4a ディレクトリへのパスを記述する py-k4a.pth ファイルを作成する。
f:id:bluebirdofoz:20191121092013j:plain

3.py-k4a.pth ファイルに以下の通り、py-k4a ディレクトリへのパスを記述する。

py-k4a

f:id:bluebirdofoz:20191121092024j:plain

4.py-k4a ディレクトリに k4a.cp37-win_amd64.pyd ファイルをコピーする。
f:id:bluebirdofoz:20191121092033j:plain

5.py-k4a ディレクトリに以下の2つの *.dll ファイルをコピーする。

C:\Program Files\Azure Kinect SDK v1.3.0\sdk\windows-desktop\amd64\release\bin\k4a.dll
C:\Program Files\Azure Kinect SDK v1.3.0\sdk\windows-desktop\amd64\release\bin\depthengine_2_0.dll

f:id:bluebirdofoz:20191121092043j:plain

これで Python へのインストールは完了です。

動作確認

py-k4a を組み込んだ Python ディレクトリの python.exe を起動し、以下のコマンドを実行してみます。

import k4a
print(k4a)

k4a.cp37-win_amd64.pyd の参照パスが表示されれば py-k4a のインポートに成功しています。

module 'k4a' from '(参照パス)\lib\site-packages\py-k4a\k4a.cp37-win_amd64.pyd'

f:id:bluebirdofoz:20191121092051j:plain

次回は実際にツールを利用してみます。
bluebirdofoz.hatenablog.com

エラー対処

以下にいくつかのエラー対処のパターンを記載します。

error: command 'cl.exe' failed: No such file or directory

python .\setup.py build」の実行時に以下のエラーが発生することがあります。

error: command 'cl.exe' failed: No such file or directory

Cコンパイラへのパスが通っていないことが原因です。
環境変数の[Path]にCコンパイラへのパスを手動で追加することで解消されることがあります。

例えば VisualStudio のCコンパイラ(32bit版)はデフォルトでは以下のディレクトリにインストールされます。

C:\Program Files (x86)\Microsoft Visual Studio\(バージョン)\(ライセンス)\VC\Tools\MSVC\(バージョン)\bin\Hostx86\x86\cl.exe

Pythonの環境構築手順

本日は環境構築枠です。
Pythonのインストール手順を記事にします。
Windows 10環境へのインストール手順になります。
www.python.org
f:id:bluebirdofoz:20191120214533j:plain

インストーラの取得

以下の公式ダウンロードページから最新のインストーラを取得します。
www.python.org
f:id:bluebirdofoz:20191120214600j:plain

[Download Python]からインストーラが取得できます。
もしバージョンやインストールOSなどを任意に選択したい場合は、ページ下部の[Releasse version]から選択します。
f:id:bluebirdofoz:20191120214609j:plain

任意のバージョンを選択すると、そのバージョンのリリースページが開きます。
[Files]の一覧から任意のOSやインストール形式のインストーラやファイルがダウンロードできます。
今回は Windows x86-64 向けの executable installer を利用します。
f:id:bluebirdofoz:20191120214618j:plain

インストールの実行

ダウンロードした python-X.X.X.exe を実行します。
f:id:bluebirdofoz:20191120214628j:plain

インストーラが起動します。
デフォルトでは[Add Python X.X to PATH]のチェックが外れています。
チェックを入れると環境変数のパスが追加され、PowerShellコマンドプロンプトpython コマンドが利用できます。
[Install Now]をクリックします。
f:id:bluebirdofoz:20191120214637j:plain

インストールの完了まで待機します。
f:id:bluebirdofoz:20191120214646j:plain

インストールが完了すると、以下の画面が表示されます。
[Disable path length limit]は実行すると、Windowsのパスの長さがデフォルトで260文字に制限されているのを解除できます。
深いディレクトリ階層や長いファイル名を用いないのであれば解除しなくても問題ありません。
f:id:bluebirdofoz:20191120214656j:plain

インストールの確認

Windows 10 環境ではデフォルトの設定で以下のパスに Python がインストールされます。

C:\Users\(ユーザ名)\AppData\Local\Programs\Python\Python37

f:id:bluebirdofoz:20191120214711j:plain

[Add Python X.X to PATH]でパスを通した場合は、PowerShell などで python または py コマンドで Python が利用できます。
f:id:bluebirdofoz:20191120214723j:plain

PythonのctypesでDLLの関数から様々な戻り値を受け取る その3(構造体)

本日は Python の技術調査枠です。
Python の ctypes で DLL の関数から様々な戻り値を受け取る方法について記事にします。
f:id:bluebirdofoz:20191119090010j:plain

前回記事の続きです。
bluebirdofoz.hatenablog.com

構造体を受け取る

次に DLL 内で定義した構造体のデータを受け取ってみます。
DLL に以下の通り、構造体を定義します。こちらも参照渡しで構造体型を受け取る関数を追加します。
・TestLib.h

#pragma once

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

struct vector3 {
    int x;
    int y;
    int z;
};

struct databox {
    int id;
    char name[10];
    vector3 point;
};

TESTLIB_API void ReturnStruct(databox*);

・TestLib.cpp

#include "stdafx.h"
#include "string.h" // memcpy
#include "TestLib.h"

void ReturnStruct(databox* return_databox)
{
    vector3 vecter_result;
    vecter_result.x = 10;
    vecter_result.y = 30;
    vecter_result.z = 60;
    databox databox_result;
    databox_result.id = 3;
    strcpy_s(databox_result.name, 10, "TestData");
    databox_result.point = vecter_result;
    memcpy(return_databox, &databox_result, sizeof(databox));
}

次に関数を呼び出す Python スクリプトを修正します。
構造体のデータ構造を Python スクリプト側で Structure の派生クラスとして定義します。
このとき、クラス内のフィールド型は ctypes 型に含まれる型である必要があります。
構造体の中に構造体を定義することも可能です。

class VECTOR3(Structure):
    _fields_ = [("x", c_int), ("y", c_int), ("z", c_int)]
class DATABOX(Structure):
    _fields_ = [("id", c_int), ("name", c_char * 10), ("point", VECTOR3)]

以下の通り、構造体を受け取って表示するスクリプトを作成しました。
・dllAccesstest05.py

# ctypesインポート
from ctypes import *

# クラスを定義する
class VECTOR3(Structure):
    _fields_ = [("x", c_int), ("y", c_int), ("z", c_int)]
class DATABOX(Structure):
    _fields_ = [("id", c_int), ("name", c_char * 10), ("point", VECTOR3)]

# DLLをロードする
dll = cdll.LoadLibrary("TestLib.dll")
# 構造体を生成する
p_databox = DATABOX()
# 引数に構造体を渡して関数を実行する
dll.ReturnStruct(byref(p_databox))
# 収納された値を表示する
print("ReturnStruct Execute")
print(p_databox.id)
print(p_databox.name)
print(p_databox.point.x)
print(p_databox.point.y)
print(p_databox.point.z)

実行すると、構造体に代入された値が取得できました。
f:id:bluebirdofoz:20191119090023j:plain

PythonのctypesでDLLの関数から様々な戻り値を受け取る その2(参照渡し)

本日は Python の技術調査枠です。
Python の ctypes で DLL の関数から様々な戻り値を受け取る方法について記事にします。
f:id:bluebirdofoz:20191118093316j:plain

今回は参照渡しを利用して、値を受け取ってみます。
前回記事の続きです。
bluebirdofoz.hatenablog.com

参照渡しを利用する

関数で参照渡しを利用して、Python スクリプトに値を受け取る例を試してみます。
最初に int 型の参照を受け取って値を代入する関数を用意します。
前回のプロジェクトを修正し、DLL に int* 型を受け取る関数を追加します。
・TestLib.h

#pragma once

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

TESTLIB_API void ReturnInt(int*);

・TestLib.cpp

void ReturnInt(int* pointer_int)
{
    *pointer_int = 11;
}

次に関数を呼び出す Python スクリプトを修正します。
変数の参照を渡す場合は byref() を利用します。
・dllAccesstest04.py

# ctypesインポート
from ctypes import *
# DLLをロードする
dll = cdll.LoadLibrary("TestLib.dll")
# 文字列受け取り用の int 型を作成する
ret_int = c_int()
# 参照を引数に渡して実行する
dll.ReturnInt(byref(ret_int))
# 収納された値を表示する
print("ReturnInt Execute")
print(ret_int)
print(ret_int.value)

実行すると、ret_int の値に 11 が設定されたことが確認できました。
f:id:bluebirdofoz:20191118093327j:plain

文字列を受け取る

次は参照渡しを利用して文字列を返す関数を作成してみます。
文字列を返却する場合、文字列を受け取るバイト列先頭の参照を関数に引き渡す必要があります。
前回のプロジェクトを修正し、DLL に char* 型を受け取る関数を追加します。
・TestLib.h

#pragma once

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

TESTLIB_API void ReturnString(char*, int);

・TestLib.cpp

#include "stdafx.h"
#include "string.h" // strcpy
#include "TestLib.h"

void ReturnString(char* return_pchar, int length_int)
{
    strcpy_s(return_pchar, length_int, "resultStr");
    return;
}

次に関数を呼び出す Python スクリプトを修正します。
文字列を収納するバッファは create_string_buffer を使って作成します。
・dllAccesstest04.py

# ctypesインポート
from ctypes import *
# DLLをロードする
dll = cdll.LoadLibrary("TestLib.dll")
# 文字列受け取り用のバッファを作成する
buffer_length = 10
uni_buffer = create_string_buffer(buffer_length)
# バッファを引数に渡して実行する
dll.ReturnString(uni_buffer, buffer_length)
# 収納された値を表示する
print("ReturnString Execute")
print(type(uni_buffer))
print(uni_buffer.value)

実行すると、戻り値の"resultStr"の文字列が確認できました。
f:id:bluebirdofoz:20191118093337j:plain