When using functions exported by cdll.LoadLibrary, you're releasing the Global Interpreter Lock (GIL) as you enter the method. If you want to call python code, you need to re-acquire the lock.

e.g.

void someFunctionWithPython()
{
    ...
    PyGILState_STATE state = PyGILState_Ensure();
    printf("importing numpy...\n");
    PyObject* numpy = PyImport_ImportModule("numpy");
    if (numpy == NULL)
    {
        printf("Warning: error during import:\n");
        PyErr_Print();
        Py_Finalize();
        PyGILState_Release(state);
        exit(1);
    }

    PyObject* repr = PyObject_Repr(numpy);
    PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
    const char *bytes = PyBytes_AS_STRING(str);

    printf("REPR: %s\n", bytes);

    Py_XDECREF(repr);
    Py_XDECREF(str);

    PyGILState_Release(state);
    return;
}
(python3.9-config --includes --ldflags --embed) -shared -o mylibwithpy.so mylibwithpy.c
$ LD_LIBRARY_PATH=. python driver.py
opening mylibwithpy.so...
.so object:  <CDLL 'mylibwithpy.so', handle 1749f50 at 0x7fb603702fa0>
.so object's 'someFunctionWithPython':  <_FuncPtr object at 0x7fb603679040>
calling someFunctionWithPython...
python alread initialized.
importing numpy...
REPR: <module 'numpy' from '/home/me/test/.venv/lib/python3.9/site-packages/numpy/__init__.py'>

Also if you look at PyDLL it says:

Instances of this class behave like CDLL instances, except that the Python GIL is not released during the function call, and after the function execution the Python error flag is checked. If the error flag is set, a Python exception is raised.

So if you use PyDLL for your driver then you wouldn't need to re-acquire the lock in the C code:

from ctypes import PyDLL

if __name__ == "__main__":
    print("opening mylibwithpy.so...");
    my_so = PyDLL("mylibwithpy.so")

    print(".so object: ", my_so)
    print(".so object's 'someFunctionWithPython': ", my_so.someFunctionWithPython)

    print("calling someFunctionWithPython...");
    my_so.someFunctionWithPython()

UPDATE

Why do numpy's internal shared objects files not link to libpython3.8.so?

I believe numpy is setup this way because it expects to be called by the python interpreter where libpython will already be loaded and have the symbols made available.

That said, we can make the python libraries available for when mylibwithpy calls the import of numpy by using RTLD_GLOBAL.

The symbols defined by this shared object will be made available for symbol resolution of subsequently loaded shared objects.

The update to your code is simple:

void* mylibwithpy_so = dlopen("mylibwithpy.so", RTLD_LAZY | RTLD_GLOBAL);

Now all of the python libraries will be included because they are a dependency of mylibwithpy, meaning they will be available by the time that numpy loads its own shared libraries.

Alternatively, you could choose to load just libpythonX.Y.so with RTLD_GLOBAL to prior to loading mylibwithpy.so to minimize the amount symbols made globally available.

printf("opening libpython3.9.so...\n");
void* libpython3_so = dlopen("libpython3.9.so", RTLD_LAZY | RTLD_GLOBAL);
if (libpython3_so == NULL){
    printf("an error occurred during loading libpython3.9.so: \n%s\n", dlerror());
    exit(1);
}

printf("opening mylibwithpy.so...\n");
void* mylibwithpy_so = dlopen("mylibwithpy.so", RTLD_LAZY);
if (mylibwithpy_so == NULL){
    printf("an error occurred during loading mylibwithpy.so: \n%s\n", dlerror());
    exit(1);
}

Docker setup I used to recreate and test:

FROM ubuntu:20.04

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
   build-essential \
   python3.9-dev \
   python3.9-venv 

RUN mkdir /workspace
WORKDIR /workspace

RUN python3.9 -m venv .venv
RUN .venv/bin/python -m pip install numpy

COPY . /workspace

RUN gcc -o mylibwithpy.so mylibwithpy.c -fPIC -shared \
    $(python3.9-config --includes --ldflags --embed --cflags) 

RUN gcc -o cdriver driver.c -L/usr/lib/x86_64-linux-gnu -Wall -ldl

