本ページはプロモーションが含まれています

PythonからC言語関数を使う「ctypes」

「ctypesモジュール」は、C言語のライブラリの関数をPythonから呼び出すことを可能にします。LinuxなどUnixは共有ライブラリの.soファイル、Windowsはダイナミックリンクライブラリの.DLLファイルを読み込み、ライブラリの関数を実行します。
C言語のライブラリを利用できれば、ネイティブコードによる高速な処理が期待できます。また、蓄積された多くのC言語実装の資源を利用できるようになります。

標準Cライブラリ関数を呼び出す

ctypesは、C言語関数への引数と戻り値をPythonのデータで受け渡しするために、C言語のデータ構造へ適合させるデータ型のクラスを用意しています。またライブラリのロードで生成されるオブジェクトから、C言語の関数名と同じメソッドとして呼び出すことができます。
標準のC言語ライブラリ(libc)のrand()とprintf()を試してみます。
import ctypes

libc = ctypes.CDLL("libc.so.6")
print(libc.rand())
libc.printf(ctypes.c_char_p(b"Hello World\n"))
1804289383
Hello World
このように、Cのライブラリ関数が実行されました。上の実装を順番に見てみます。
まず、ctypesモジュールをインポートします。
import ctypes
ctypes.CDLL()は、指定のライブラリファイルをロードし、ライブラリにアクセスするCDLLクラスのインスタンスを返します。libc.so.6をロードしたインスタンスをlibcに代入します。
libc = ctypes.CDLL("libc.so.6")
ライブラリの検索は、その環境に依存します。例えばLinuxならば/etc/ld.so.confや環境変数などに従います。
また、上のctypes.CDLL()は次のようにしても同じことができます。
libc = ctypes.cdll.LoadLibrary("libc.so.6")
CDLLオブジェクト = ctypes.CDLL(共有ライブラリ名)
CDLLオブジェクト = ctypes.cdll.LoadLibrary(共有ライブラリ名)
ライブラリロードで得られたCDLLオブジェクトをlibcへ代入すると「libc.C関数名()」のようにライブラリ関数を呼び出すことができます。この例ではrand()とprintf()を呼び出しています。
print(libc.rand())
libc.printf(ctypes.c_char_p(b"Hello World\n"))
CDLLオブジェクト.C言語関数(引数)
printfの引数(char*)には、"Hello World"のバイト列をc_char_pで変換したデータを与えています。c_char_p()は、バイト列をC言語のchar*に変換します。
c_char_pはC言語互換のデータ型クラス(ctypes型)のひとつです。他にも整数や浮動小数点、配列などが用意されています。
C言語関数のデータの受け渡しには、PythonのデータをC言語のデータ構造に変換する作業が必要になります。
主なctypesのC言語のデータ型互換クラスは以下があります。
C言語互換クラス(ctypes型) C言語型 Python型
c_bool _Bool ブール
c_char char 1バイト
c_wchar wchar_t 1文字
c_byte, c_ubyte char, unsigned char 整数
c_int, c_uint int, unsigned int 整数
c_short, c_ushort short, unsigned short 整数
c_long, c_ulong long, unsigned long 整数
c_float, c_double float, double 浮動小数点数
c_char_p char*, NULL* バイト列, None
c_wchar_p wchar_t*, NULL* 文字列, None
c_void_p void* 整数, None
この中で、整数・バイト列・Noneは特別に変換が不要です。整数はint、バイト列はchar*、NoneはNULL*として直接解釈されます。上の例のb"Hello World\n"は、実際はc_char_pを使わずに、そのまま与えても問題ありません。
更に詳しいctypes型については公式ドキュメントを参照ください。

C言語ライブラリを作ってPythonから呼び出す

C言語ライブラリは、自分で作ることができます。C言語ライブラリを作成してPythonから呼び出してみます。
次のような簡単な関数を共有ライブラリにします。引数で与えられた文字列を標準出力に出力し、その文字数を戻り値に返します。
testlib.c
#include <stdio.h>
#include <string.h>

