19

I am trying to call a C++ function from a Python script. I have seen different solutions on Stackoverflow from 2010-2015 but they are all using complicated packages and was hoping for something easier/newer and more sophisticated. The C++ function I am trying to call takes in a double variable and returns a double.

double foo(double var1){
    double result = ...
    return result;
}
  • 3
    The easiest way would be using [ctypes](https://docs.python.org/3/library/ctypes.html). Compile your project to `.so` or `.dll`, export the functions you want to export and load in python script. – Tony Tannous Sep 27 '20 at 01:50
  • 2
    Consider adding links to some of the questions/solutions you looked at. – Adomas Baliuka Sep 27 '20 at 01:56
  • 2
    A tutorial on how to do this as well. https://www.geeksforgeeks.org/how-to-call-a-c-function-in-python/ In C++ you would enclose the exported functions in `extern "C"` to work. – Tony Tannous Sep 27 '20 at 01:58

2 Answers2

27

Python has ctypes package which allows calling functions in DLLs or shared libraries. Compile your C++ project into a shared library (.so) on Linux, or DLL on Windows. Export the functions you wish to expose outside.

C++ supports function overloading, to avoid ambiguity in the binary code, additional information is added to function names, known as name mangling. To ensure no name is changed, place inside an extern "C" block.
More on importance of extern "C" at the end!


Demo: In this dummy demo, our library has a single function, taking an int and printing it.

lib.cpp

#include <iostream>

int Function(int num) 
{
    std::cout << "Num = " << num << std::endl;
    return 0;
}

extern "C" {
    int My_Function(int a)
    {
        return Function(a);
    }
}

We will compile this into a shared object first

g++ -fPIC -shared -o libTest.so lib.cpp

Now we will utilized ctypes, to load the shared object/dll and functions.

myLib.py

import ctypes
import sys
import os 

dir_path = os.path.dirname(os.path.realpath(__file__))
handle = ctypes.CDLL(dir_path + "/libTest.so")     

handle.My_Function.argtypes = [ctypes.c_int] 
  
def My_Function(num):
    return handle.My_Function(num)    

For our test, we will call the function with num = 16

test.py

from myLib import *

My_Function(16)

The expected out as well.

enter image description here


EDIT: Comment section does not understand the importance of extern "C". As already explained above, C++ supports function overloading and additional information is added to function names known as name mangling.

Consider the following library

#include <iostream>

int My_Function(int num) 
{
    std::cout << "Num = " << num << std::endl;
    return 0;
}

Compiled the same: g++ -fPIC -shared -o libTest.so lib.cpp. Listing the exported symbols with nm -gD libTest.so results in:

enter image description here

Notice how the function name in the exported symbols is changed to _Z11My_Functioni. Running test.py now fails as it can not find the symbol.

enter image description here

You'd have to change myLib.py to reflect the change. However, you do not compile your library, take a look at the resulting symbol and build your module extension because there's no guarantee that re-compiling in the future with different version and additional code will result in the same symbol names. This is why one uses extern "C". Notice how in the first code the function name is unchanged.

enter image description here

Tony Tannous
  • 14,154
  • 10
  • 50
  • 86
  • Note: this example exposes everything, to limit what is being exposed compile with `-fvisibility=hidden`, and mark the functions you want to expose with `__attribute__((visibility("default")))` – Tony Tannous Sep 27 '20 at 02:35
  • Only My_Function() is in the extern "C" block, so in a a way, it's the only thing exposed to Python. If I understand correctly, your answer is more general, like when your .so/.dll is not *only* designed to be called from a Python program, right ? – Adrian B. Feb 11 '21 at 18:42
  • 2
    @AdrianB. `My_Function()` is in "extern C" to prevent name mangling, so that when someone searches for the symbol (function name) they find it. Everything is being exposed outside. To limit what is being exposed, you can compile with [-fvisibility=hidden](https://stackoverflow.com/questions/3570355/c-fvisibility-hidden-fvisibility-inlines-hidden) and then mark functions you want to expose with `__attribute__((visibility("default")))` as in [this answer](https://stackoverflow.com/a/3570430/6530695). Any programming language which offers loading a `.dll` or `.so` would work. – Tony Tannous Feb 11 '21 at 21:52
  • @AdrianB. And on how to use the attribute visibility look at https://stackoverflow.com/questions/52719364/how-to-use-the-attribute-visibilitydefault – Tony Tannous Feb 11 '21 at 21:55
  • I wasn't clear enough, I'll rephrase : if you make a C++ .so/.dll for which the **only** usage will be your Python module, then changing the visibility isn't important as only what you exposed via "extern C" will be available (if I understood correctly). I guess it doesn't harm to do it anyway, but as this post was specific to Python, I wanted to clarify if there was advantages from Python's point of view. Thanks for the additional information. – Adrian B. Feb 11 '21 at 23:07
  • 4
    @AdrianB. extern C is **NOT** used to expose, or allow this only function to be used! it's a way to insure the function name will remain the same in the exported symbols! so that anyone (in python) who tries to use it (i.e. search the function name), will find it in the `.so/.dll`. So it is very important to place them in a `extern C` – Tony Tannous Feb 11 '21 at 23:34
  • `test.py` or `script.py` ? – Geositta Aug 14 '22 at 20:16
  • @Geositta script.py but you could name the file test.py and run `python3 test.py` instead... – Tony Tannous Aug 16 '22 at 00:37
  • @Geositta the 2nd section of the answer (anything after EDIT) was added some time after. Then I named the file test.py instead of script.py – Tony Tannous Aug 16 '22 at 00:49
0

Replace double foo(double var1) with

#define PY_SSIZE_T_CLEAN
#include <Python.h>

extern "C" PyObject* foo(PyObject* self,PyObject* args, PyObject* kwargs)
{
    double var1;
    char* arr[] = {(char*)"var1",NULL};
    int err = PyArgs_ParseTupleAndKeywords(args,kwargs,"d",arr,&var1);
    if (err==0) {return NULL;}
    var result = ...;
    return Py_BuildValue("d",result);
}

Then add the function

PyMethodDef pymethods[2]:
PyModuleDef pymodule;
extern "C" PyObject* PyInit_bar() // Replace bar with your C++ Source file's name without its extension
{
    pymethods[0].ml_meth = (PyCFunction)foo;
    pymethods[0].ml_name = "foo";
    pymethods[0].ml_flags = METH_VAARGS | METH_KEYWORDS;
    pymethods[0].ml_doc = "";
    pymethods[1] = NULL;
    pymodule.m_name = "bar"; // Replace bar here too
    pymodule.m_methods = pymethods;
    pymodule.m_doc = "";
    PyObject* mod = PyModule_Create(&pymodule);
    return rtn;
}

Compile it as dll and change its file extension to .pyd and then you can import that pyd from your python code

Example

import bar
print(bar.foo(5.5))
Supergamer
  • 411
  • 4
  • 13