CMake for dynamic linked library AND lib, minor issue

I’ve successfully followed some advice for using CMake to make a JUCE dynamic linked library.

Now I want to use JUCE functions in another project by linking against a JUCE lib. I think it’s necessary to set JUCE_DLL_BUILD=1 to export the functions and get a lib file too.

So I tried adding this line to CMakeLists.txt

add_compile_definitions(JUCE_DLL_BUILD=1)

However, now when building the solution I get a single error:

Error C2247 'juce::Thread::operator delete' not accessible because 'juce::dsp::BackgroundMessageQueue' uses 'private' to inherit from 'juce::Thread'

Any advice on how to proceed? Thank you.

Seems foolish but if I change that line to
class BackgroundMessageQueue : public Thread
I can get a .lib

Thanks for raising, this should be fixed on develop now:

I’m not sure exactly what approach you ended going with to build a DLL, but the cleanest approach is probably something like this:

add_library(my_juce_dll SHARED)

target_link_libraries(my_juce_dll
    PRIVATE
        juce::juce_audio_utils
        juce::juce_dsp
    PUBLIC
        juce::juce_recommended_config_flags
        juce::juce_recommended_lto_flags
        juce::juce_recommended_warning_flags)

target_compile_definitions(my_juce_dll
    PUBLIC
        # Required flags for DLL build
        JUCE_DLL_BUILD=1
        JUCE_STANDALONE_APPLICATION=1
        # Other feature flags
        JUCE_WEB_BROWSER=0 
        JUCE_USE_CURL=0
    INTERFACE
        $<TARGET_PROPERTY:my_juce_dll,COMPILE_DEFINITIONS>)

target_include_directories(my_juce_dll
    INTERFACE
        $<TARGET_PROPERTY:my_juce_dll,INCLUDE_DIRECTORIES>)

set_target_properties(my_juce_dll PROPERTIES
    POSITION_INDEPENDENT_CODE TRUE
    VISIBILITY_INLINES_HIDDEN TRUE
    C_VISIBILITY_PRESET hidden
    CXX_VISIBILITY_PRESET hidden)

This will produce a target named my_juce_dll which properly exports the necessary preprocessor definitions and include directories, along with the recommended JUCE flags.

1 Like

Thanks for that fix and sample code. Now I’m trying to add VST2 support and might be facing a similar issue as before. I was able to use target_include_directories with a path to a directory holding the VST2 SDK. So my DLL/lib are built, and there’s no error about #include <pluginterfaces/vst2.x/aeffect.h>.

Next I have a C++ project that links to the lib but it has unresolved external symbols. This project worked fine before trying to add VST2 support. What could have gone wrong in building the lib, or maybe something is missing a preprocessor definition? I’m using
add_compile_definitions(JUCE_DLL_BUILD=1) add_compile_definitions(JUCE_STANDALONE_APPLICATION=1)

Concise list of missing symbols:

  • “const juce::AudioProcessor::`vftable’” (??_7AudioProcessor@juce@@6B@)
  • “public: static class juce::ModifierKeys juce::ModifierKeys::currentModifiers” (?currentModifiers@ModifierKeys@juce@@2V12@A)
  • “public: static struct juce::SingletonHolder<class juce::ContentSharer,class juce::CriticalSection,0> juce::ContentSharer::singletonHolder”
  • “private: static class juce::JUCEApplicationBase * juce::JUCEApplicationBase::appInstance” (?appInstance@JUCEApplicationBase@juce@@0PEAV12@EA)
  • “public: __cdecl juce::ResizableBorderComponent::Zone::Zone(class juce::ResizableBorderComponent::Zone const &)”
  • "public: static struct juce::SingletonHolder<class juce::ModalComponentManager,class juce::DummyCriticalSection,0>
  • “public: static class juce::JUCEApplicationBase * (__cdecl* juce::JUCEApplicationBase::createInstance)(void)”
  • "public: static struct juce::SingletonHolder<class juce::PushNotifications,class juce::CriticalSection,0>
  • “const juce::GenericAudioProcessorEditor::`vftable’” (??_7GenericAudioProcessorEditor@juce@@6B@)
all missing symbols