int cfunc_msg(char *msg)
{
    puts(msg);
    return strlen(msg);
}
-sharedオプションでコンパイルして共有ライブラリ「testlib.so」を作成します。
-fPICは位置独立で生成するオプションで必須ではありませんが、つけた方がロード時の効率がよくなります。
gcc -shared -fPIC -o testlib.so testlib.c
共有ライブラリ(soファイル)はシステムで設定されるライブラリ検索パスに配置しますが、ここでは簡単にPythonの実行パスと同じ場所(カレントディレクトリ)に置くことにします。
Pythonでは、ctypesでカレントディレクトリのtestlib.soをロードして、CDLLインスタンスを取得します。CDLLインスタンスからC言語関数をメソッドのように呼び出します。このとき、前項のprintfと同様、バイト列をctypes.c_char_p()でchar*型へ変換して引数に与えます。戻り値はintで帰ります。これは前項でも少し触れましたが整数はC言語・Python間で変換不要なので、そのままPython変数に代入できます。
import ctypes

c = ctypes.CDLL("./testlib.so")
r = c.cfunc_msg(ctypes.c_char_p(b"Hello so"))
print(r)
Hello so
8
自作したC言語の関数をPythonから呼び出すことができました。




C言語関数への引数渡し

基本データ型の引数渡し

ctypesによるC言語関数呼び出しは、引数と戻り値をC言語のデータ型に合わせる作業が主になります。前項は文字列型(char*)の引数の例でしたが、他のデータ型について試してみます。
整数・浮動小数点型の引数
整数型と浮動小数点型の引数を渡す方法を試します。整数型は互換性があるため変換不要ですが、ここではあえて明示的に行います。
intとlongの引数を取るcfunc_arg_int()と、doubleを引数に取るcfunc_arg_float()のC言語ライブラリを作成します。
testlib.c
#include <stdio.h>

void cfunc_arg_int(int i, long li)
{
    printf("%d\n", i);
    printf("%ld\n", li);
}

void cfunc_arg_float(double f)
{
    printf("%f\n", f);
}
これらをPythonから次のように呼び出します。
import ctypes

cf = ctypes.CDLL("./testlib.so")
cf.cfunc_arg_int(ctypes.c_int(100), ctypes.c_long(100000000))
cf.cfunc_arg_float(ctypes.c_double(1.2345))
100
100000000
1.234500
引数はそれぞれのctypesのデータ型に変換して、C言語関数に正しい値が渡されました。
c_long()やc_double()は、データの参照型を変える型キャストのようなものではなく、c_longクラスやc_doubleクラスをコンストラクタで改めて生成していることに注意してください。
文字列の引数
バイト列はctypesの文字列と互換なのでctypesのデータ型に変換せずに引数に渡すことができます。最初の例の"Hello World"は全ての文字がASCIIコードなので、c_char_p()で変換しなくてもバイト列のまま与えても問題ありません。
C言語の文字列は、char型の配列に、その文字コードが何であるかに関係なく1バイトの列として格納されます。Pythonの文字列はUnicodeで格納されているので、プラットフォームに応じた文字コードのバイト列に変換する必要があります。変換したバイト列は、そのままC言語関数の引数に渡すことができます。あるいは、c_char_p()で明示的にchar*型に変換して与えてもかまいません。
char*、すなわち文字列を引数に取るcfunc_str()のC言語ライブラリを作成します。
testlib.c
#include <stdio.h>

void cfunc_str(char *s)
{
    puts(s);
}
Pythonから次のように呼び出します。文字列をUTF-8のエンコードしてバイト列を生成し、c_char_p()でchar*型に変換して引数に与えます。
import ctypes

cf = ctypes.CDLL("./testlib.so")
u8s = "string 文字列".encode('UTF-8')
cf.cfunc_str(ctypes.c_char_p(u8s))      # あるいはu8sのまま与えてもよい
string 文字列
C言語関数から標準出力に文字列が表示されました。
この場合のプラットフォームのテキストはUTF-8です。Windowsの場合はsjisやcp932を指定することになるでしょう。

配列の引数

PythonではC言語のようなデータ型サイズ✕要素数の連続したメモリブロックを確保するような配列型はありません。ctypesで配列を作るには、次のステップを行います。
1. 要素型✕要素数のctypesの配列クラスを生成する
    ctypes型 * 要素数 ⇒ 配列クラス

2. 生成した配列クラスのコンストラクタでインスタンスを生成する
    インスタンス = 配列クラス(要素, 要素, ...)

3. インスタンスをC言語関数の引数に与える
    cdll.func(インスタンス)
任意のデータ型の配列型の「クラス」を一旦作り、そのクラスのインスタンスで実体のある配列データを作ります。
次のような、int型配列を引数に取るcfunc_arr()のC言語ライブラリを作成します。
testlib.c
#include <stdio.h>

