pthom / cvnp

cvnp: pybind11 casts between numpy and OpenCV, with shared memory
MIT License
53 stars 10 forks source link

Undefined symbol: _ZTIN2cv12MatAllocatorE #9

Closed justincdavis closed 1 year ago

justincdavis commented 1 year ago

I am attempting to use cvnp to handle the type casting for numpy arrays and cv::Mat for a project. I have the following directory structure:

When I build my project everything succeeds with the following warning:

/home/jcdavis/pteranodon/extern/pybind11/include/pybind11/cast.h:38:7: note: type ‘struct type_caster’ itself violates the C++ One Definition Rule 38 | class type_caster : public type_caster_base {}; | ^ /home/jcdavis/pteranodon/extern/cvnp/cvnp/cvnp.h:137:16: note: the incompatible type is defined here 137 | struct type_caster | ^ make[3]: Leaving directory '/home/jcdavis/pteranodon/build_ext'

Once I install my project and attempt to import in python I get the following error:

import pteranodon_ext Traceback (most recent call last): File "", line 1, in ImportError: /home/jcdavis/.local/lib/python3.8/site-packages/pteranodon_ext.cpython-38-x86_64-linux-gnu.so: undefined symbol: _ZTIN2cv12MatAllocatorE

Here is my CMakeLists.txt for reference:

cmake_minimum_required(VERSION 3.4)
project(pteranodon_ext VERSION 0.1.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_CXX_FLAGS "-O3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/cpp/include/headers)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/python)

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extern/pybind11)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extern/cvnp)

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

file (GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/src/*.cpp)
file (GLOB_RECURSE HEADER_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/include/headers/*.hpp)
file (GLOB_RECURSE PYTHON_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/python/*.hpp)

pybind11_add_module(pteranodon_ext 
    ${SOURCE_FILES}
    ${HEADER_FILES}
    ${PYTHON_FILES}
)

target_link_libraries(pteranodon_ext PUBLIC ${OPENCV_LIBS} PRIVATE cvnp pybind11::pybind11)

find_package(PythonInterp 3 REQUIRED)
find_package(PythonLibs 3 REQUIRED)

exec_program(${PYTHON_EXECUTABLE}
             ARGS "-c \"import sysconfig; print(sysconfig.get_paths()['purelib'])\""
             OUTPUT_VARIABLE PYTHON_LIBRARY_DIR
             RETURN_VALUE PYTHON_LIBRARY_DIR_NOT_FOUND
            )
if(PYTHON_LIBRARY_DIR_NOT_FOUND)
    message(FATAL_ERROR "Python library directory not found")
endif()

install(TARGETS pteranodon_ext
  COMPONENT python
  LIBRARY DESTINATION "${PYTHON_LIBRARY_DIR}"
)

My typical build process is as follows:

mkdir -p build && cd build && cmake -S ../ -B ./ && make
cd build && make install

I am wondering if there are any insights on this issues, thank you!

pthom commented 1 year ago

Hi,

When I build my project everything succeeds with the following warning: /home/jcdavis/pteranodon/extern/pybind11/include/pybind11/cast.h:38:7: note: type ‘struct type_caster’ itself violates the C++ One Definition Rule 38 | class type_caster : public type_caster_base {};

May be look at https://github.com/pybind/pybind11/issues/1055 this seems to have helped several people.

import pteranodon_ext Traceback (most recent call last): File "", line 1, in ImportError: /home/jcdavis/.local/lib/python3.8/site-packages/pteranodon_ext.cpython-38-x86_64-linux-gnu.so: > undefined symbol: _ZTIN2cv12MatAllocatorE

_ZTIN2cv12MatAllocatorE when demangled (for example with demangler.com) is equivalent to typeinfo for cv::MatAllocator.

This SO asnwer provides some possible reasons:

justincdavis commented 1 year ago

Thank you for your response!

I resolved the ODR issue fairly quickly with the issues you linked, just had to include the cvnp header wherever I include pybind.

As for the typeinfo for cv::MatAllocator, I have not been able to resolve the issue there. All of my code has no virtual functions/methods. I made the destructor for the only class I use virtual and it also did not affect anything.

I believe the flags are also not an issue.

That leaves the linking order of the librarys. I have tried multiple permutations, but none have worked. Would you be able to provide any insight on the linking order when using cvnp? To me it seems most logical to link opencv, pybind11, cvnp but that ordering does not work.

pthom commented 1 year ago

Maybe you should try to link opencv twice : once at the start and once at the end

justincdavis commented 1 year ago

I ended up solving the issue and it appears it was not a "target_link_libraries" issue, but most likely from an error in the linking of directories or not including the numpy directories. Here is the final CMakeLists.txt I have:

cmake_minimum_required(VERSION 3.4)
project(pteranodon_ext VERSION 0.1.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

include(CTest)
enable_testing()

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_CXX_FLAGS "-O3 -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/cpp/include/headers)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/python)

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extern/pybind11)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extern/cvnp)

set(NUMPY_INCLUDE_DIR "" CACHE FILEPATH "Path to numpy header if cmake can't find them.")
if (NOT ${NUMPY_INCLUDE_DIR} STREQUAL "")
  message( " *** NUMPY_INCLUDE_DIR : ${NUMPY_INCLUDE_DIR}" )
  if(NOT EXISTS ${NUMPY_INCLUDE_DIR}/numpy/ndarrayobject.h)
    message(SEND_ERROR "Can't find numpy/ndarrayobject.h in ${NUMPY_INCLUDE_DIR}")
    endif()
  include_directories(${NUMPY_INCLUDE_DIR})
endif()

find_package(PythonInterp 3 REQUIRED)
find_package(PythonLibs 3 REQUIRED)
find_package(OpenCV REQUIRED)

include_directories(${pybind11_INCLUDE_DIR})
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories(${OpenCV_INCLUDE_DIRS})
include_directories(${OpenCV_INCLUDE_DIRS}/opencv4)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

file (GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/src/*.cpp)
file (GLOB_RECURSE HEADER_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/include/headers/*.hpp)
file (GLOB_RECURSE PYTHON_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/python/*.hpp)

LINK_DIRECTORIES(
  ${OpenCV_LIB_DIR}
)

pybind11_add_module(pteranodon_ext MODULE
    ${SOURCE_FILES}
    ${HEADER_FILES}
    ${PYTHON_FILES}
)

target_link_libraries(pteranodon_ext PRIVATE cvnp pybind11::pybind11 ${OpenCV_LIBS})

exec_program(${PYTHON_EXECUTABLE}
             ARGS "-c \"import sysconfig; print(sysconfig.get_paths()['purelib'])\""
             OUTPUT_VARIABLE PYTHON_LIBRARY_DIR
             RETURN_VALUE PYTHON_LIBRARY_DIR_NOT_FOUND
            )
if(PYTHON_LIBRARY_DIR_NOT_FOUND)
    message(FATAL_ERROR "Python library directory not found")
endif()

install(TARGETS pteranodon_ext
  COMPONENT python
  RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
  LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
  ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}")

add_test(NAME pteranodon_ext_test
  COMMAND ${PYTHON_EXECUTABLE} -c "import pteranodon_ext"
)