Crash upon adding a window to desktop when using Cmake in a DLL on macOS

Hello,

I am building a crossplatform DLL for Windows and macOS that uses JUCE as a GUI. For now it just tries to create a new window and add it to the desktop. The code works well on windows and when using the projucer it works fine for both platforms.

But using CMake on macOS the application crashes as soon as the DLL tries to add the window to the desktop, here is the backtrace of the crash:

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libunwind.dylib               	       0x19894dad4 libunwind::UnwindCursor<libunwind::LocalAddressSpace, libunwind::Registers_arm64>::step(bool) + 828
1   libunwind.dylib               	       0x19894fcb8 _Unwind_RaiseException + 456
2   Usinehh6                      	       0x100a479ac System::_RaiseAtExcept(System::TObject*, void*) + 236
3   Usinehh6                      	       0x100a6c808 System::Internal::Excutils::SignalConverter(NativeUInt, NativeUInt, NativeUInt) + 48
4   ???                           	    0x421b47e577a6 ???
5   libsystem_platform.dylib      	       0x18aefc184 _sigtramp + 56
6   libsystem_pthread.dylib       	       0x18aec6f70 pthread_kill + 288
7   libsystem_c.dylib             	       0x18add3908 abort + 128
8   libc++abi.dylib               	       0x18ae7d44c abort_message + 132
9   libc++abi.dylib               	       0x18ae6b968 demangling_terminate_handler() + 132
10  libc++abi.dylib               	       0x18ae7c710 std::__terminate(void (*)()) + 16
11  libc++abi.dylib               	       0x18ae7c66c std::terminate() + 36
12  JuceDemoModuleARM.5.241104.usr-osxarm64	       0x1709a6258 juce::CoreGraphicsContext::setFill(juce::FillType const&) + 60 (juce_CoreGraphicsContext_mac.mm:462)

Removing the line at juce_CoreGraphicsContext_mac.mm:462 solves the issue, but considering it worked fine with the projucer, it must be an error in my CMake. So is there something wrong in the following CMake ?

cmake_minimum_required(VERSION 3.26)

set(CMAKE_CXX_STANDARD 20)

project(JuceDemoModule VERSION 241104)

set (CMAKE_CXX_STANDARD 20)

FILE(GLOB SRC *.cpp)
FILE(GLOB USINE_SDK ../../../sdk/*.cpp)

add_subdirectory(JUCE)

SET(INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/JUCE/modules ${CMAKE_SOURCE_DIR}/../../../sdk ${CMAKE_SOURCE_DIR}/JuceLibraryCode)
SET(OUTDIR ${CMAKE_SOURCE_DIR}/../../../bin/Graphics)

add_compile_definitions(CMAKE_COMPILATION)
add_library (JuceDemoModule SHARED ${SRC} ${USINE_SDK} ${EXTRAS})
target_compile_definitions(JuceDemoModule
        PUBLIC
        JUCE_ASIO=0
        JUCE_WEB_BROWSER=0
        JUCE_USE_CURL=0
        JUCE_VST3_CAN_REPLACE_VST2=0)


target_link_libraries(JuceDemoModule PRIVATE
        juce::juce_analytics
        juce::juce_audio_basics
        juce::juce_audio_devices
        juce::juce_audio_formats
        juce::juce_audio_processors
        juce::juce_audio_utils
        juce::juce_core
        juce::juce_cryptography
        juce::juce_data_structures
        juce::juce_dsp
        juce::juce_events
        juce::juce_graphics
        juce::juce_gui_basics
        juce::juce_gui_extra
        juce::juce_opengl
        juce::juce_osc
        juce::juce_video
        juce::juce_recommended_config_flags
        juce::juce_recommended_lto_flags
        juce::juce_recommended_warning_flags)


target_compile_options(JuceDemoModule PRIVATE)

target_include_directories(JuceDemoModule PRIVATE ${INCLUDE_DIRS})

Best,
Basile

I don’t see anything obviously wrong there. How are you linking and using the shared library? Are you directly calling C++ functions from the host application, or have you hidden all the C++ symbols and provided a pure C API?

If you’re exposing and using the C++ symbols in the shared library (e.g. calling JUCE functions defined in the library from the host app) then you must be careful that the host app and library have exactly the same set of preprocessor definitions. If this is not the case, then the host app and library will disagree about object layout (amongst other things), which is very likely to cause undefined behaviour, and if you’re lucky, crashes.

If your host app uses JUCE, and your client library also exposes JUCE types via a C++ interface, then you must ensure that both host and client use an identical version of JUCE, as well as ensuring that all preprocessor definitions match.

The application does not use JUCE functions or types, they are all hidden behind the API. However the applcation does also use JUCE in other DLLs through the same API so no JUCE types or function are exported.

I know for a fact the version of JUCE is different between all the DLLs. But why would it work with the projucer and not with CMake ?

Edit: Nevermind I see you mentionned it’s an undefined behaviour. Well thank you very much, I will try to get all the JUCE versions to match !

If you’ve written your own API for the DLL, then I strongly recommend adding the following to your CMakeLists:

    set_target_properties(my_shared_lib_target PROPERTIES
        VISIBILITY_INLINES_HIDDEN TRUE
        C_VISIBILITY_PRESET hidden
        CXX_VISIBILITY_PRESET hidden)

This will ensure that all symbols that are not explicitly exported will remain private to the shared library. As a result, the host app will always use its local copy of any JUCE symbols, rather than loading them from the shared library; similarly, the shared library will only use its internal symbols, rather than using those defined in the host app.

This is standard practice for dynamically-loaded modules such as plugins, and will allow you to use different versions of JUCE in the host and library.

1 Like

That did it, thank you !

1 Like