void cfunc_arr(int ar[])
{
    int i;
    for (i = 0; ar[i] != 0; i++)
        printf("(%d) ", ar[i]);
}
Pythonでは、最初にint型(ctype.c_int)の長さ5の配列のクラスを作成します。「ctypes.c_int * 5」は「int型でサイズ5の配列」というクラス定義を返します(この場合はc_int_Array_5というクラスになります)。
クラス定義を代入したintarrtypeはintarrtype()のようにコンストラクタを呼び出すことができます。このコンストラクタの引数には、配列要素の整数値を与えます。intarrtype()が生成するインスタンスをcfunc_arr()の引数に渡して呼び出します。
import ctypes

cf = ctypes.CDLL("./testlib.so")
intarrtype = ctypes.c_int * 5
cf.cfunc_arr(intarrtype(1, 2, 3, 4, 0))
(1) (2) (3) (4)
この結果により、C言語のcfunc_arr()に配列が渡ったことがわかります。
次のcfunc_intp()は、cfunc_arr()と同じですが、引数が[]ではなくポインタになっています。
testlib.c
#include <stdio.h>

void cfunc_intp(int *ar)
{
    int i;
    for (i = 0; ar[i] != 0; i++)
        printf("(%d) ", ar[i]);
}
C言語としてはcfunc_arr()と等価なのでPython側は上のままでも同じ結果が得られますが、正確にポインタ型として渡すならば、以下のようにctypes.pointer()でポインタ型に変換します。
ctypes.pointer(x)    xのポインタ型インスタンス
import ctypes

cf = ctypes.CDLL("./testlib.so")
intarrtype = ctypes.c_int * 5
cf.cfunc_intp(ctypes.pointer(intarrtype(10, 20, 30, 40, 0)))
(10) (20) (30) (40)
多次元配列の引数を渡す
多次元配列のC言語関数の引数に、ctypesの多次元配列を作成して渡す方法を考えます。
cfunc_darr()は「int [][]」のようにint型の二次元配列の引数を取ります。
testlib.c
void cfunc_darr(int ar[2][5])
{
    int i, j;
    for (i = 0; i < 2; i++) {
        for (j = 0; ar[i][j] != 0; j++)
            printf("(%d:%d) ", i, ar[i][j]);
        putchar('\n');
    }
}
一次元目は、上の例と同じ方法でintarrtypeにint型✕5の配列クラスを作成します。
intarrtype = ctypes.c_int * 5
続いて二次元目を、intarrtypeの2要素の配列クラスを生成し、intdarrtypeに代入します。
intdarrtype = intarrtype * 2
intdarrtypeは、int型の5✕2の配列クラスになります。コンストラクタintdarrtype()は、その要素をintarrtype()コンストラクタのインスタンスで初期化します。
import ctypes

cf = ctypes.CDLL("./testlib.so")
intarrtype = ctypes.c_int * 5
intdarrtype = intarrtype * 2
cf.cfunc_darr(intdarrtype(intarrtype(1, 2, 3, 4, 0), intarrtype(10, 20, 30, 0)))
(0:1) (0:2) (0:3) (0:4)
(1:10) (1:20) (1:30)
この結果により、C言語のcfunc_darr()に多次元配列が渡ったことがわかります。
多次元配列はポインタの配列のような実装になっていることがあります。cfunc_intparr()はcfunc_darr()と似ていますが、引数が「int *[]」のようにint型ポインタの配列になっています。
testlib.c
void cfunc_intparr(int* ar[])
{
    int i, j;
    for (i = 0; ar[i] != NULL; i++) {
        for (j = 0; ar[i][j] != 0; j++)
            printf("(%d:%d) ", i, ar[i][j]);
        putchar('\n');
    }
}
最初にint*の部分は、上の例と同じ方法でintarrtypeにint型✕5の配列クラスを作成します。
一次元目の配列クラスのintarrtypeをポインタ型に変換したクラスで、二次元目の配列クラスを作成しています。このとき、「intarrtypeのポインタ型」を
ctypes.POINTER(intarrtype) * 3
のように作成します。このときのctypes.POINTER()は「ポインタ型クラス」を生成します。
ctypes.POINTER(X)   Xクラスのポインタ型クラスを返す
ctypes.pointer()はポインタ型のインスタンスを返すのに対し、ctypes.POINTER()はポインタ型のクラス定義を返します。
intparrtypeは、int型の5配列×ポインタの3配列となります。コンストラクタintparrtype()は、その要素をintarrtype()で得たインスタンスをさらにポインタにしたもので初期化します。
配列のデリミタの役割にNoneを指定していますが、これはctypes型でNULLと互換です。
import ctypes

