1

My question is based on this example. What's different is that in my C++ code, I have a function that initiates an object of a self-defined class within that function. In my python code, I simply want to call that function. I don't need to create an object of the self-defined class in Python. For example, there are three files in my C++ code

foo.h

class foo {

public: 
        foo();
        void bar();
        int a;


};

foo.cpp

extern "C" {
    foo::foo () {
      a = 5;
    }


    void foo::bar(){
            std::cout << "Hello" << std::endl;
    };
}

mainFunc.cpp

extern "C" {
    void printStuff (int addNum) {
        foo fooArg;
        fooArg.a = fooArg.a + addNum;
        std::cout<<"Printing..."<<std::endl;
        std::cout<<fooArg.a<<std::endl;
    }
}

In my Python code, I want to create a Python function that calls printStuff in mainFunc.cpp. I don't need to initiate an object of foo in Python. However, two questions:

(1) Did I use extern "C" correctly?

(2) How should I compile these three files into a shared library? I use g++.

tryingtosolve
  • 763
  • 5
  • 20

1 Answers1

3

(1) Did I use extern "C" correctly?

Don't put extern ”C" around the method definitions.

The purpose of extern "C" is to tell the compiler that some function needs to be accessible as a C function—that is, called by C code, or by ctypes or some other library that loads and calls functions out of shared objects.

The only function you want to call through ctypes is printStuff, so only that function should be extern "C".

Trying to extern "C" the methods will probably harmlessly do nothing with g++ (they'll still end up named something like __ZN3foo3barEv in the export table), but you may get a warning, or even a non-running library, with a different compiler.


(2) How should I compile these three files into a shared library? I use g++.

You probably want to write a Makefile or use some other build system, but if you want to do it manually:

g++ -shared -o foo.so foo.cpp mainFunc.cpp

Depending on your platform, you may also need to manually specify -fPIC or -fpic.1

If you want to do the steps separately:

g++ -c foo.cpp
g++ -c mainFunc.cpp
g++ -shared -o foo.so foo.o mainFunc.o

In this case, if a PIC flag is needed, it goes on the first two lines.


And now, to use it from Python:

$ python3
>>> import ctypes
>>> foo = ctypes.cdll.LoadLibrary('foo.so')
>>> foo.printStuff(10)
Printing...
15
>>> foo.printStuff("abc")
Printing...
116480373

Of course it's generally better to set the argtypes and restype instead of making ctypes guess. It guessed that the Python int 10 should be converted to the C int 10, which worked great, but it also guessed that the Python str "abc" should be converted to a C const char *, and you end up with the low 32 bits of the pointer to the string buffer being used as an int. So:

>>> foo.printStuff.argtypes = [ctypes.c_int]
>>> foo.printStuff.restype = None
>>> foo.printStuff(10)
Printing...
15
>>> foo.printStuff("abc")
ArgumentError: argument 1: <class 'TypeError'>: wrong type

And you may want to write a foo.py that wraps that up:

import ctypes
foo = ctypes.cdll.LoadLibrary('foo.so')
foo.printStuff.argtypes = [ctypes.c_int]
foo.printStuff.restype = None
printStuff = foo.printStuff

1. From quick tests, I didn't need it for x86_64 macOS, x86 macOS, or x86_64 Linux, but PowerPC Linux needs -fpic and ARM64 Linux needs -fPIC. However, I didn't actually run all of those. And, other than macOS (where I made sure to test with Homebrew gcc 8.2 and Apple Clang 9.1) I don't know which compiler version I had.

abarnert
  • 354,177
  • 51
  • 601
  • 671