python3 ctypes, array of structs

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
Hoe kan ik een array van structs in python doorgeven aan mijn c library? Ik heb me gebasseerd op de tweede oplossing https://stackoverflow.com...n-ctypes-array-of-structs maar krijg nog een foutmelding.

foo.h
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct si16s {

    int16_t si;
    int8_t scale;

};

struct covar_si {

    struct si16s matrix[6];
    int8_t min_scale[3];

};

void test_print(const struct covar_si *val);


foo.c
C:
1
2
3
4
5
6
7
8
9
10
11
void test_print(const struct covar_si *val) {

    uint8_t i = 0;
    for (; i < 6; ++i)
        printf("%i / 2 ^ %i\n", val -> matrix[i].si, val -> matrix[i].scale);

    for (i = 0; i < 3; ++i)
        printf("%i\t", val -> min_scale[i]);
    printf("\n");
    
}


test_run.py
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
lib = ct.cdll.LoadLibrary(LIB_PATH)

POINTER = ct.POINTER


class SI16S(ct.Structure):

    _fields_ = [('si', ct.c_int16), ('scale', ct.c_int8)]


COVAR_LENGTH = int(N_TOT_FEAT * (N_TOT_FEAT + 1) / 2)
COVAR_ARR = SI16S * COVAR_LENGTH


class COVAR_SI(ct.Structure):

    _fields_ = [('matrix', POINTER(SI16S)), ('min_scale', POINTER(ct.c_int8))]

    def __init__(self):
        
        elems = (SI16S * COVAR_LENGTH)()
        self.matrix = ct.cast(elems, POINTER(SI16S))
        elems = (ct.c_int8 * N_TOT_FEAT)()
        self.min_scale = ct.cast(elems, POINTER(ct.c_int8))

        for i in range(COVAR_LENGTH):

            self.matrix[i] = SI16S(i, 2)

        for i in range(N_TOT_FEAT):

            self.min_scale[i] = 3

Python:
1
2
3
4
5
6
7
8
def main():

    test_int = COVAR_SI()
    lib.test_print(POINTER(test_int))


if __name__ == "__main__":
    main()



Dit geeft mij de foutmelding op de lijn lib.test_print(..)
Traceback (most recent call last):
File "test_run.py", line 21, in <module>
main()
File "test_run.py", line 17, in main
lib.test_print(POINTER(test_int))
TypeError: must be a ctypes type
Wat doe ik verkeerd? test_int is toch een COVAR_SI class object en dus een ctypes type? Het lukt mij om een SI16S door te geven naar mijn C library om hem te printen. De COVAR_SI lukt dan weer niet.

Een klein bijvraagje. Mij eerste fout was
elems = (SI16S * COVAR_LENGTH)()
schrijven als
elems = (SI16S * COVAR_LENGHT)
Dus zonder haakjes op het einde, had ze niet gezien. Wat doen deze haakjes?

Beste antwoord (via Swedgin op 28-04-2018 14:54)


  • GlowMouse
  • Registratie: November 2002
  • Niet online
De datatypes kloppen niet omdat er in de struct geen pointers zitten. Zo werkt het wel:
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
class COVAR_SI(ct.Structure):
    _fields_ = [('matrix', SI16S*6), ('min_scale', ct.c_int8*3)]

    def __init__(self):
        for i in range(COVAR_LENGTH):
            self.matrix[i] = SI16S(i, 2)

        for i in range(3):
            self.min_scale[i] = 3


test_int = COVAR_SI()
lib.test_print(ct.byref(test_int))

De reden waarom si16s wel goed werd geprint komt omdat je een object gaf waarvan het eerste element een pointer is, en C een pointer verwachtte naar een object. C kon de pointer dus gewoon volgen, maar vond daar alleen si16s, en niet aansluitend de min_scale.

[ Voor 22% gewijzigd door GlowMouse op 27-04-2018 20:44 ]

Alle reacties


Acties:
  • +1 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
code:
1
elems = (SI16S * COVAR_LENGTH)()