1>------ Build started: Project: TD-JUCE-Reverb, Configuration: Release x64 ------
1> Creating library C:/TD-JUCE/Release/TD-JUCE-Reverb.lib and object C:/TD-JUCE/Release/TD-JUCE-Reverb.exp
1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol “const juce::AudioProcessor::vftable'" (??_7AudioProcessor@juce@@6B@) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "public: static class juce::ModifierKeys juce::ModifierKeys::currentModifiers" (?currentModifiers@ModifierKeys@juce@@2V12@A) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "public: static struct juce::SingletonHolder<class juce::ContentSharer,class juce::CriticalSection,0> juce::ContentSharer::singletonHolder" (?singletonHolder@ContentSharer@juce@@2U?$SingletonHolder@VContentSharer@juce@@VCriticalSection@2@$0A@@2@A) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "private: static class juce::JUCEApplicationBase * juce::JUCEApplicationBase::appInstance" (?appInstance@JUCEApplicationBase@juce@@0PEAV12@EA) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "public: __cdecl juce::ResizableBorderComponent::Zone::Zone(class juce::ResizableBorderComponent::Zone const &)" (??0Zone@ResizableBorderComponent@juce@@QEAA@AEBV012@@Z) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "public: static struct juce::SingletonHolder<class juce::ModalComponentManager,class juce::DummyCriticalSection,0> juce::ModalComponentManager::singletonHolder" (?singletonHolder@ModalComponentManager@juce@@2U?$SingletonHolder@VModalComponentManager@juce@@VDummyCriticalSection@2@$0A@@2@A) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "public: static class juce::JUCEApplicationBase * (__cdecl* juce::JUCEApplicationBase::createInstance)(void)" (?createInstance@JUCEApplicationBase@juce@@2P6APEAV12@XZEA) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "public: static struct juce::SingletonHolder<class juce::PushNotifications,class juce::CriticalSection,0> juce::PushNotifications::singletonHolder" (?singletonHolder@PushNotifications@juce@@2U?$SingletonHolder@VPushNotifications@juce@@VCriticalSection@2@$0A@@2@A) 1>TD-JUCE-Reverb.obj : error LNK2001: unresolved external symbol "const juce::GenericAudioProcessorEditor::vftable’” (??_7GenericAudioProcessorEditor@juce@@6B@)
1>C:\TD-JUCE\Release\TD-JUCE-Reverb.dll : fatal error LNK1120: 9 unresolved externals

P.S. If that seems like a dead-end, I would happily take more advice about CMake. I’m trying to use the code you provided but am having trouble fitting it in the context of this page https://github.com/juce-framework/JUCE/blob/master/docs/CMake%20API.md and https://github.com/juce-framework/JUCE/blob/master/examples/CMake/AudioPlugin/CMakeLists.txt

For context my project’s folder is C:/TD-JUCE.

I do this in CMD:
cd C:\TD-JUCE\thirdparty\JUCE_6
cmake -B cmake-build-install -DCMAKE_INSTALL_PREFIX=C:\TD-JUCE\thirdparty\JUCE_6\my_install
Then build:
cmake --build cmake-build-install --target install

Then in my main C:\TD-JUCE\CMakeLists.txt:

project(TD-JUCE VERSION 0.0.1)
# add_subdirectory(${PROJECT_SOURCE_DIR}/thirdparty/JUCE_6) # choosing find_package instead
find_package(JUCE CONFIG REQUIRED)  
add_library(TD-JUCE SHARED)
# then code you shared replacing my_juce_dll with TD-JUCE

Then in C:/TD-JUCE/build:
cmake -DCMAKE_PREFIX_PATH=C:/TD-JUCE/thirdparty/JUCE_6/my_install ..

Then I get variations of an error discussed on the forums (Learning CMake done right with JUCE 6).

CMake Error at CMakeLists.txt:105 (add_library): Target "TD-JUCE" links to target "juce::audio_basics" but the target was not found. Perhaps a find_package() call is missing for an IMPORTED target, or an ALIAS target is missing?

Thanks!

The target is juce::juce_audio_basics, not juce::audio_basics.