cf = ctypes.CDLL("./testlib.so")
intarrtype = ctypes.c_int * 5
intparrtype = ctypes.POINTER(intarrtype) * 3
cf.cfunc_intparr(intparrtype(
    ctypes.pointer(intarrtype(1, 2, 3, 4, 0)),
    ctypes.pointer(intarrtype(10, 20, 30, 0)), None))
(0:1) (0:2) (0:3) (0:4)
(1:10) (1:20) (1:30)

構造体の引数

C言語の構造体データをPythonとやりとりするには、ctypesのC言語互換型とメンバ名の一覧からC言語の構造体互換のctypes型のクラスを作成し、そのクラスのコンストラクタによりC構造体のインスタンスを生成します。
具体的には、ctypes.Structureクラスを継承するクラスを定義します。そのクラスの_fields_フィールドに、構造体メンバのそれぞれのメンバ名とctypes型のペアのタプルからなるリストで初期化します。
class 構造体のクラス(ctypes.Structure):
    _fields_ = [
        (メンバ1, メンバ1のctypes型),
        (メンバ2, メンバ1のctypes型),
        ...
        ]
「構造体のクラス」のコンストラクタの引数に、各メンバのctypes型データを与え、インスタンスを生成します。
st = 構造体のクラス(メンバ1, メンバ2, ...)
このインスタンスはC言語関数に引き渡すことができますが、多くの場合C言語関数の構造体引数はポインタになっていることがほとんどです。生成したインスタンスは、ctypes.pointer()でポインタに変換することになると思います。
具体的な例を試します。次のような、Personという構造体のポインタを引数に取るcfunc_struct()のC言語ライブラリを作成します。
testlib.c
#include <stdio.h>

struct Person {
    char *name;
    int age;
};

void cfunc_struct(struct Person *psn)
{
    printf("%s (%d)\n", psn->name, psn->age);
}
Pythonは、まずPerson構造体を作るクラスをPersonクラスに定義します。
Personクラスはctypes.Structureを継承します。
_fields_フィールドを、メンバのnameをchar*に対応するc_char_p型で、ageをint型に対応するc_int型で組み合わせたタプルのリストで初期化します。
Personクラスのコンストラクタにnameとageの値を与えてインスタンスを生成します。cfunc_struct()のポインタ引数に合わせるために、そのインスタンスをctypes.pointer()でポインタ型に変換したものを渡します。
import ctypes

cf = ctypes.CDLL("./testlib.so")

class Person(ctypes.Structure):
    _fields_ = [("name", ctypes.c_char_p), ("age", ctypes.c_int)]

psn = Person("田中".encode("UTF-8"), 40)
cf.cfunc_struct(ctypes.pointer(psn))
田中 (40)
この結果により、構造体引数がC言語に渡ったことがわかります。
もう少し複雑な構造体を作ってみます。上のPerson構造体の配列をメンバとするPersons構造体を定義し、Persons構造体のポインタを引数に取る、cfunc_struct2()のC言語ライブラリを作成します。
testlib.c
struct Person {
    char *name;
    int age;
};

struct Persons {
    struct Person psn[3];
    int num;
};

void cfunc_struct2(struct Persons *psns)
{
    int i;
    for (i = 0; i < psns->num; i++)
        printf("%d: %s (%d)\n", i, psns->psn[i].name, psns->psn[i].age);
}
Person構造体と互換のPersonクラスの定義は上の例と同様です。Personsクラスは、Personクラスの掛け算で配列クラスを作ります。
psn3 = Person * 3
psn3はpsn[3]に対応するctypes型クラスになります。これをPersons構造体に対応するPersonsクラスのpsnメンバの型として設定します。
class Persons(ctypes.Structure):
    _fields_ = [("psn", psn3), ("num", ctypes.c_int)]
Personsクラスのコンストラクタにpsn[3]とnumに相当する引数を与え、Personsクラスのインスタンス「psns」を生成します。psn[3]は、Personクラスのコンストラクタでnameとageのペアを作ってpsn3型コンストラクタで生成します。numは整数で与えます。出来上がったPersonsクラスのインスタンスpsnsを、ctypes.pointer()でポインタに変換してC言語の関数cfunc_struct2()へ渡します。
import ctypes

cf = ctypes.CDLL("./testlib.so")

