diff --git a/cmake/avendish.examples.cmake b/cmake/avendish.examples.cmake index ccb83e5c..d4286f08 100644 --- a/cmake/avendish.examples.cmake +++ b/cmake/avendish.examples.cmake @@ -238,7 +238,7 @@ avnd_make_all( TARGET SampleAccurateControls MAIN_FILE examples/Raw/SampleAccurateControls.hpp MAIN_CLASS examples::SampleAccurateControls - C_NAME avnd_sample_accurate_controls + C_NAME avnd_sampleaccurate # must match SampleAccurateControls::c_name() ) avnd_make_all( diff --git a/include/avnd/binding/golden/prototype.cpp.in b/include/avnd/binding/golden/prototype.cpp.in index 051786a1..e84c7421 100644 --- a/include/avnd/binding/golden/prototype.cpp.in +++ b/include/avnd/binding/golden/prototype.cpp.in @@ -17,6 +17,18 @@ struct config int main(int argc, char** argv) { using type = decltype(avnd::configure())::type; + + // Every object is built for the golden harness (SDK-free, always on), so this + // single check enforces the name invariant for all back-ends at once: when an + // object declares an explicit halp_meta(c_name, ...), the avnd_addon_object / + // avnd_make_* C_NAME must equal it -- they name the same external (the file and + // class Pd/Max load by), and a mismatch ships something that can't be + // instantiated. Objects with no c_name leave the C_NAME authoritative. + static_assert( + avnd::c_name_matches("@AVND_C_NAME@"), + "avendish: the CMake C_NAME does not match this object's halp_meta(c_name, ...). " + "Make the avnd_addon_object / avnd_make_* C_NAME equal the object's c_name."); + return golden::run(argc > 1 ? argv[1] : std::string_view{"golden.json"}); } // clang-format on diff --git a/include/avnd/binding/pd/audio_processor.hpp b/include/avnd/binding/pd/audio_processor.hpp index 6d52b56c..49f164d8 100644 --- a/include/avnd/binding/pd/audio_processor.hpp +++ b/include/avnd/binding/pd/audio_processor.hpp @@ -35,7 +35,10 @@ struct audio_processor_metaclass static inline t_class* g_class{}; static inline audio_processor_metaclass* instance{}; - audio_processor_metaclass(); + // class_name is the Pd class symbol; the setup function passes @AVND_C_NAME@ so + // the registered class matches the external's filename (nullptr -> fall back to + // the introspected name, for callers that construct the metaclass directly). + audio_processor_metaclass(t_symbol* class_name = nullptr); }; template @@ -205,7 +208,7 @@ struct audio_processor }; template -audio_processor_metaclass::audio_processor_metaclass() +audio_processor_metaclass::audio_processor_metaclass(t_symbol* class_name) { audio_processor_metaclass::instance = this; using instance = audio_processor; @@ -249,8 +252,8 @@ audio_processor_metaclass::audio_processor_metaclass() /// Class creation /// g_class = class_new( - symbol_from_name(), (t_newmethod)obj_new, (t_method)obj_free, - sizeof(audio_processor), CLASS_DEFAULT, A_GIMME, 0); + class_name ? class_name : symbol_from_name(), (t_newmethod)obj_new, + (t_method)obj_free, sizeof(audio_processor), CLASS_DEFAULT, A_GIMME, 0); // First port will receive messages CLASS_MAINSIGNALIN(g_class, audio_processor, f); diff --git a/include/avnd/binding/pd/message_processor.hpp b/include/avnd/binding/pd/message_processor.hpp index f0742ff5..0194fca0 100644 --- a/include/avnd/binding/pd/message_processor.hpp +++ b/include/avnd/binding/pd/message_processor.hpp @@ -35,7 +35,10 @@ struct message_processor_metaclass static inline t_class* g_class{}; static inline message_processor_metaclass* instance{}; - message_processor_metaclass(); + // class_name is the Pd class symbol; the setup function passes @AVND_C_NAME@ so + // the registered class matches the external's filename (nullptr -> fall back to + // the introspected name, for callers that construct the metaclass directly). + message_processor_metaclass(t_symbol* class_name = nullptr); }; template @@ -187,7 +190,7 @@ struct message_processor }; template -message_processor_metaclass::message_processor_metaclass() +message_processor_metaclass::message_processor_metaclass(t_symbol* class_name) { message_processor_metaclass::instance = this; using instance = message_processor; @@ -228,8 +231,8 @@ message_processor_metaclass::message_processor_metaclass() /// Class creation /// g_class = class_new( - symbol_from_name(), (t_newmethod)obj_new, (t_method)obj_free, - sizeof(message_processor), CLASS_DEFAULT, A_GIMME, 0); + class_name ? class_name : symbol_from_name(), (t_newmethod)obj_new, + (t_method)obj_free, sizeof(message_processor), CLASS_DEFAULT, A_GIMME, 0); // Connect our methods class_addanything(g_class, (t_method)obj_process); diff --git a/include/avnd/binding/pd/prototype.cpp.in b/include/avnd/binding/pd/prototype.cpp.in index 5cefe039..d548c6a5 100644 --- a/include/avnd/binding/pd/prototype.cpp.in +++ b/include/avnd/binding/pd/prototype.cpp.in @@ -10,17 +10,23 @@ using type = decltype(avnd::configure())::type; extern "C" AVND_EXPORTED_SYMBOL void @AVND_C_NAME@_setup() { - [] { + // Register the class under the external's own name (the file Pd just loaded is + // @AVND_C_NAME@.pd_*, and this function is @AVND_C_NAME@_setup). Pd looks up a + // single-object external by filename, so the class symbol must match it exactly; + // otherwise the object loads but the class is registered under a different name, + // Pd never finds it, and its loader retries until it hits the recursion limit. + t_symbol* const avnd_class_name = gensym("@AVND_C_NAME@"); + [avnd_class_name] { if constexpr( avnd::monophonic_audio_processor || avnd::polyphonic_audio_processor) { // If we're an audio effect, make a type with the whole DSP stuff - static const pd::audio_processor_metaclass instance{}; + static const pd::audio_processor_metaclass instance{avnd_class_name}; } else { // Simpler case which just processes messages - static const pd::message_processor_metaclass instance{}; + static const pd::message_processor_metaclass instance{avnd_class_name}; } }(); } diff --git a/include/avnd/wrappers/metadatas.hpp b/include/avnd/wrappers/metadatas.hpp index 42181470..6ec8537d 100644 --- a/include/avnd/wrappers/metadatas.hpp +++ b/include/avnd/wrappers/metadatas.hpp @@ -318,6 +318,21 @@ static constexpr auto get_static_symbol() return avnd::get_c_identifier(); } +// True unless the object declares an explicit c_name (halp_meta(c_name, ...)) that +// differs from the external's CMake C_NAME. The per-backend prototypes call this so +// a C_NAME that disagrees with the object's own c_name is a compile error instead of +// an external whose file name and registered class name silently differ (which Pd and +// Max load by, so it can't be instantiated). Objects with no c_name meta leave the +// CMake C_NAME authoritative and always pass. +template +consteval bool c_name_matches(std::string_view cmake_c_name) +{ + if constexpr(avnd::has_c_name) + return avnd::get_c_name() == cmake_c_name; + else + return true; +} + template