Thank you for catching that! Now the CMake is working better. It builds a DLL with JUCE_DLL_BUILD=1. I have a subdirectory project that does add_library and target_link_libraries(${PROJECT_NAME} PUBLIC TD-JUCE). It also adds add_compile_definitions(JUCE_DLL=1).

When I open the subdirectory project in visual studio I see the preprocessor definition JUCE_DLL_BUILD=1, which causes issues. Everything builds fine if I manually remove it. What’s the correct way for this subdirectory to ignore this definition? I tried PRIVATE/INTERFACE variations of building the primary DLL but they didn’t prevent the inheritance during target_link_libraries.

target_compile_definitions(TD-JUCE
    # omitting other defs for brevity
    #PUBLIC
    #    JUCE_DLL_BUILD=1
    #PRIVATE
    #    JUCE_DLL_BUILD=1
    INTERFACE
        JUCE_DLL_BUILD=1

add_compile_definitions(JUCE_DLL=1) applies to all targets defined in the CMakeLists.txt where it is called, and any subdirectory added afterwards with add_subdirectory.

You should really avoid using add_compile_definitions if possible.

Thanks. My main DLL project isn’t using add_compile_definitions. It’s just what reuk recommended.

target_compile_definitions(TD-JUCE
    PUBLIC
        # Required flags for DLL build
        JUCE_DLL_BUILD=1
        JUCE_STANDALONE_APPLICATION=1
        # Other feature flags
        JUCE_WEB_BROWSER=0 
        JUCE_USE_CURL=0
    INTERFACE
        $<TARGET_PROPERTY:TD-JUCE,COMPILE_DEFINITIONS>)

In the subdirectory, I can similarly avoid add_compile_definitions by doing target_compile_definitions(${PROJECT_NAME} PUBLIC JUCE_DLL=1)

But once I call target_link_libraries(${PROJECT_NAME} PUBLIC TD-JUCE) how do I prevent inheritance of JUCE_DLL_BUILD=1?

JUCE_DLL_BUILD=1 should be set PRIVATEly on the DLL target, otherwise it indeed gets propagated to targets that depend on it.

I don’t think @reuk’s code works for your use case.

That makes sense. I’m seeing that PRIVATE will put it in COMPILE_DEFINITIONS but then the generator expression INTERFACE $<TARGET_PROPERTY:TD-JUCE,COMPILE_DEFINITIONS> makes it inherited again.

I’ve gotten around it by not using the generator expression and instead specifying everything either private or public.

target_compile_definitions(TD-JUCE
    PUBLIC
        JUCE_STANDALONE_APPLICATION=1
        JUCE_WEB_BROWSER=0
        JUCE_USE_CURL=0
        # the generator expression originally made these:
        JUCE_MODULE_AVAILABLE_juce_audio_basics=1
        JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1
        NDEBUG=1
        _NDEBUG=1
        JUCE_MODULE_AVAILABLE_juce_core=1
        JUCE_MODULE_AVAILABLE_juce_audio_devices=1
        JUCE_MODULE_AVAILABLE_juce_events=1
        JUCE_MODULE_AVAILABLE_juce_audio_formats=1
        JUCE_MODULE_AVAILABLE_juce_audio_processors=1
        JUCE_MODULE_AVAILABLE_juce_gui_extra=1
        JUCE_MODULE_AVAILABLE_juce_gui_basics=1
        JUCE_MODULE_AVAILABLE_juce_graphics=1
        JUCE_MODULE_AVAILABLE_juce_data_structures=1
        JUCE_MODULE_AVAILABLE_juce_audio_utils=1
        JUCE_MODULE_AVAILABLE_juce_cryptography=1
        JUCE_MODULE_AVAILABLE_juce_dsp=1
        JUCE_MODULE_AVAILABLE_juce_opengl=1
        JUCE_MODULE_AVAILABLE_juce_video=1
    PRIVATE
        JUCE_DLL_BUILD=1
)

Thanks for your help throughout :+1:

Sorry for thinking about this only now, but you can actually filter out JUCE_DLL_BUILD using the following code:

$<FILTER:$<TARGET_PROPERTY:TD-JUCE,COMPILE_DEFINITIONS>,EXCLUDE,JUCE_DLL_BUILD>

as long as you use CMake 3.15 or later.

1 Like