Zie paragraaf 9.3.2 in de handleiding.

Volgens de handleiding werkt POINTER alleen op ctypes. Wat gebeurt er als je POINTER weglaat?

Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
Ok, die klasse initialisatie is eigenlijk echt wel nog logisch. Raar dat ik het verband niet gelegd heb.

De pointer weglaten in main is inderdaad de oplossing. Ik dacht nochtans dat ik het geprobeerd had, maar waarschijnlijk met nog een ander foutje.

Nu kan ie printen, maar nog niet correct. Dit is de output
0 / 2 ^ 2
1 / 2 ^ 2
2 / 2 ^ 2
3 / 2 ^ 2
4 / 2 ^ 2
5 / 2 ^ 2
-127 5 0
De laatste lijn zou "3 3 3" moeten zijn
En na een paar keer uit te voeren, lijkt het alsof die laatste lijn garbage waarden zijn

min_scale is een array die de minimum scale bijhoud van de bijhorende rij in de matrix. deze heb ik later nodig en zo moet ik niet telkens eerst eens elke rij overlopen om de minimum scale te weten

[ Voor 23% gewijzigd door Swedgin op 27-04-2018 15:40 ]


Acties:
  • 0 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
Min_scale moet je vullen met c_int8, zie paragraaf 16.16.1.15 in de handleiding.

Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
C:
1
self.min_scale[i] = ct.c_int8(3)


lost niets op, of heb ik je verkeerd begrepen?

Acties:
  • 0 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
De handleiding meldt dit over pointers:
In addition, if a function argument is explicitly declared to be a pointer type (such as POINTER(c_int)) in argtypes, an object of the pointed type (c_int in this case) can be passed to the function. ctypes will apply the required byref() conversion in this case automatically.
Nu zit er bij jou een cast tussen die ik niet helemaal begrijp. Waarom zou je een int8 naar een int8 casten, en niet
code:
1
self.min_scale = (ct.c_int8 * N_TOT_FEAT)()

doen?

Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
Omdat min_scale een pointer naar c_int8 is en ik hetzelfde wou toepassen als de pointer naar SI16S. Dit doe ik op regel 23-24. Op regel 32 vul ik de waarde in.

Ik heb de cast weggelaten, maar het help niet.

Acties:
  • Beste antwoord
  • +1 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
De datatypes kloppen niet omdat er in de struct geen pointers zitten. Zo werkt het wel:
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
class COVAR_SI(ct.Structure):
    _fields_ = [('matrix', SI16S*6), ('min_scale', ct.c_int8*3)]

    def __init__(self):
        for i in range(COVAR_LENGTH):
            self.matrix[i] = SI16S(i, 2)

        for i in range(3):
            self.min_scale[i] = 3


test_int = COVAR_SI()
lib.test_print(ct.byref(test_int))

De reden waarom si16s wel goed werd geprint komt omdat je een object gaf waarvan het eerste element een pointer is, en C een pointer verwachtte naar een object. C kon de pointer dus gewoon volgen, maar vond daar alleen si16s, en niet aansluitend de min_scale.

[ Voor 22% gewijzigd door GlowMouse op 27-04-2018 20:44 ]


Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
Ja, het werkt nu. Bedankt.

Dus het verschil tussen mijn case en deze van stackoverflow, is dat daar STRUCT_1.STRUCT_ARRAY een pointer is naar een STRUCT_2 en hier is COVAR_SI.matrix een pointer naar SI16S[6]?

Dit is toch hetzelfde? Of zit het intern anders in elkaar dan ik voor ogen heb?

Ik heb je antwoord als beste antwoord aangeduid omdat deze de opening post beantwoord heeft.

Acties:
  • +1 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
Swedgin schreef op zaterdag 28 april 2018 @ 15:07:
Dus het verschil tussen mijn case en deze van stackoverflow, is dat daar STRUCT_1.STRUCT_ARRAY een pointer is naar een STRUCT_2 en hier is COVAR_SI.matrix een pointer naar SI16S\[6]?

