Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a 2d array from Python to C?

I'm trying to pass a 2d array from Python to C, using ctypes. The array dtype is uint16. I wrote a simple code just to understand how it works:

C:

#include <stdint.h>

__declspec(dllexport) uint16_t Test(uint16_t **arr)
{
     return (arr[5][5]);
}

Python:

import numpy as np
from ctypes import cdll
import ctypes
from numpy.ctypeslib import ndpointer

_p = ndpointer(dtype=np.uint16, ndim=2, shape=(10, 10), flags='C')
mydll = cdll.LoadLibrary("mydll.dll")
_func = mydll.Test
_func.argtypes = [_p]
_func.restypes = ctypes.c_uint16

data = np.empty([10, 10], dtype=np.uint16)
data[5, 5] = 5
print(_func(data))

I get OSError: access violation reading 0xFFFFFFFFFFFFFFFFFFFFFFF what am I doing wrong and how do I fix it?

like image 936
Dan Shorla Ki Avatar asked Nov 06 '19 10:11

Dan Shorla Ki


People also ask

Can you pass a 2D array in C?

A 2-D array can be easily passed as a parameter to a function in C. A program that demonstrates this when both the array dimensions are specified globally is given as follows.

How does 2D array work in C?

The two-dimensional array can be defined as an array of arrays. The 2D array is organized as matrices which can be represented as the collection of rows and columns. However, 2D arrays are created to implement a relational database lookalike data structure.

Can you make 2D arrays in Python?

Python provides many ways to create 2-dimensional lists/arrays.


1 Answers

Listing [SciPy.Docs]: C-Types Foreign Function Interface (numpy.ctypeslib) (and [Python.Docs]: ctypes - A foreign function library for Python just in case).

This is a exactly like [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) (a duplicate), it just happens to also involve NumPy.
In other words, you have Undefined Behavior, as argtypes must be CTypes types (not NumPy).

Below is a modified version of the your code, that works.

dll00.c:

#include <stdint.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif


#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API uint16_t dll00Func00(uint16_t **ppArr);

#if defined(__cplusplus)
}
#endif


uint16_t dll00Func00(uint16_t **ppArr)
{
    return ppArr[5][5];
}

code00.py:

#!/usr/bin/env python

import ctypes as ct
import sys

import numpy as np


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")


def main(*argv):
    UI16Ptr = ct.POINTER(ct.c_uint16)
    UI16PtrPtr = ct.POINTER(UI16Ptr)

    dll00 = ct.CDLL(DLL_NAME)
    dll00Func00 = dll00.dll00Func00
    dll00Func00.argtypes = [UI16PtrPtr]
    dll00Func00.restype = ct.c_uint16


    dim0 = 10
    dim1 = 10
    np_arr_2d = np.empty([dim0, dim1], dtype=np.uint16)

    np_arr_2d[5][5] = 5
    print(np_arr_2d)

    # The "magic" happens in the following lines of code
    ct_arr = np.ctypeslib.as_ctypes(np_arr_2d)
    UI16PtrArr = UI16Ptr * ct_arr._length_
    ct_ptr = ct.cast(UI16PtrArr(*(ct.cast(row, UI16Ptr) for row in ct_arr)), UI16PtrPtr)
    res = dll00Func00(ct_ptr)

    print("\n{0:s} returned: {1:d}".format(dll00Func00.__name__, res))


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    print("NumPy: {0:s}\n".format(np.version.version))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q058727931]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64>nul

[prompt]> dir /b
code00.py
dll00.c

[prompt]> cl /nologo /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32

NumPy: 1.16.2

[[19760  5277   632     0 32464  5280   632     0   111   114]
 [  107    92    68   101   118    92    86    69   110   118]
 [  115    92   112   121    95    48    54    52    95    48]
 [   51    46    48    55    46    48    51    95   116   101]
 [  115   116    48    92   108   105    98    92   115   105]
 [  116   101    45   112    97     5   107    97   103   101]
 [  115    92   110   117   109   112   121    92   116   101]
 [  115   116   105   110   103    92    95   112   114   105]
 [  118    97   116   101    92   110   111   115   101   116]
 [  101   115   116   101   114    46   112   121     0     0]]

dll00Func00 returned: 5

Done.

The explanation for all those funky conversions can be found at [SO]: C++ & Python: Pass and return a 2D double pointer array from python to c++ (@CristiFati's answer) (and the referenced [SO]: Problems with passing and getting arrays for a C function using ctypes (@CristiFati's answer)).

like image 73
CristiFati Avatar answered Oct 16 '22 18:10

CristiFati