ENV LD_LIBRARY_PATH=/workspace
# Then run: . .venv/bin/activate && ./cdriver
Answer from flakes on Stack Overflow
🌐
Python
docs.python.org › 3 › c-api › index.html
Python/C API reference manual — Python 3.14.3 documentation
This manual documents the API used by C and C++ programmers who want to write extension modules or embed Python. It is a companion to Extending and Embedding the Python Interpreter, which describes...
🌐
GitHub
github.com › fpoli › python-c-api
GitHub - fpoli/python-c-api: Python/C API quick example · GitHub
A quick example of Python 3.10 modules implemented in C using the Python/C API.
Starred by 21 users
Forked by 12 users
Languages   C 74.7% | Makefile 17.4% | Python 7.9%
Discussions

How would you properly use the Python/C API in a C shared library? - Stack Overflow
To accomplish this, I am trying to make a shared object library in C that leverages the Python/C API. I have been running into some issues getting things to run as expected, so I've tried to distill the problem down to an "as-simple-as-possible" example: More on stackoverflow.com
🌐 stackoverflow.com
AskPython: Best Tutorials/Examples of the Python C API?
I don't know, I went through the python docs in detail once and could write some basic extensions in C but then I decided to try out cython and never looked back :) Have you tried cython ? More on reddit.com
🌐 r/Python
7
28
December 14, 2009
Is there a good guide for learning how to write native (c) code for Python?
I don't believe C is the biggest issue here. The whole reason modules are written in C rather than just more python, is the low level optimisations. Knowing how to program something that works is one thing, knowing how to optimise code for speed is the realm of wizards. You would need a solid basis in data structures, algorithms, that sort of thing. Maybe even some assembly if you want absolute optimal code. That sounds like getting a degree. But I'll start with asking around the circuitpython team first. They should have a forum somewhere. Maybe they can offer some advice for contributions. More on reddit.com
🌐 r/learnpython
10
22
November 8, 2023
Guide on how to use DeepSeek-v3 model with Cline
hey mate, I loved your video, no intro, ask for subs, just straight to the point More on reddit.com
🌐 r/ChatGPTCoding
58
94
December 29, 2024
🌐
Python
docs.python.org › 3 › c-api › intro.html
Introduction — Python 3.14.3 documentation
Convert x to a C string. For example, Py_STRINGIFY(123) returns "123". Added in version 3.4. The following macros can be used in declarations. They are most useful for defining the C API itself, and have limited use for extension authors.
🌐
Medium
medium.com › @wansac › the-python-c-api-a-brief-introduction-7926ea0ef488
The Python/C API: A Brief Introduction | by Ashley Wans | Medium
September 14, 2020 - The Python/C API allows C programmers to embed Python directly into C code by exposing aspects of CPython internals. It provides direct access to the Python interpreter from C, acting as a bridge between the two languages.
🌐
Python
docs.python.org › 3 › extending › extending.html
1. Extending Python with C or C++ — Python 3.14.3 documentation
For example, an extension module could implement a type “collection” which works like lists without order. Just like the standard Python list type has a C API which permits extension modules to create and manipulate lists, this new collection ...
Top answer
1 of 1
3

When using functions exported by cdll.LoadLibrary, you're releasing the Global Interpreter Lock (GIL) as you enter the method. If you want to call python code, you need to re-acquire the lock.

e.g.

void someFunctionWithPython()
{
    ...
    PyGILState_STATE state = PyGILState_Ensure();
    printf("importing numpy...\n");
    PyObject* numpy = PyImport_ImportModule("numpy");
    if (numpy == NULL)
    {
        printf("Warning: error during import:\n");
        PyErr_Print();
        Py_Finalize();
        PyGILState_Release(state);
        exit(1);
    }

    PyObject* repr = PyObject_Repr(numpy);
    PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
    const char *bytes = PyBytes_AS_STRING(str);

    printf("REPR: %s\n", bytes);

    Py_XDECREF(repr);
    Py_XDECREF(str);

    PyGILState_Release(state);
    return;
}
(python3.9-config --includes --ldflags --embed) -shared -o mylibwithpy.so mylibwithpy.c
$ LD_LIBRARY_PATH=. python driver.py
opening mylibwithpy.so...
.so object:  <CDLL 'mylibwithpy.so', handle 1749f50 at 0x7fb603702fa0>
.so object's 'someFunctionWithPython':  <_FuncPtr object at 0x7fb603679040>
calling someFunctionWithPython...
python alread initialized.
importing numpy...
REPR: <module 'numpy' from '/home/me/test/.venv/lib/python3.9/site-packages/numpy/__init__.py'>

