ctypes pointer Problem

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
101182021
User
Beiträge: 3
Registriert: Dienstag 29. November 2022, 10:00

Guten Morgen zusammen,
ich versuche momentan eine c dll über ctype in meinem Python Skript einzubinden.
Bisher hatte ich damit leider nur mäßigen Erfolg.

Daher wende ich mich jetzt an Euch, in der Hoffnung, dass mir jemand helfen kann.

Mein Code sieht wie folgt aus:

foo.h

Code: Alles auswählen

#ifndef GUARD_FOO
#define GUARD_FOO
#define NUMB 2
#include<stdint.h>

typedef struct Type
{
    int re, im;
} Type;

uint8_t func(Type *fout[NUMB], int start, int end);

#endif // GUARD_FOO
foo.c

Code: Alles auswählen

#include<stdio.h>
#include<stdint.h>
#include"foo.h"

uint8_t func(Type *fout[NUMB], int start, int end)
{
    for(int i=0;i<NUMB;i++)
    {
        Type* ptr = fout[i];
        for(int o=start;o<end;o++)
        {
            printf("%d %d re: %d im: %d\n", i, o, (ptr+o)->re, (ptr+o)->im);
            (ptr+o)->re = o;
            (ptr+o)->im = o;
            printf("%d %d re: %d im: %d\n", i, o, (ptr+o)->re, (ptr+o)->im);
        }
    }
    return 0;
}
makefile

Code: Alles auswählen

OBJS = foo.o 
SOURCE = foo.c 
HEADER = foo.h 
OUT = foo.dll
CC = gcc
FLAGS = -g
LFLAGS = -fPIC -shared -Wall

library: $(OBJS)
	$(CC) $(LFLAGS) -o $(OUT) $(OBJS)

object: foo.c 
	$(CC) $(FLAGS) $(SOURCE)

clean: 
	rm -f $(OBJS) $(OUT)

main.py

Code: Alles auswählen

from ctypes import *
import numpy as np

class Type(Structure):
    _fields_=[("re", c_int),
              ("im", c_int)]
    def __repr__(self):
        return 're: {0} im: {1}'.format(self.re, self.im)


lib=cdll.LoadLibrary("foo.dll")
print(lib)

func=lib.func
print(func)
func.argtypes=[POINTER(Type)*2, c_int, c_int]
func.restype=c_uint8

arr_0=np.zeros(shape=(100,), dtype=Type)
arr_1=np.ones(shape=(100,), dtype=Type)

##
#
# ???
#
##

func(???. 2, 10)
Mein Problem ist folgendes:

Ich habe eine Funktion func welcher ich ein Pointer Array des Datentyp "Type" übergeben muss.
Dieses Pointer Array beinhält die Anfangsadressen meiner beiden numpy Arrays „arr_0“ und „arr_1“.

Kann mir jemand erklären wie ich das bewerkstellige?


Gruß,

Robin
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

So ganz grundsätzlich: `numpy.ctypeslib` und das `ctypes`-Attribut von Numpy-Arrays sind bekannt?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
101182021
User
Beiträge: 3
Registriert: Dienstag 29. November 2022, 10:00

Danke für deine Antwort.

`numpy.ctypeslib` war mir bis vor 10 minuten tatsächlich noch kein Begriff.

Habe mitlerweile eine Lösung gefunden.

Code: Alles auswählen

lib=cdll.LoadLibrary("foo.dll")
print(lib)

func=lib.func
print(func)
func.argtypes=[POINTER(Type)*2, c_int, c_int]
func.restype=c_uint8

arr_0=np.zeros(shape=(100,), dtype=Type)
arr_1=np.ones(shape=(100,), dtype=Type)

c_Type_p = POINTER(Type)
ptr_0=arr_0.ctypes.data_as(c_Type_p)
ptr_1=arr_1.ctypes.data_as(c_Type_p)

