0

I am trying to import a c++ function for use in Python(3.9) on MacOS. My project has the following structure,

.
├── CMakeLists.txt
├── cmake-build-debug
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── Testing
│   ├── build.ninja
│   ├── cmake_install.cmake
│   └── libDENCLUS.dylib
├── denclus.py
├── library.cpp
└── library.h

with CMakeLists.txt as,

cmake_minimum_required(VERSION 3.22)
project(DENCLUS)

set(CMAKE_CXX_STANDARD 14)

add_library(DENCLUS SHARED library.cpp)

the header file,

#ifndef DENCLUS_LIBRARY_H
#define DENCLUS_LIBRARY_H

extern "C" void hello();

#endif //DENCLUS_LIBRARY_H

the source file,

#include "library.h"

#include <iostream>

void hello() {
    std::cout << "Hello, World!" << std::endl;
}

and the python file,

from ctypes.util import find_library
import ctypes
import os

cwd = os.getcwd()
lib = f'{cwd}/cmake-build-debug/libDENCLUS.dylib'
if find_library(lib):
    libx = ctypes.cdll.LoadLibrary(lib)
    libx.hello()
else:
    raise OSError("Could not find lib.")

ctypes find_library returns None and the subsequent block which calls the function hello is not executed if i specify the absolute path to the .dylib file, or if I call it as find_library(DENCLUS) or any similar permutation. How can I get my python code to find the library and call the function?

EDIT:

I can call the c++ function outside of the if block. I think there is an issue with find_library on MacOS.

Kyle
  • 115
  • 7

1 Answers1

0

According to [Python.Docs]: ctypes - Finding shared libraries (emphasis is mine):

ctypes.util.find_library(name)

      Try to find a library and return a pathname. name is the library name without any prefix like lib, suffix like .so, .dylib or version number (this is the form used for the posix linker option -l). If no library can be found, returns None.

That's why find_library returns None.

The same resource also states (although I don't necessarily agree with):

If wrapping a shared library with ctypes, it may be better to determine the shared library name at development time, and hardcode that into the wrapper module instead of using find_library() to locate the library at runtime.

The million dollar question: if the path of the library is already known, why the need of calling find_library? It makes no sense, as it simply beats its purpose.

Simply use:

import ctypes as ct
import os


cwd = os.getcwd()
lib_path = f"{cwd}/cmake-build-debug/libDENCLUS.dylib"
lib = ct.cdll.LoadLibrary(lib_path)
hello = lib.hello
# @TODO: !!! Define argtypes ans restype for any function called via CTypes !!!
hello.argtypes = ()
hello.restype = None
hello()

Regarding the @TODO (even if for this particular case it's not necessary), check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer).

If for some reason (that remains unknown to me at this point), you feel the urge to use find_library, here's how to do it:

os.environ["DYLD_LIBRARY_PATH"] = os.pathsep.join((os.environ.get("DYLD_LIBRARY_PATH", ""), os.path.join(os.getcwd(), "cmake-build-debug")))
lib_path = ct.util.find_library("DENCLUS")
# ...
CristiFati
  • 38,250
  • 9
  • 50
  • 87