Dit is toch hetzelfde? Of zit het intern anders in elkaar dan ik voor ogen heb?

Ik heb je antwoord als beste antwoord aangeduid omdat deze de opening post beantwoord heeft.
De C-code op stackoverflow (als het al C is) bevat
C:
1
STRUCT_2 *STRUCT_ARRAY

en niet
C:
1
STRUCT_2 STRUCT_ARRAY[6]

De data die vanuit python wordt doorgegeven (STRUCT_1) bevat 10 bytes, namelijk een 2-byte short waarin staat hoeveel elementen er zijn, en een 8-byte pointer naar het geheugenadres van het eerste STRUCT_2 element.
Jouw c-functie wil een pointer naar het adres waar eerst 6 matrixen staan en daarna 3 min_scales. COVAR_SI.matrix is geen pointer, de struct covar_si is 21 bytes lang en bevat eerst 6 si16s van 3 bytes elk, en daarna 3 min_scales van 1 byte elk.

Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
Ok. Ik had het dus verkeerd voor ogen. Ik dacht dat COVAR_SI.matrix een pointer was naar het eerste element in COVAR_SI.matrix[6] een COVAR_SI.min_scale een pointer naar het eerste element in COVAR_SI.min_scale[3].
Maar omdat dit at compile-time aangemaakt is, is dit dus verschillend? Moest ik dus .matrix en .min_scale at runtime aanmaken (malloc) zou ik dus stackoverflow moeten volgen.

C:
1
2
3
4
5
6
7
8
STRUCT *STRUCT_array;
STRUCT_array = malloc(6 * sizeof(STRUCT))

[*] -> [0|1|2|3|4|5]

STRUCT STRUCT_array[6];

[0|1|2|3|4|5]


Zie ik dit juist? Ik heb enkele jaren geleden wat C gehad op school maar heb eigenlijk mijn kennis opgebouwd door doorheen de jaren wat te programmeren en te falen. Ik leer nog steeds bij, maar merk al dat ik minder vlug moet googlen.
Ik had eigenlijk altijd gedacht dat ze hetzelfde zijn. STRUCT_array wijst toch in beide gevallen toch altijd naar de eerste plaats in de rij.
Vanuit geheugen standpunt, is het dus goed dat ik ze at compile time aanmaak zodat ik die 8-byte pointer niet nodig heb.

Acties:
  • 0 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
Dat kan inderdaad ook. Je krijgt alleen een probleem met je huidige methode als je heel veel data over wilt zetten omdat dat mogelijk niet op de stack past.

Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
GlowMouse schreef op zaterdag 28 april 2018 @ 18:31:
Dat kan inderdaad ook. Je krijgt alleen een probleem met je huidige methode als je heel veel data over wilt zetten omdat dat mogelijk niet op de stack past.
Hoe bedoel je? Data van waar naar waar?
Ik veronderstel dat we het nu over mijn C code hebben. Als ik de COVAR_SI nodig heb in een functie, doe ik dat met pass by reference, zodat er niets gekopieerd moet worden.

Uiteindelijk komt de C code te lopen op een Arduino Uno. Er zal 1 instantie van struct COVAR_SI zijn die voorlopig constant is. De covar matrix heb ik dan nodig om een array te transformeren.

Acties:
  • +1 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
Niet echt van toepassing hier, maar als je zoiets hebt:
C:
1
2
3
int main() {
    int vec[10];
}

dan mag 10 niet te groot zijn omdat de ruimte op de stack beperkt is. Dat toont al aan dat het niet slechts een pointer is, maar de volledige array in de stack staat.

Acties:
  • 0 Henk 'm!

  • Swedgin
  • Registratie: Februari 2016
  • Laatst online: 12-01-2022
Ok, bedankt. Ik weet dat er zaken zijn zoals de stack en de heap, maar dit zit allemaal zo ver haha. Ik moet mezelf me er eens toe verbinden een boek op te pakken.
Pagina: 1