a=[ptr_0,ptr_1]
arr=(c_Type_p*2)(*(s for s in a))

func(arr, 2, 10)
Falls ihr noch Anmerkungen oder ein "best practice" Beispiel für mich habt, würde ich mich sehr darüber freuen.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast keine zwei Arrays, sondern ein Array mit Komplexen Einträgen re/im. Das entspricht in numpy einem Structured Array. Das ist effizienter, als extra ein ctypes-Struct zu definieren und dort nochmals alles zu kopieren.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@101182021: Sternchen-Importe sind Böse™. Auch Namen die gar nicht in `ctypes` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

Namen: Ich weiss das ist nur ein Beispiel, aber kryptische Abkürzungen und Nummerierungen sollten nicht in Namen sein. Was soll `s` beispielsweise bedeuten? Warum nicht `b`, oder `z`?

Der Generatorausdruck ist überflüssig, da sollte man gleich ``*a`` schreiben. Beziehungsweise sind auch die ganzen Zwischenergebnisse nicht wirklich notwendig.

Die `__repr__()`-Methode gibt per Konvention entweder eine Zeichenkette zurück, die man in den Quelltext kopieren könnte um ein Objekt mit dem gleichen Wert zu erzeugen *oder* etwas in ”spitze Klammern” eingefasst, also "< … >". Und man statt `format()` böten sich f-Zeichenkettenliterale an. Falls "=" anstelle von ":" okay ist, kann man das auch einfacher haben und muss den Attributnamen nicht von Hand schreiben. Also:

Code: Alles auswählen

    def __repr__(self):
        return f"{self.__class__.__name__}({self.re!r}, {self.im!r})"
    
    # oder
    
    def __repr__(self):
        return f"<re: {self.re=!r} im: {self.im!r}>"
Man könnte noch Konstanten für einen Pointer auf Type und ein Paar davon definieren. Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from ctypes import POINTER, Structure, c_int, c_uint8, cdll

import numpy as np


class Type(Structure):
    _fields_ = [("re", c_int), ("im", c_int)]

    def __repr__(self):
        return f"{self.__class__.__name__}({self.re!r}, {self.im!r})"


TYPE_P = POINTER(Type)
TYPE_P_PAIR = TYPE_P * 2

FOO_LIB = cdll.LoadLibrary("foo.dll")
func = FOO_LIB.func
func.argtypes = [TYPE_P_PAIR, c_int, c_int]
func.restype = c_uint8


def main():
    length = 100
    an_array_in_need_of_a_name = np.zeros(shape=(length,), dtype=Type)
    another_one_of_those = np.ones(shape=(length,), dtype=Type)
    result = func(
        TYPE_P_PAIR(
            an_array_in_need_of_a_name.ctypes.data_as(TYPE_P),
            another_one_of_those.ctypes.data_as(TYPE_P),
        ),
        2,
        10,
    )
    print(result)


if __name__ == "__main__":
    main()
Je nach dem wie stark man möchte das `ctypes` sich in den übrigen Python-Code rein zieht, könnte man die C-Funktionen zum Implementierungsdetail erklären (einfacher führender Unterstrich), und eine Python-Funktion definieren, die sich um's übersetzen kümmert. Also beispielsweise zwei Arrays entgegen nimmt, und eine ganze Zahl als Ergebnis liefert.

@Sirius3: Das sind zwei Arrays und beide enthalten beide Komponenten:

Code: Alles auswählen

In [598]: ts = np.ones((5,), dtype=Type)

In [599]: ts
Out[599]: 
array([(1, 1), (1, 1), (1, 1), (1, 1), (1, 1)],
      dtype={'names': ['re', 'im'], 'formats': ['<i4', '<i4'], 'offsets': [0, 4], 'itemsize': 8, 'aligned': True})
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
101182021
User
Beiträge: 3
Registriert: Dienstag 29. November 2022, 10:00