Also if you look at PyDLL it says:

Instances of this class behave like CDLL instances, except that the Python GIL is not released during the function call, and after the function execution the Python error flag is checked. If the error flag is set, a Python exception is raised.

So if you use PyDLL for your driver then you wouldn't need to re-acquire the lock in the C code:

from ctypes import PyDLL

if __name__ == "__main__":
    print("opening mylibwithpy.so...");
    my_so = PyDLL("mylibwithpy.so")

    print(".so object: ", my_so)
    print(".so object's 'someFunctionWithPython': ", my_so.someFunctionWithPython)

    print("calling someFunctionWithPython...");
    my_so.someFunctionWithPython()

UPDATE

Why do numpy's internal shared objects files not link to libpython3.8.so?

I believe numpy is setup this way because it expects to be called by the python interpreter where libpython will already be loaded and have the symbols made available.

That said, we can make the python libraries available for when mylibwithpy calls the import of numpy by using RTLD_GLOBAL.

The symbols defined by this shared object will be made available for symbol resolution of subsequently loaded shared objects.

The update to your code is simple:

void* mylibwithpy_so = dlopen("mylibwithpy.so", RTLD_LAZY | RTLD_GLOBAL);

Now all of the python libraries will be included because they are a dependency of mylibwithpy, meaning they will be available by the time that numpy loads its own shared libraries.

Alternatively, you could choose to load just libpythonX.Y.so with RTLD_GLOBAL to prior to loading mylibwithpy.so to minimize the amount symbols made globally available.

printf("opening libpython3.9.so...\n");
void* libpython3_so = dlopen("libpython3.9.so", RTLD_LAZY | RTLD_GLOBAL);
if (libpython3_so == NULL){
    printf("an error occurred during loading libpython3.9.so: \n%s\n", dlerror());
    exit(1);
}

printf("opening mylibwithpy.so...\n");
void* mylibwithpy_so = dlopen("mylibwithpy.so", RTLD_LAZY);
if (mylibwithpy_so == NULL){
    printf("an error occurred during loading mylibwithpy.so: \n%s\n", dlerror());
    exit(1);
}

Docker setup I used to recreate and test:

FROM ubuntu:20.04

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
   build-essential \
   python3.9-dev \
   python3.9-venv 

RUN mkdir /workspace
WORKDIR /workspace

RUN python3.9 -m venv .venv
RUN .venv/bin/python -m pip install numpy

COPY . /workspace

RUN gcc -o mylibwithpy.so mylibwithpy.c -fPIC -shared \
    $(python3.9-config --includes --ldflags --embed --cflags) 

RUN gcc -o cdriver driver.c -L/usr/lib/x86_64-linux-gnu -Wall -ldl

