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...
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With