Search code examples
pythonctypessetuptools

Python C Extension with Multiple Functions


I'm currently learning how to create C extensions for Python so that I can call C/C++ code. I've been teaching myself with a few examples. I started with this guide and it was very helpful for getting up and running. All of the guides and examples I've found online only give C code where a single function is defined. I'm planning to access a C++ library with multiple functions from Python and so I decided the next logical step in learning would be to add more functions to the example.

However, when I do this, only the first function in the extension is accessible from Python. Here's the example that I've made for myself (for reference I'm working on Ubuntu 21):

The C code (with two functions: func1 and func2, where func1 also depends on func2) and header files:

//func1.cpp

extern "C"

#include "func1.h"

double func1(double x, double y, double z) {
    return x*x + y*y + z*z*z + func2(x,y,z);
}

double func2(double x, double y, double z) {
    return x*y*z;
}
//func1.h

double func1(double x, double y, double z);
double func2(double x, double y, double z);

Then the Python extension setup which is run with python setup.py build:

#setup.py

from setuptools import setup, Extension

setup(
    ext_modules=[Extension('func1', sources=['func1.cpp'], include_dirs=['func1.h'])]
)

And finally the Python script where the extension is used which is run with python example.py:

#example.py

import ctypes
import numpy
import glob


libfile = glob.glob('build/*/func1*.so')[0]

lib = ctypes.CDLL(libfile)
func1 = lib.func1
func2 = lib.func2

func1.restype = ctypes.c_double
func1.argtypes = [ctypes.c_double, ctypes.c_double, ctypes.c_double]

func2.restype = ctypes.c_double
func2.argtypes = [ctypes.c_double, ctypes.c_double, ctypes.c_double]


print( func1(2,3,4) )
print( func2(2,3,4) )

When I do this example, python setup.py build compiles fine. However, when I get run the example file with python example.py I get that following error and traceback:

Traceback (most recent call last):
  File "~/Desktop/c_extension_example/example.py", line 12, in <module>
    func2 = lib.func2
  File "/usr/lib/python3.9/ctypes/__init__.py", line 387, in __getattr__
    func = self.__getitem__(name)
  File "/usr/lib/python3.9/ctypes/__init__.py", line 392, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: build/lib.linux-x86_64-3.9/func1.cpython-39-x86_64-linux-gnu.so: undefined symbol: func2

Which indicates that the function func2 isn't accessible through the extension. However, if I remove the references to func2 in example.py then everything runs fine and I get a result from func1 that is correct.

This is interesting to me because func1 depends on func2, but I can't access func2 directly. How can I change this example such that I can also access func2? Do I need to make a separate file and extension for each function that I want to access? That would be a bit cumbersome if I want to make an extension for a large C library.

Any help is greatly appreciated!

Thanks!


Solution

  • Make export "C" include both functions:

    #include "func1.h"
    
    extern "C" {
    
    double func1(double x, double y, double z) {
        return x*x + y*y + z*z*z + func2(x,y,z);
    }
    
    double func2(double x, double y, double z) {
        return x*y*z;
    }
    
    }
    
    extern "C" {
    
    double func1(double x, double y, double z);
    double func2(double x, double y, double z);
    
    }
    

    Without the braces, extern "C" is only applied to the next declaration, which is double func1(double x, double y, double z);.