@__blackjack__: Vielen lieben dank erstmal für deine ausführliche Antwort.

Ich werde versuchen deine Verbesserungen in Zukunft auch umzusetzen.

Leider stehe ich vor einem weiteren Problem welches eigentlich auch die ganze Zeit mein Hauptproblem darstellt.
Dachte, dass sich das damit meiner Lösung von oben erledigt hätte.
Dem war aber leider nicht so.

Es wäre super wenn ihr mir das noch schnell erklären könntet.

Ich habe nun den Code um zwei struct's erweitert.

Der struct "Pos" enthält eine Variable "x" vom typ int.
Der struct "Start" enthält ein Array vom typ Pos.

Der struct Start wird nun als zweiter Parameter der Funktion func übergeben:


foo.h

Code: Alles auswählen

#ifndef GUARD_FOO
#define GUARD_FOO
#define NUMB 2
#include<stdint.h>
typedef struct Type
{
    int re, im;
} Type;
typedef struct Pos{
    int x;
}Pos;
typedef struct Start{
    Pos pos[NUMB];
}Start;
uint8_t func(struct Type *fout[NUMB], struct Start* start, int end);
#endif // GUARD_FOO
foo.c

Code: Alles auswählen

#include<stdio.h>
#include<stdint.h>
#include"foo.h"

uint8_t func(struct Type *fout[NUMB],struct Start* start, int end)
{
    for(int i=0;i<NUMB;i++)
    {
        Type* ptr = fout[i];
        for(int o=start->pos[1].x;o<end;o++)
        {
            //printf("%d %d re: %d im: %d\n", i, o, (ptr)->re, (ptr)->im);
            (ptr+o)->re = o;
            (ptr+o)->im = o;
            printf("%d %d re: %d im: %d\n", i, o, (ptr+o)->re, (ptr+o)->im);
        }
    }
    return 0;
}
main.py

Code: Alles auswählen

from ctypes import *
import numpy as np


class Type(Structure):
    _fields_ = [("re", c_int),
                ("im", c_int)]

    def __repr__(self):
        return 're: {0} im: {1}'.format(self.re, self.im)


class Pos(Structure):
    _fields_ = [("x", c_int)]

    def __repr__(self):
        return '{0}'.format(self.x)


class Start(Structure):
    _fields_ = [("pos", Pos*2)]
   # def __repr_(self):
   #     return '{0} {1}'.format(self)


lib = cdll.LoadLibrary("foo.dll")
func = lib.func
func.argtypes = [POINTER(Type)*2, POINTER(Start), c_int]
func.restype = c_uint8

arr_0 = np.zeros(shape=(100,), dtype=Type)
arr_1 = np.ones(shape=(100,), dtype=Type)

c_Type_p = POINTER(Type)
ptr_0 = arr_0.ctypes.data_as(c_Type_p)
ptr_1 = arr_1.ctypes.data_as(c_Type_p)

a = [ptr_0, ptr_1]
arr = (c_Type_p*2)(*(s for s in a))


pos1 = Pos(2)
pos2 = Pos(3)
start= Start((Pos*2)(*(s for s in [pos1,pos2])))
ptrstart=pointer(start)
func(arr, ptrstart, 10)



func(arr, 2, 10)
nn = np.ctypeslib.as_array(ptr_0, shape=(100,))
print(nn)
Nun laufe ich allerdings in eine exception:

Code: Alles auswählen

OSError: exception: access violation reading 0x000001FE8D686084
Versteht jemand was ich hier falsch mache?
Denn die Datentypen stimmen soweit.
Es scheint auf jeden Fall an dem Array vom typ "Pos" in dem struct "Start" zu liegen.
Und wenn ich den OSError richtig verstehe dann gibt es zwei Möglichkeiten.
Entweder ich laufe über den Speicherbereich drüber oder ich fange erst gar nicht an im richtigen Speicher zu lesen.
Antworten