windows and linux compatible minimal reproducible example from 2020
overview of similar discussion
Here an overview of similar discussions (where I constructed this answer from).
minimal reproducible example
This is for windows and linux, hence there are 2 scripts given for compilation.
Tested under:
- Win 8.1, Python 3.8.3 (anaconda), ctypes 1.1.0, mingw-w64 x86_64-8.1.0-posix-seh-rt_v6-rev0
 
- Linux Fedora 32, Python 3.7.6 (anaconda), ctypes 1.1.0, g++ 10.2.1
 
cpp_code.cpp
extern "C" int my_fct(int n)
{
    int factor = 10;
    return factor * n;
}
compile-linux.sh
#!/bin/bash
g++ cpp_code.cpp -shared -o myso.so
compile-windows.cmd
set gpp="C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin\g++.exe"
%gpp% cpp_code.cpp -shared -o mydll.dll
PAUSE
Python code
from sys import platform
import ctypes
if platform == "linux" or platform == "linux2":
    # https://stackoverflow.com/a/50986803/7128154
    # https://stackoverflow.com/a/52223168/7128154
    dlclose_func = ctypes.cdll.LoadLibrary('').dlclose
    dlclose_func.argtypes = [ctypes.c_void_p]
    fn_lib = './myso.so'
    ctypes_lib = ctypes.cdll.LoadLibrary(fn_lib)
    handle = ctypes_lib._handle
    valIn = 42
    valOut = ctypes_lib.my_fct(valIn)
    print(valIn, valOut)
    del ctypes_lib
    dlclose_func(handle)
elif platform == "win32": # Windows
    # https://stackoverflow.com/a/13129176/7128154
    # https://stackoverflow.com/questions/359498/how-can-i-unload-a-dll-using-ctypes-in-python
    lib = ctypes.WinDLL('./mydll.dll')
    libHandle = lib._handle
    # do stuff with lib in the usual way
    valIn = 42
    valOut = lib.my_fct(valIn)
    print(valIn, valOut)
    del lib
    ctypes.windll.kernel32.FreeLibrary(libHandle)
A more general solution (object oriented for shared libraries with dependencies)
If a shared library has dependencies, this does not necessarily work anymore (but it can - depends on the dependency ^^). I did not investigate the very details, but it looks like the mechanism is the following: library and dependency are loaded. As the dependency is not unloaded, the library can not get unloaded.
I found, that if I include OpenCv (Version 4.2) into my shared library, this messes up the unloading procedure. The following example was only tested on the linux system:
code.cpp
#include <opencv2/core/core.hpp>
#include <iostream> 
extern "C" int my_fct(int n)
{
    cv::Mat1b mat = cv::Mat1b(10,8,(unsigned char) 1 );  // change 1 to test unloading
    
    return mat(0,1) * n;
}
Compile with
g++ code.cpp -shared  -fPIC -Wall -std=c++17 -I/usr/include/opencv4 -lopencv_core -o so_opencv.so
Python code
from sys import platform
import ctypes
class CtypesLib:
    def __init__(self, fp_lib, dependencies=[]):
        self._dependencies = [CtypesLib(fp_dep) for fp_dep in dependencies]
        if platform == "linux" or platform == "linux2":  # Linux
            self._dlclose_func = ctypes.cdll.LoadLibrary('').dlclose
            self._dlclose_func.argtypes = [ctypes.c_void_p]
            self._ctypes_lib = ctypes.cdll.LoadLibrary(fp_lib)
        elif platform == "win32":  # Windows
            self._ctypes_lib = ctypes.WinDLL(fp_lib)
        self._handle = self._ctypes_lib._handle
    def __getattr__(self, attr):
        return self._ctypes_lib.__getattr__(attr)
    def __del__(self):
        for dep in self._dependencies:
            del dep
        del self._ctypes_lib
        if platform == "linux" or platform == "linux2":  # Linux
            self._dlclose_func(self._handle)
        elif platform == "win32":  # Windows
            ctypes.windll.kernel32.FreeLibrary(self._handle)
fp_lib = './so_opencv.so'
ctypes_lib = CtypesLib(fp_lib, ['/usr/lib64/libopencv_core.so'])
valIn = 1
ctypes_lib.my_fct.argtypes = [ctypes.c_int]
ctypes_lib.my_fct.restype = ctypes.c_int
valOut = ctypes_lib.my_fct(valIn)
print(valIn, valOut)
del ctypes_lib
Let me know, when there are any issues with the code examples or the explanation given so far. Also if you know a better way! It would be great, if we could settle the issue once and for all.