3

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!

0

1 Answer 1

2

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);.

Sign up to request clarification or add additional context in comments.

1 Comment

@matthews. Actually, func2 was exported, but with a C++ "mangled" name: _Z5func2ddd. This is done because of overloading by parameter types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.