Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I cannot access a C++ class attribute using ctypes

Tags:

c++

python

ctypes

I am using ctypes to develop a kind of Python API for a C++ library. So far, everything has been working fine. However, I upgraded my OS from Ubuntu 20.4 LTS to 22.04 (now with Python3.10.6 and g++ 11.3.0, but even with g++ 9.x.x, the following problem occurs).

The problem I have now is that I get a core dumped error whenever I want to access an attribute of a C++ object. The strange thing (to me) is that it works perfectly well when I use valgrind to debug. Here is a test case that illustrates what I am saying.

First, my .h (moncc.h) and my .cc (moncc.cc) files :

class test {
public:
    test() : value(0) { ; }
    void set_value(int lval);
    void print();
    
protected:
    int value;
};
#include <iostream>
#include "moncc.h"

void test::set_value(int val)
{
    printf("Here1\n");
    value = val;
    printf("Here2\n");
}

void test::print()
{
    printf("valeur : %d\n",value);
}

extern "C"
{
    test* CreateTest()
    {
        return new test();
    }
    
    void set_value(test* obj, int val)
    {
        obj->set_value(val);
    }
    
    void print(test* obj)
    {
        obj->print();
    }
}

Next, my compilation to create the test.so library:

g++ -c -Wall -lstdc++ -fPIC moncc.cc -o moncc.o
gcc moncc.o -shared -o test.so

The wrapper.py file which defines the interface with previous extern "C" functions:

#!/usr/bin/env python
# coding: utf-8

import ctypes
lib = ctypes.CDLL("test.so")

class test(object):
    def __init__(self):
        self.obj = lib.CreateTest()
        
        
    def set_value(self,val):
        lib.set_value(self.obj,val)
        
    def print(self):
        lib.print(self.obj)

And finally my main.py file :

#!/usr/bin/env python
# coding: utf-8

from wrapper import *

mon_obj = test()
mon_obj.set_value(5)
mon_obj.print()

I add the directory where test.so is in LD_LIBRARY_PATH, and here are the results :

python3 main.py 
Here1
Erreur de segmentation (core dumped)

(sorry, it's in French)

But,

valgrind python3 cas.py 
==245604== Memcheck, a memory error detector
==245604== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==245604== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==245604== Command: python3 main.py
==245604== 
Here1
Here2
valeur : 5
==245604== 
==245604== HEAP SUMMARY:
==245604==     in use at exit: 656,376 bytes in 179 blocks
==245604==   total heap usage: 2,348 allocs, 2,169 frees, 3,568,710 bytes allocated
==245604== 
==245604== LEAK SUMMARY:
==245604==    definitely lost: 0 bytes in 0 blocks
==245604==    indirectly lost: 0 bytes in 0 blocks
==245604==      possibly lost: 568 bytes in 1 blocks
==245604==    still reachable: 655,808 bytes in 178 blocks
==245604==         suppressed: 0 bytes in 0 blocks
==245604== Rerun with --leak-check=full to see details of leaked memory
==245604== 
==245604== For lists of detected and suppressed errors, rerun with: -s
==245604== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Any idea? I really don't understand...

like image 841
Julien B. Avatar asked Jul 18 '26 01:07

Julien B.


1 Answers

Set .argtypes and .restype for the functions called by ctypes. The 64-bit pointer returned by CreateTest is being truncated due to the return value defaulting to a c_int (a 32-bit integer).

Working code:

test.cpp

#include <stdio.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

class test {
    int value;
public:
    test() : value(0) { printf("Created test\n"); }
    ~test() { printf("Destroyed test\n"); }
    void set_value(int val) { value = val; }
    void print() { printf("value: %d\n",value); }
};

extern "C" {

API test* CreateTest() {
    return new test();
}

API void DestroyTest(test* obj) {
    delete obj;
}

API void set_value(test* obj, int val) {
    obj->set_value(val);
}

API void print(test* obj) {
    obj->print();
}

}

test.py

import ctypes as ct

class Test:
    def __init__(self):
        self.obj = lib.CreateTest()
    def __del__(self):
        lib.DestroyTest(self.obj)
    def set_value(self,val):
        lib.set_value(self.obj, val)
    def print(self):
        lib.print(self.obj)

lib = ct.CDLL('./test')

class PTEST(ct.c_void_p): pass  # opaque pointer aids type-checking

lib.CreateTest.argtypes = ()
lib.CreateTest.restype = PTEST
lib.DestroyTest.argtypes = PTEST,
lib.DestroyTest.restype = None
lib.set_value.argtypes = PTEST, ct.c_int
lib.set_value.restype = None
lib.print.argtypes = PTEST,
lib.print.restype = None

mon_obj = Test()
mon_obj.set_value(5)
mon_obj.print()

Output:

Created test
value: 5
Destroyed test
like image 196
Mark Tolonen Avatar answered Jul 19 '26 14:07

Mark Tolonen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!