3434
3535#include < atomic>
3636
37+ #import < objc/runtime.h>
38+
3739
3840// JUCE hotfix: because JUCE directly includes the .cpp/.mm files from this SDK instead of properly
3941// compiling the ARA_IPC_Library, these switches allow for skipping the host- or the
4648#endif
4749
4850
51+ // crude hack to use std::bit_width in C++17
52+ #if __cplusplus < 202002L
53+ namespace std
54+ {
55+ constexpr size_t bit_width (size_t n)
56+ {
57+ return (n <= 1 ) ? n : 1 + bit_width (n / 2 );
58+ }
59+ }
60+ #endif
61+
62+
4963namespace ARA {
5064namespace IPC {
5165
@@ -158,7 +172,7 @@ void sendMessage (MessageID messageID, std::unique_ptr<MessageEncoder> && encode
158172 // remote side until the first message is sent. However, if another channel is assigned
159173 // its (same) callHostBlock, then the pending callHostBlock for the first channel is
160174 // lost for some reason. We need to send an empty dummy message to work around this bug,
161- // which them must be filtered in ARAIPCAUProxyHostCommandHandler ().
175+ // which them must be filtered before calling routeReceivedDictionary ().
162176 [audioUnitChannel callAudioUnit: [NSDictionary dictionary ]];
163177 }
164178
@@ -237,25 +251,13 @@ void sendMessage (MessageID messageID, std::unique_ptr<MessageEncoder> && encode
237251#endif // !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
238252
239253
240- #if defined (__GNUC__)
241- _Pragma (" GCC diagnostic push" )
242- _Pragma (" GCC diagnostic ignored \" -Wunguarded-availability\" " )
243- #endif
254+ // host side: proxy plug-in C adapter
255+ #if !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
244256
245- ARA_MAP_IPC_REF (AudioUnitMessageChannel, ARAIPCMessageChannelRef)
246257ARA_MAP_IPC_REF (AUProxyPlugIn, ARAIPCProxyPlugInRef)
247258
248- #if defined (__GNUC__)
249- _Pragma (" GCC diagnostic pop" )
250- #endif
251-
252-
253259extern "C" {
254260
255-
256- // host side: proxy plug-in C adapter
257- #if !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
258-
259261ARAIPCProxyPlugInRef ARA_CALL ARAIPCAUProxyPlugInInitialize (AUAudioUnit * _Nonnull audioUnit,
260262 ARAMainThreadWaitForMessageDelegate _Nullable waitForMessageDelegate,
261263 void * _Nullable delegateUserData)
@@ -285,6 +287,8 @@ void ARA_CALL ARAIPCAUProxyPlugInUninitialize (ARAIPCProxyPlugInRef _Nonnull pro
285287 delete fromIPCRef (proxyPlugInRef);
286288}
287289
290+ } // extern "C"
291+
288292#endif // !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY
289293
290294
@@ -314,9 +318,11 @@ void ARA_CALL ARAIPCAUProxyPlugInUninitialize (ARAIPCProxyPlugInRef _Nonnull pro
314318
315319AUProxyHost* _proxyHost;
316320
321+ typedef ARA_IPC_REF (ARAIPCMessageChannelRef);
322+ ARA_MAP_IPC_REF (AudioUnitMessageChannel, ARAIPCMessageChannelRef)
317323
318- ARAIPCMessageChannelRef _Nullable ARA_CALL ARAIPCAUProxyHostInitializeMessageChannel (NSObject <AUMessageChannel> * _Nonnull audioUnitChannel,
319- bool isMainThreadChannel)
324+ static ARAIPCMessageChannelRef _Nullable initializeMessageChannel (NSObject <AUMessageChannel> * _Nonnull audioUnitChannel,
325+ bool isMainThreadChannel)
320326{
321327 // \todo the connection currently stores the creation thread as main thread for Windows compatibility,
322328 // so we need to make sure the proxy is created on the main thread
@@ -343,28 +349,226 @@ ARAIPCMessageChannelRef _Nullable ARA_CALL ARAIPCAUProxyHostInitializeMessageCha
343349 return result;
344350}
345351
346- NSDictionary * _Nonnull ARA_CALL ARAIPCAUProxyHostCommandHandler (ARAIPCMessageChannelRef _Nonnull messageChannelRef, NSDictionary * _Nonnull message)
352+ #endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
353+
354+
355+ // plug-in side: NSObject<AUMessageChannel> implementation
356+ #if !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
357+
358+
359+ /*
360+ The following code implements the below class at runtime to work around potential ObjC class name conflicts
361+
362+ @interface ARAIPCAUMessageChannelImpl : NSObject<AUMessageChannel>
363+ @end
364+
365+ @implementation ARAIPCAUMessageChannel {
366+ ARA::IPC::ARAIPCMessageChannelRef _messageChannelRef;
367+ }
368+
369+ @synthesize callHostBlock = _callHostBlock;
370+
371+ - (instancetype)initAsMainThreadChannel:(BOOL) isMainThreadChannel {
372+ self = [super init];
373+
374+ if (self == nil) { return nil; }
375+
376+ _callHostBlock = nil;
377+ _messageChannelRef = initializeMessageChannel (self, isMainThreadChannel);
378+
379+ return self;
380+ }
381+
382+ - (NSDictionary * _Nonnull)callAudioUnit:(NSDictionary *)message {
383+ if ([message count])
384+ static_cast<ProxyHostMessageChannel *> (fromIPCRef (_messageChannelRef))->routeReceivedDictionary (message);
385+ return [NSDictionary dictionary];
386+ }
387+
388+ @end
389+ */
390+
391+ // helper to add ivars to an ObjC class
392+ template <typename T>
393+ void addObjCIVar (Class cls, const char * name)
394+ {
395+ constexpr auto size { sizeof (T) };
396+ constexpr auto alignment { static_cast <uint8_t > (std::bit_width (size)) };
397+ [[maybe_unused]] const auto success = class_addIvar (cls, name, size, alignment, @encode (T));
398+ ARA_INTERNAL_ASSERT (success);
399+ }
400+
401+ // helpers to get/set a scalar ivar of an ObjC object
402+ template <typename T>
403+ inline T getScalarIVar (id self, Ivar ivar)
404+ {
405+ const auto offset { ivar_getOffset (ivar) };
406+ const auto bytes = static_cast <const unsigned char *> ((__bridge void *) self);
407+ return *reinterpret_cast <const T *> (bytes + offset);
408+ }
409+
410+ template <typename T>
411+ inline void setScalarIVar (id self, Ivar ivar, T value)
347412{
348- if ([message count ]) // \todo filter dummy message sent in ProxyPlugInMessageChannel(), see there
413+ const auto offset { ivar_getOffset (ivar) };
414+ auto bytes = static_cast <unsigned char *> ((__bridge void *) self);
415+ *reinterpret_cast <T *> (bytes + offset) = value;
416+ }
417+
418+
419+ // optimization of object_getInstanceVariable: cache the ivars of our class
420+ Ivar messageChannelRefIvar { nullptr };
421+ Ivar callHostBlockIvar { nullptr };
422+
423+
424+ // methods of our class
425+ extern " C" {
426+
427+ static CallHostBlock callHostBlock_getter (id self, SEL /* _cmd*/ )
428+ {
429+ return object_getIvar (self, callHostBlockIvar);
430+ }
431+
432+ static void callHostBlock_setter (id self, SEL /* _cmd*/ , CallHostBlock newBlock)
433+ {
434+ id oldBlock { object_getIvar (self, callHostBlockIvar) };
435+ if (oldBlock != newBlock)
436+ {
437+ if (oldBlock)
438+ Block_release ((__bridge void *)oldBlock);
439+ if (newBlock)
440+ newBlock = (__bridge CallHostBlock) Block_copy ((__bridge void *)newBlock);
441+ object_setIvar (self, callHostBlockIvar, newBlock);
442+ }
443+ }
444+
445+ static NSDictionary * _Nonnull callAudioUnit_impl (id self, SEL /* _cmd*/ , NSDictionary * message)
446+ {
447+ const auto messageChannelRef { getScalarIVar<ARA::IPC::ARAIPCMessageChannelRef> (self, messageChannelRefIvar) };
448+ if ([message count ]) // \todo filter dummy message sent in ProxyPlugInMessageChannel, see there
349449 static_cast <ProxyHostMessageChannel *> (fromIPCRef (messageChannelRef))->routeReceivedDictionary (message);
350450 return [NSDictionary dictionary ]; // \todo it would yield better performance if -callAudioUnit: would allow nil as return value
351451}
352452
353- void ARA_CALL ARAIPCAUProxyHostUninitialize (void )
453+ } // extern "C"
454+
455+
456+ // creation of the ARAIPCAUMessageChannelImpl Class
457+ static Class createMessageChannelObjCClass ()
354458{
355- if (_proxyHost)
356- delete _proxyHost;
459+ // create a unique class name by appending a unique address to the base name
460+ std::string className { " ARAIPCAUMessageChannelImpl" + std::to_string (reinterpret_cast <intptr_t > (&messageChannelRefIvar)) };
461+ ARA_INTERNAL_ASSERT (!objc_getClass (className.c_str ()));
462+
463+ // allocate class
464+ Class cls { objc_allocateClassPair ([NSObject class ], className.c_str (), 0 ) };
465+ ARA_INTERNAL_ASSERT (cls);
466+
467+ // add iVars
468+ addObjCIVar<intptr_t > (cls, " _messageChannelRef" );
469+ addObjCIVar<CallHostBlock> (cls, " _callHostBlock" );
470+
471+ // add properties
472+ objc_property_attribute_t attr[] { { " T" , " @?" }, // type block
473+ { " C" , " " }, // C = copy
474+ { " V" , " _callHostBlock" } }; // backing i-var
475+ [[maybe_unused]] auto success { class_addProperty (cls, " callHostBlock" , attr, sizeof (attr) / sizeof (attr[0 ])) };
476+
477+ // add getters/setters
478+ success = class_addMethod (cls, @selector (callHostBlock ), (IMP )callHostBlock_getter, " @@:" );
479+ ARA_INTERNAL_ASSERT (success);
480+ class_addMethod (cls, @selector (setCallHostBlock: ), (IMP )callHostBlock_setter, " v@:@" );
481+ ARA_INTERNAL_ASSERT (success);
482+
483+ // add methods
484+ class_addMethod (cls, @selector (callAudioUnit: ), (IMP )callAudioUnit_impl, " @@:@" );
485+ ARA_INTERNAL_ASSERT (success);
486+
487+ // declare conformance to <AUMessageChannel>
488+ const auto messageChannelProtocol { objc_getProtocol (" AUMessageChannel" ) };
489+ ARA_INTERNAL_ASSERT (messageChannelProtocol);
490+ success = class_addProtocol (cls, (Protocol * _Nonnull)messageChannelProtocol);
491+ ARA_INTERNAL_ASSERT (success);
492+
493+ // activate class
494+ objc_registerClassPair (cls);
495+
496+ // update cached ivar ptrs after activation
497+ messageChannelRefIvar = class_getInstanceVariable (cls, " _messageChannelRef" );
498+ ARA_INTERNAL_ASSERT (messageChannelRefIvar);
499+ callHostBlockIvar = class_getInstanceVariable (cls, " _callHostBlock" );
500+ ARA_INTERNAL_ASSERT (callHostBlockIvar);
501+
502+ return cls;
357503}
358504
359- #endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
505+ // creation and designated initialisation of instances of ARAIPCAUMessageChannelImpl Class
506+ static NSObject <AUMessageChannel> * createAndInitMessageChannel (Class cls, bool isMainThreadChannel)
507+ {
508+ NSObject <AUMessageChannel> * obj { class_createInstance (cls, 0 ) };
509+ ARA_INTERNAL_ASSERT (obj);
510+ ARA_INTERNAL_ASSERT ([obj conformsToProtocol: @protocol (AUMessageChannel)]);
511+ obj = [obj init ];
512+ ARA_INTERNAL_ASSERT (obj);
513+
514+ object_setIvar (obj, callHostBlockIvar, nil );
515+
516+ const auto messageChannelRef { initializeMessageChannel (obj, isMainThreadChannel) };
517+ setScalarIVar<ARA::IPC::ARAIPCMessageChannelRef> (obj, messageChannelRefIvar, messageChannelRef);
518+
519+ return obj;
520+ }
521+
522+
523+ NSObject <AUMessageChannel> * __strong _mainMessageChannel { nil };
524+ NSObject <AUMessageChannel> * __strong _otherMessageChannel { nil };
525+
526+ static void createSharedMessageChannels ()
527+ {
528+ ARA_INTERNAL_ASSERT (!_mainMessageChannel && !_otherMessageChannel);
529+
530+ const auto cls { createMessageChannelObjCClass () };
531+ _mainMessageChannel = createAndInitMessageChannel (cls, true );
532+ _otherMessageChannel = createAndInitMessageChannel (cls, false );
533+ }
534+
535+ extern " C" id <AUMessageChannel> _Nullable ARA_CALL ARAIPCAUProxyHostMessageChannelFor (NSString * _Nonnull channelName)
536+ {
537+ if ([channelName isEqualToString: ARA_AUDIOUNIT_MAIN_THREAD_MESSAGES_UTI])
538+ {
539+ if (!_mainMessageChannel)
540+ createSharedMessageChannels ();
541+ return _mainMessageChannel;
542+ }
543+ else if ([channelName isEqualToString: ARA_AUDIOUNIT_OTHER_THREADS_MESSAGES_UTI])
544+ {
545+ if (!_otherMessageChannel)
546+ createSharedMessageChannels ();
547+ return _otherMessageChannel;
548+ }
549+ return nil ;
550+ }
360551
361552API_AVAILABLE_END
362553
554+ __attribute__ ((destructor))
555+ static void destroySharedMessageChannelsIfNeeded ()
556+ {
557+ if (@available (macOS 13.0 , iOS 16.0 , *))
558+ {
559+ if (_proxyHost)
560+ delete _proxyHost;
561+
562+ _mainMessageChannel = nil ;
563+ _otherMessageChannel = nil ;
564+ }
565+ }
566+
567+ #endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY
363568
364569_Pragma (" GCC diagnostic pop" )
365570
366571
367- } // extern "C"
368572} // namespace IPC
369573} // namespace ARA
370574
0 commit comments