Ad

Ctypes: Pointer Address Modification When Using Lib.so (on Mac) But Not When Using Lib.dll (on Windows)

- 1 answer

Source

C++

/*** type define class template for extern "C" ***/
typedef Mpoly<double> MpolyDouble;

// Declare functions as extern "C" for ctypes
//
// >> compiler statement (mac/linux): g++ -shared -o libPoly.so -fPIC libPoly.cpp
// >> compiler statement (windows): g++ -shared -o libPoly.dll libPoly.cpp
//
extern "C" {

    /*** libPoly Constructor/Destructor Routines ***/
    MpolyDouble* Poly_new(int size, int dim) { return new(nothrow) MpolyDouble(size,dim); }
    void Poly_del(MpolyDouble* foo)  { delete foo; }

    /*** libPoly Miscellaneous Routines ***/
    void PolyPrint(MpolyDouble* foo) {

        // print out address of object pointer
        std::cout << "address of foo: " << foo << std::endl; 

        // call MpolyDouble.print() method
        foo->print(); 
    }
}

Python

#*** import python libraries ***#
from ctypes import *
from numpy.ctypeslib import ndpointer
import numpy as np
import weakref

#**************************************#
#*** load the libPoly shared object ***#
#**************************************#
lib = CDLL('./libPoly.so')      #! for Mac/Linux
#lib = CDLL('./libPoly.dll')    #! for Windows

#********************************************************#
#*** set the argument types for each member attribute ***#
#********************************************************#
## Poly_new
lib.Poly_new.argtypes = [c_int, c_int]
lib.Poly_new.restype  = c_void_p
## Poly_del
lib.Poly_del.argtypes  = [c_void_p] #<---edit
lib.Poly_del.restype  = None
## PolyPrint
lib.PolyPrint.argtypes = [c_void_p] #<---edit
lib.PolyPrint.restype = None

#********************************************************#
#***            define a python class Poly            ***#
#********************************************************#
class Poly(object):

    # GHV Instantiation Routine:
    def __init__(self,size=1,dim=2):
        # ensure that the input args are of correct type
        size = int(size)
        dim = int(dim)

        # call the C/C++ function
        self.obj = lib.Poly_new(size,dim)

    def __del__(self):
        lib.Poly_del(self.obj)
        self._obj = None

    # GHV Print Routine:
    def Print(self):
        print 'address of self.obj',hex(id(self.obj))
        # call the C/C++ function
        lib.PolyPrint(self.obj)

OS X (Sample) Implementation

Terminal

$ python

from PolyWrapper import *

P = Poly()

P.Print()

Terminal Output

address of self.obj 0x10038fc08

address of foo: 0x1804810

Segmentation fault: 11

Console

Process: python2.7 [3359]

Path: /Users/USER/*/python2.7

Identifier: python2.7

Code Type: X86-64 (Native) Parent Process: bash [3341] Responsible: Terminal [235]

Date/Time: 2016-02-16 12:48:22.220 -0500 OS Version: Mac OS X 10.11.3 (15D21)

Exception Type: EXC_BAD_ACCESS (SIGSEGV)

Exception Codes: KERN_INVALID_ADDRESS at 0x0000000001804810

Description

Question

Why does this work on my windows platform but not anywhere else?

I do not get any compiler errors on either platform. Also, my use to the typedef to get past the function template has not given me problems on other similar projects.

Possible Clues

  • Notice that the 'address of foo', printed from within the .cpp file is the same address that is raising the alarm in the console 'Exception Codes'. Is python receiving the correct address when creating a new object? How to tell, and how is that possible?

  • A similar issue was reported here, but I don't believe this is the problem here, because I have explicitly set restype, argtype, and argtypes.

  • g++ --version -- Apple LLVM version 7.0.2 (clang-700.1.81) (OS X) and g++ (GCC) 2.9.3 (Windows CygWIN). If you think compilers might be the issue just note that I used this same compiler to generate both a lib.so and lib.dll. Execution from Spyder (Python (x,y)) works; execution from CygWIN shell for the lib.so gives the SIG11.

Ad

Answer

Here's my final solution. I made no significant changes on the c++ side of things - only the Python ctypes bindings. I also added some logic to handle the importing of dynamic library (shared object), which is not general. It works for my two workstations and a few others that I have to collaborate with the exception of a 32 bit version of Python2.7.

Lessons Learned:

  • Pay attention to default bit lengths for numbers when using different platforms (Unix vs. Windows, 32 bit vs 64 bit, etc.).
  • Take advantage of class attributes for: (i) good coding practices, and (ii) ensuring that a __del__ routine can be easily linked to a c++ object's destructor routine, which is essential when passing objects back and forth as pointers.
  • I made the mistake of calling lib as a global, which did not give me problems until evoking __del__; however, by the time lib.Poly_del(self.obj) was called, lib was no longer definded.

Python bindings (PolyWrapper.py)

#*************************************#    
#***    import python libraries    ***#
#*************************************#
from ctypes import *
from numpy.ctypeslib import ndpointer
import numpy as np
import weakref
import platform

#**************************************#
#*** load the libPoly shared object ***#
#**************************************#
print.system()
if (platform.system() == "Windows" or platform.system() == "CYGWIN-6.1"):
    lib = CDLL('./libPoly.dll')
else:
    lib = CDLL('./libPoly.so')

#********************************************************#
#*** set the argument types for each member attribute ***#
#********************************************************#
## Poly_new
lib.Poly_new.argtypes  = [c_int, c_int]
lib.Poly_new.restype   = c_void_p
## Poly_del
lib.Poly_del.argtypes  = [c_void_p]
lib.Poly_del.restype   = None
## PolyPrint
lib.PolyPrint.argtypes = [c_void_p]
lib.PolyPrint.restype  = None

#********************************************************#
#***            define a python class Poly            ***#
#********************************************************#
class Poly(object):
    _obj = None
    _local = lib

    # Poly Instantiation Routine:
    def __init__(self,size=1,dim=1):
        # ensure that the input args are of correct type
        size = int(size)
        dim = int(dim)
        # call the C/C++ function
        self._obj = c_void_p(self._lib.Poly_new(size,dim))
    
    # Poly Destructor Routine:
    def __del__(self):
        if (self._obj is not None):
            self._lib.Poly_del(self._obj)
            del self._obj

    # Poly Print Routine:
    def Print(self):
        # call the C/C++ function
        self._lib.PolyPrint(self._obj)
Ad
source: stackoverflow.com
Ad