What ctypes argument type to use for variably modified multidimensional arrays

Viewed 167

Using ctypes in python I can freely pass an array to a C function using a pointer to its first element since an array decays to a pointer to its first element in C. So for the C function signature

void insert_sort(int A[], size_t length);

I can implement the following python wrapper.

import ctypes
from typing import List, Callable

def wrap_function(lib: ctypes.CDLL, name: str, restype: object, argtypes: List[object]) -> Callable:
    """Simplify wrapping ctypes functions"""
    func = lib.__getattr__(name)
    func.restype = restype
    func.argtypes = argtypes
    return func


lib = ctypes.CDLL("path/to/library.so")
insert_sort = wrap_function(lib, 'insert_sort', None, [ctypes.POINTER(ctypes.c_int), ctypes.c_size_t])

But C also allows one to have arrays of array and whats more variable sized arrays of arrays. For example the C function signature below demonstrates this.

void matrix_multiply(size_t n, int A[n][n], int B[n][n], int C[n][n]);

How would one wrap this function? To codify my question what goes in the blank below?

matrix_multiply = wrap_function(lib, 'matrix_multiply', None, [...What goes here...])

If the size of the arrays were known we could simply write

matrix_multiply = wrap_function(lib, 'matrix_multiply', None, [
    ctypes.c_size_t, ((ctypes.c_int * 4)*4), ((ctypes.c_int * 4)*4), ((ctypes.c_int * 4)*4)
])

for arrays of size 4. But what can we do when this number is unknown?

1 Answers

This answer is based on reverse engineering the ABI. I compiled this test program to assembly...

#include <stddef.h>
extern void matrix_multiply(size_t n, int A[n][n], int B[n][n], int C[n][n]);

extern int X[64][64];
extern int Y[64][64];
extern int Z[64][64];

void test(void)
{
  matrix_multiply(64, X, Y, Z);
}

... using GCC 9 on x86_64-linux, and I got this assembly language:

test:
    leaq    Z(%rip), %rcx
    leaq    Y(%rip), %rdx
    leaq    X(%rip), %rsi
    movl    $64, %edi
    jmp matrix_multiply@PLT

Based on this I conclude that a variably modified array parameter is passed as a pointer to its first element, so the appropriate ctypes declaration for matrix_multiply is

c_int_p = ctypes.POINTER(ctypes.c_int)
matrix_multiply = wrap_function(lib, 'matrix_multiply', None, [
    ctypes.c_size_t, c_int_p, c_int_p, c_int_p
])

Wrapper that takes NumPy arrays and/or memoryviews left as an exercise.

Related