class Person(ctypes.Structure):
    _fields_ = [("name", ctypes.c_char_p), ("age", ctypes.c_int)]

psn3 = Person * 3
class Persons(ctypes.Structure):
    _fields_ = [("psn", psn3), ("num", ctypes.c_int)]

psns = Persons(
    psn3(
        Person("田中".encode(), 48), Person("山根".encode(), 47), Person(None, 0)
    ), 2)
cf.cfunc_struct2(ctypes.pointer(psns))
0: 田中 (48)
1: 山根 (47)
この結果により、Persons構造体がC言語に渡ったことがわかります。

ポインタの引数と引数戻り値

C言語関数は、参照渡しの引数に引数戻り値を返すことができますが、ctypesで渡す引数が、ポインタによる参照渡しになっている場合、C言語と同様にPythonで引数戻り値を受け取ることができます。
次のような、int型とdouble型を参照渡しで受け取るcfunc_ref()のC言語ライブラリを作成します。
testlib.c
void cfunc_ref(int *x, double *f)
{
    *x = 100;
    *f = 1.2345;
}
cfunc_ref()は、引数の参照に値を代入して引数戻り値を返します。
Python側は、c_intとc_doubleをctypes.pointer()でポインタ変換したものをcfunc_ref()の引数に与えて呼び出します。
import ctypes

cf = ctypes.CDLL("./testlib.so")
i = ctypes.c_int()
f = ctypes.c_double()
cf.cfunc_ref(ctypes.pointer(i), ctypes.pointer(f))
print(i, f)
print(i.value, f.value)
c_int(100) c_double(1.2345)
100 1.2345
結果は、ポインタ変換前のc_intのi、c_doubleのfに引数戻り値が得られます。この場合のiとfはctypesの型なので、そのままではPythonのデータ型としては参照できません。、それらの「valueフィールド」で参照することで、Pythonのデータ型で引き出すことができます。
配列のポインタによる参照渡しで、配列の引数戻り値を返すことがあります。次のような、int型配列を参照渡しで受け取るcfunc_ref_arr()のC言語ライブラリを作成します。
testlib.c
void cfunc_ref_arr(int ar[])
{
    ar[0] = 10;
    ar[1] = 20;
    ar[2] = 30;
}
Pythonでは、c_intの配列クラスのインスタンスで、cfunc_ref_arr()に渡します。配列クラスを引数に渡す場合は暗黙にポインタとして渡されます。
import ctypes

cf = ctypes.CDLL("./testlib.so")
arr = ctypes.c_int * 3
ia = arr()
cf.cfunc_ref_arr(ia)
print(ia[0], ia[1], ia[2])
10 20 30
結果は、配列クラスのインスタンスの各添字に、cfunc_ref_arr()の引数戻り値が返されます。
C言語の文字列は1バイトの配列です。文字列の引数戻り値は、多くの場合引数はchar*で定義されます。次のような、文字列の引数戻り値を返すcfunc_ref_str()のC言語ライブラリを作成します。
testlib.c
#include <string.h>

void cfunc_ref_str(char *s)
{
    strcpy(s, "TANAKA");
}
文字列をC言語関数に渡す場合は、バイト列を直接かもしくはc_char_p()でchar*互換型に変換して渡しますが、引数戻り値として文字列がコピーされて返る場合はそれはできません。なぜなら、バイト列やc_char_p()のインスタンスは変更不可(イミュータブル)だからです。
そのため、文字列が返るためのバッファを備えた実引数を与える必要があります。ctypes.create_string_buffer()は、指定するバイト数の(バイト列のような)書き込み可能なバッファを生成します。
文字列の引数戻り値を受け取る領域として、ctypes.create_string_buffer()で確保した10バイトのバッファをcfunc_ref_str()に与えます。
import ctypes

cf = ctypes.CDLL("./testlib.so")
s = ctypes.create_string_buffer(10)
cf.cfunc_ref_str(s)
print(s.value)
b'TANAKA'
結果は、C言語関数からの文字列の引数戻り値を受け取ることができました。create_string_buffer()が生成するクラスのvalueフィールドを参照してバイト列を取り出します。
ctypes.create_string_buffer()はバイトバッファを作る便利なメソッドですが、手間をかけてctypes互換のchar型配列を作成して渡しても同じ結果が得られます。
s = ctypes.c_char * 10
sbuf = s()
cf.cfunc_ref_str(ctypes.pointer(sbuf))
print(sbuf.value)
C言語の関数が構造体で処理結果を返す場合は、ほとんどは構造体の引数戻り値で返します。
次のようなPersonという構造体のポインタを取り、構造体メンバに値を代入して引数戻り値として返すcfunc_ref_struct()のC言語ライブラリを作成します。
testlib.c
#include <string.h>

