mallocされた配列へのポインターをctypesで返すC関数をどのようにラップしますか?

Aug 23 2020

バイナリファイルを読み取り、動的にサイズ設定された符号なし整数の配列を返すC関数があります(サイズはバイナリファイルのメタデータに基づいています)。

//example.c
#include <stdio.h>
#include <stdlib.h>

__declspec(dllexport)unsigned int *read_data(char *filename, size_t* array_size){
  FILE *f = fopen(filename, "rb");
  fread(array_size, sizeof(size_t), 1, f);
  unsigned int *array = (unsigned int *)malloc(*array_size * sizeof(unsigned int));
  fread(array, sizeof(unsigned int), *array_size, f);
  fclose(f);

  return array;
}

この答えは、作成された配列をCからPythonに渡す正しい方法は次のようなものであると言っているようです。

# example_wrap.py
from ctypes import *
import os

os.add_dll_directory(os.getcwd())
indexer_dll = CDLL("example.dll")

def read_data(filename):
    filename = bytes(filename, 'utf-8')
    size = c_size_t()
    ptr = indexer_dll.read_data(filename, byref(size))
    return ptr[:size]

ただし、Pythonラッパーを実行するptr[:size]と、範囲外の配列にアクセスしようとしているようにコードがサイレントに失敗します。おそらくそうですが、この動的なサイズの配列を渡す正しい方法は何ですか?

回答

2 filbranden Aug 23 2020 at 02:58

いくつかの考慮事項:

まず、ctypesがC型とPython型の間で適切に変換できるように、C関数のプロトタイプを適切に設定する必要があります。

次に、sizeは実際にはctypes.c_size_tオブジェクトであるため、実際にはを使用size.valueして配列サイズの数値にアクセスする必要があります。

第三に、ptr[:size.value]実際には配列の内容をPythonリストにコピーfree()するので、もう使用しないので、割り当てられたC配列も確認する必要があります。

(おそらく、配列をPythonリストにコピーすることはここでは理想的ではありませんが、PythonでC配列を処理するのがより複雑になるため、ここでは問題ないと思います。)

これは機能するはずです:

from ctypes import *
import os

os.add_dll_directory(os.getcwd())
indexer_dll = CDLL("example.dll")
indexer_dll.read_data.argtypes = [c_char_p, POINTER(c_size_t)
indexer_dll.read_data.restype = POINTER(c_int)
libc = cdll.msvcrt

def read_data(filename):
    filename = bytes(filename, 'utf-8')
    size = c_size_t()
    ptr = indexer_dll.read_data(filename, byref(size))
    result = ptr[:size.value]
    libc.free(ptr)
    return result