ENV LD_LIBRARY_PATH=/workspace
# Then run: . .venv/bin/activate && ./cdriver
🌐
YouTube
youtube.com › watch
Python/C API Introduction - YouTube
This video explain introductory concepts used in mixing python with C/C++. Using Python and C/C++ is beneficial because you can use the performance of C/C++...
Published   September 15, 2012
Find elsewhere
🌐
Real Python
realpython.com › build-python-c-extension-module
Building a Python C Extension Module – Real Python
June 27, 2023 - In this tutorial, you'll learn how to write Python interfaces in C. Find out how to invoke C functions from within Python and build Python C extension modules. You'll learn how to parse arguments, return values, and raise custom exceptions using the Python API.
🌐
GitHub
github.com › dexpota › cpython-api-examples
GitHub - dexpota/cpython-api-examples: Some examples of using the C API of python 2.7.
04 Custom type: this recipe shows how to define a custom data type inside the C/C++ module; 05 Embedding python inside C/C++: this recipe shows how to integrate a python interpreter inside a C/C++ program;
Starred by 5 users
Forked by 2 users
Languages   C++ 56.8% | CMake 21.2% | C 19.1% | Python 2.9% | C++ 56.8% | CMake 21.2% | C 19.1% | Python 2.9%
🌐
Cornell Virtual Workshop
cvw.cac.cornell.edu › python-performance › intro › cpython-api
Cornell Virtual Workshop > Python for High Performance > Overview > CPython and the Python/C API
Extension modules can be written ... Python/C API, but typically, they are generated by third-party tools that automate the generation of interface code based upon specifications, such as function declarations in a library that someone wants to wrap up and call from Python. With some third-party tools, compiled code is auto-generated and imported back into running Python code automatically. We will see examples of these sorts ...
🌐
Python Tips
book.pythontips.com › en › latest › python_c_extension.html
22. Python C extensions — Python Tips 0.1 documentation
In this example the C file is self explanatory - it contains two functions, one to add two integers and another to add two floats. In the python file, first the ctypes module is imported. Then the CDLL function of the ctypes module is used to load the shared lib file we created.
🌐
SciPy Lecture Notes
scipy-lectures.org › advanced › interfacing_with_c › interfacing_with_c.html
2.8. Interfacing with C — Scipy lecture notes
Since reference counting bugs are easy to create and hard to track down, anyone really needing to use the Python C-API should read the section about objects, types and reference counts from the official python documentation. Additionally, there is a tool by the name of cpychecker which can help discover common errors with reference counting. The following C-extension module, make the cos function from the standard math library available to Python: /* Example of wrapping cos function from math.h with the Python-C-API.
🌐
Readthedocs
pythoncapi.readthedocs.io
Design a new better C API for Python — pythoncapi 0.1 documentation
To be able to introduce backward incompatible changes to the C API without breaking too many C extensions, this project proposes two things: design a helper layer providing removed functions; a new Python runtime which is only usable with C extensions compiled with the new stricter and smaller C API (and the new stable ABI) for Python 3.8 and newer, whereas the existing “regular python” becomes the “regular runtime” which provides maximum backward compatibility with Python 3.7 and older.
🌐
Python Developer's Guide
devguide.python.org › developer-workflow › c-api
Changing Python’s C API
In other words, while we’re allowed to break calling code, we shouldn’t break it unnecessarily. Expose the API under its new name, with the PyUnstable_ prefix. The PyUnstable_ prefix must be used for all symbols (functions, macros, variables, etc.). Make the old name an alias (for example, a static inline function calling the new function).
🌐
Cornell Virtual Workshop
cvw.cac.cornell.edu › python › api
CPython and the Python/C API - Cornell Virtual Workshop
The CPython interpreter (aka, "python") works by compiling Python source code to intermediate bytecodes, and then operating on those. CPython, which is written in C, is also accompanied by an Application Programming Interface (API) that enables communication between Python and C (and thus basically ...
🌐
Developers Heaven
developers-heaven.net › home › introduction to the python c api: writing your first extension module
Introduction to the Python C API: Writing Your First Extension Module - Developers Heaven
July 16, 2025 - Now, let’s write a simple extension module that exposes a C function to Python. This example will demonstrate the basic structure of an extension module and how to interact with the Python C API.
🌐
AskPython
askpython.com › home › calling python scripts from c: a step-by-step guide using python/c api
Calling Python Scripts from C: A Step-by-Step Guide Using Python/C API - AskPython
April 10, 2025 - This article explores how to call Python scripts from a C application using the Python/C API. It provides a step-by-step guide on setting up the API, creating
🌐
Wikibooks
en.wikibooks.org › wiki › Python_Programming › Extending_with_C
Python Programming/Extending with C - Wikibooks, open books for an open world
Microsoft Windows users can use MinGW to compile the extension module from the command line. Assuming gcc is in the path, you can build the extension as follows: ... The above will produce file hello.pyd, a Python Dynamic Module, similar to a DLL.