struct Person {
    char name[10];
    int age;
};

void cfunc_ref_struct(struct Person *psn)
{
    strcpy(psn->name, "YAMANE");
    psn->age = 47;
}
構造体のctypes互換クラスはctypes.Structureを継承して作成しますが、文字列メンバには、引数戻り値を得るためのバッファを確保するためにc_charの配列を作成しています。
Personクラスのコンストラクタでインスタンスpsnを生成し、そのポインタをctypes.pointer()で取得してcfunc_ref_struct()へ渡します。psnは引数戻り値を受けるためのものなので、Personコンストラクタには初期値を指定しません。
import ctypes

cf = ctypes.CDLL("./testlib.so")

class Person(ctypes.Structure):
    _fields_ = [("name", ctypes.c_char * 10), ("age", ctypes.c_int)]

psn = Person()
cf.cfunc_ref_struct(ctypes.pointer(psn))
print(psn.name, psn.age)
b'YAMANE' 47
結果は、Personクラスのインスタンスに、各メンバの引数戻り値を得ることができました。
上の例は、C言語の構造体Personの戻り値が10バイトの配列として定義されていたので、それに合わせてPython側もc_charの10バイト配列で定義しました。
このような構造体の文字列メンバは、配列バッファではなくchar*とされていて、ポインタが示すバッファへ文字列をコピーして返すことがあります。
次のような、Person2構造体のポインタを引数に取るcfunc_struct2()のC言語ライブラリを作成します。Person2構造体のnameメンバは配列ではなくポインタになってます。
testlib.c
#include <string.h>

struct Person2 {
    char *name;
    int age;
};

void cfunc_ref_struct2(struct Person2 *psn)
{
    strcpy(psn->name, "YAMANE");
    psn->age = 47;
}
Person2構造体を作るPerson2クラスの、nameフィールドの型はc_char_pとします。そしてPerson2クラスのコンストラクタからインスタンスpsnを生成します。
psnのnameフィールドにcreate_string_buffer()でバッファを確保して代入します。このとき、create_string_buffer()で返るバッファのクラスをnameのc_char_p型に合わせるために、ctypes.cast()でキャストします。これをcfunc_ref_struct2()へ渡します。
class Person2(ctypes.Structure):
    _fields_ = [("name", ctypes.c_char_p), ("age", ctypes.c_int)]

psn = Person2()
psn.name = ctypes.cast(ctypes.create_string_buffer(10), ctypes.c_char_p)
cf.cfunc_ref_struct2(ctypes.pointer(psn))
print(psn.name, psn.age)
b'YAMANE' 47
結果は、Person2クラスのインスタンスに、各メンバの引数戻り値を得ることができました。Person2クラスのnameが指す、create_string_buffer()で確保したバイト列のバッファに、文字列が返っていることがわかります。

C言語関数からの戻り値を得る

int以外の数値が返る場合

ctypesでは、C言語からの戻り値はデフォルトでint型としています。Pythonの整数はctypes型互換なので、C言語呼び出しの戻り値としてそのままPythonの変数で受け取ることができます。
C言語関数がint型以外の型を返す場合は、CDLL関数クラスのrestypeフィールドにctypes型を設定してから呼び出す必要があります。
cf = ctypes.CDLL("xxxx.so")
cf.関数名.restype= ctypes.型
戻り値をdouble型で返すcfunc_ret_d()のC言語ライブラリを作成します。
testlib.c
double cfunc_ret_d()
{
    return 3.14;
}
cfunc_ret_d.restypeにctypes.c_doubleを代入し、戻り値がdouble型であることを指定します。
import ctypes

cf = ctypes.CDLL("./testlib.so")
cf.cfunc_ret_d.restype= ctypes.c_double
r = cf.cfunc_ret_d()
print(r)
3.14
cfunc_ret_d()のdouble型の戻り値を浮動小数点で受け取れたことがわかります。
続いて、戻り値をchar型で返すcfunc_ret_c()のC言語ライブラリを作成します。
testlib.c
char cfunc_ret_c()
{
    return 'A';
}
同様にcfunc_ret_c.restypeにc_char型を指定しています。
import ctypes

cf = ctypes.CDLL("./testlib.so")
cf.cfunc_ret_c.restype= ctypes.c_char
r = cf.cfunc_ret_c()
print(r)
b'A'
C言語のchar型の戻り値は、1バイトのバイト列で得られます。C言語でのchar型1バイトの数値の扱いですが、Pythonでは整数ではなくバイト列になります。

文字列のポインタが返る

戻り値を文字列のポインタ、すなわちchar*型で返すcfunc_ret_cp()のC言語ライブラリを作成します。返す文字列はstaticとして永続的なデータとして宣言します。
testlib.c
char *cfunc_ret_cp()
{
    static char *s = "ABCDE";
    return s;
}
同様にcfunc_ret_cp.restypeにc_char_p型を指定しています。
import ctypes

cf = ctypes.CDLL("./testlib.so")
cf.cfunc_ret_cp.restype= ctypes.c_char_p
r = cf.cfunc_ret_cp()
print(r)
b'ABCDE'
結果は、C言語関数の永続的な文字列のポインタの参照が帰り、ポインタから文字列を参照することができます。

構造体のポインタが返る

戻り値を構造体のポインタで返すcfunc_ret_st()のC言語ライブラリを作成します。返す構造体は永続的なグローバル変数データとして宣言します。
testlib.c
#include <string.h>

struct Person {
    char name[10];
    int age;
} psn;

struct Person *cfunc_ret_st()
{
    strcpy(psn.name, "YAMANE");
    psn.age = 47;
    return &psn;
}
この場合は構造体なので、ctypes.Structureを継承したPersonクラスを定義して、そのポインタをcfunc_ret_st.restypeに戻り値型として指定します。
Personクラスのインスタンスのポインタではなく、Personクラスのポインタ型を指定するので、ctypes.POINTER()でPersonクラスのポインタ型クラスを生成して戻り値型として与えます。
import ctypes

cf = ctypes.CDLL("./testlib.so")
class Person(ctypes.Structure):
    _fields_ = [("name", ctypes.c_char * 10), ("age", ctypes.c_int)]

psn = Person()
cf.cfunc_ret_st.restype = ctypes.POINTER(Person)
r = cf.cfunc_ret_st()
psn = r.contents        # ポインタが指すオブジェクト取得
print(psn.name, psn.age)
b'YAMANE' 47
結果は、C言語関数の構造体のポインタの参照が帰り、メンバにアクセスできます。ポインタ型クラスの戻り値は、Personクラスのインスタンスを参照しています。インスタンスにアクセスするにはcontentsフィールドを参照します。

引数の型

型チェック

ctypesのC言語関数呼び出し時の引数の型は、Pythonから関数定義にあわせたctypes型を与えますが、このとき、ある程度の型の不一致を適合させるように自動的な調整が入ることがあります。C言語関数の引数型とは異なる意図しないものが、エラーにならずに渡る可能性があります。
C/C++のプロトタイプ宣言が引数の型を照合するように、Pythonから渡す引数のctypes型を宣言し、C言語関数呼び出し時に型チェックさせることができます。
次のような、int型とdouble型を参照渡しで受け取るcfunc_argty_i_d()のC言語ライブラリを作成します。
testlib.c
#include <stdio.h>

void cfunc_argty_i_d(int a, double d)
{
    printf("%d %f\n", a, d);
}
Pythonがcfunc_argty_i_d()を呼び出すときのctypes型(クラス)を、argtypesフィールドにタプルで指定します。
関数.argtypes  = (引数1クラス, 引数2クラス, ...)
ここで、cfunc_argty_i_d()の引数定義のint、doubleの順をわざと間違ってdouble、intの順で呼び出します。
import ctypes

cf = ctypes.CDLL("./testlib.so")

cf.cfunc_argty_i_d.argtypes = (ctypes.c_int, ctypes.c_double)
cf.cfunc_argty_i_d(ctypes.c_double(3.14), ctypes.c_int(10))
ctypes.ArgumentError: argument 1: TypeError: wrong type
この結果は、TypeError例外のエラーになりました。これは期待通りに引数型の誤りが検出されたことになります。
ところでこの例の場合、argty_i_d.argtypesを削除しても、なぜかエラーにはならず結果が得られます。
10 3.140000
これは誤りが自動的に修正されたからです。ctypesの引数渡しは、C言語関数の引数に適合するように暗黙の調整が入ることがありますが、誤りに気付きにくくなります。
次のような、int型ポインタで定義したint型配列を引数に取る、cfunc_argty_iarr()のC言語ライブラリを作成します。int型ポインタの引数は、int型の数値ではなく配列であることを期待しています。
testlib.c
#include <stdio.h>
void cfunc_argty_iarr(int *arr)
{
    printf("%d %d %d\n", arr[0], arr[1], arr[2]);
}
argtypesフィールドには、それに従いctypesのint型ポインタを設定します。そして、c_intの配列クラスのintarrtypeから配列を生成し、そのままC言語関数の引数に与えて呼び出します。
import ctypes

cf = ctypes.CDLL("./testlib.so")
cf.cfunc_argty_iarr.argtypes = (ctypes.POINTER(ctypes.c_int), )
intarrtype = ctypes.c_int * 3;
arr = intarrtype(10, 20, 30)
print(type(arr))
cf.cfunc_argty_iarr(arr)
argtypesにはc_intのポインタ型を設定したにもかかわらず、実際はc_intの配列クラスを与えているので型チェックエラーになりそうですが、正常に呼び出されます。ctypesは配列クラスを、C言語の配列変数を先頭ポインタとして解釈することと同じような解釈をするため、この場合は型チェックエラーにはなりません。
<class '__main__.LP_c_int_Array_3'>
10 20 30

キャストの利用

argtypesフィールドによる厳密な引数型チェックをする場合、自動的な調整に頼らずに、厳格にC言語の互換型を与える必要があるときのために、ctypesにはC言語の型キャストに相当するcastメソッドがあります。
ctypes.cast(インスタンス, 変換したい型)
次のような、unsigned shortポインタを引数に取るcfunc_cast_p_si()のC言語ライブラリを作成します。
testlib.c
#include <stdio.h>

void cfunc_cast_p_si(unsigned short *psi)
{
    unsigned short sp = *psi; 
    printf("%04x\n", sp);
}
argtypesフィールドにはc_shortのポインタを指定します。引数には2バイトのバイト列の配列クラスのインスタンスを指定しますが、このときバイト列の配列をc_shortのポインタにキャストします。
import ctypes

cf = ctypes.CDLL("./testlib.so")

cf.cfunc_cast_p_si.argtypes = (ctypes.POINTER(ctypes.c_short),)
shortbarr = ctypes.c_byte * 2;
si = shortbarr(0x22, 0xaa)
r = cf.cfunc_cast_p_si(ctypes.cast(si, ctypes.POINTER(ctypes.c_short)))
aa22
この結果は、正常にC言語関数を呼び出すことができました。この場合は、argtypesの指定がなければキャストなしても正常に動作しますが、そうでない場合は厳格にキャストしなければ「TypeError」の例外となります。

ctypesのC言語データ型のアクセス

PythonのデータをC言語と互換型に適合させるctypesのデータは、そのままではPythonの中では使うことができません。例えば、ctypes.c_intは整数ですが、Pythonの整数ではないので演算などはできません。
C言語関数に渡す引数はctypes型に変換したものを渡せばよいですが、戻り値をPython側で利用する場合は、逆にctypes型からPythonデータを引き出す必要があります。
これまでの戻り値の説明にも登場しましたが、ctypesデータが格納するPythonのデータを参照する方法があります。
ctypesインスタンス.value ctypes型インスタンスに格納されているPythonのデータ
ctypesインスタンス.contents ctypes型ポインタが指している実体データのctypes型インスタンス
ctypesインスタンス.contents.value ctypes型ポインタが指している実体データのctypes型インスタンスに格納されるPythonのデータ
次の例では、ctypes.c_int()のC言語のint型互換のiの中に格納されるPythonの整数値を参照しています。c_int(20)は、あくまでctypesの型で、中身の20はvalue属性で参照します。
i = ctypes.c_int()
i.value = 20
print(i, type(i.value), i.value)
c_int(20) <class 'int'> 20
次の例は、char配列、すなわちcharポインタ型のctypesインスタンスのbpから、bpそのもの、ポインタであるbpが指すオブジェクト、そのオブジェクトのPythonが参照できる内容、バイト列としての参照、の順に確認しています。
barr = ctypes.c_char * 3
bp = ctypes.pointer(barr(b'A', b'B', b'C'))
print(bp)
print(bp.contents)
print(bp.contents.value)
print(bp.contents[0])
<__main__.LP_c_char_Array_3 object at 0x7fd6225ac0c0>
<__main__.c_char_Array_3 object at 0x7fd6225ac140>
b'ABC'
b'A'