JUCE_ObjCExtraSuffix no longer needed


#1

Just an FYI… I’ve been doing some internal obj-C refactoring, and for various complicated reasons, all my internal obj-C classes are now defined dynamically at runtime, rather than using normal obj-C syntax.

A side-effect of this is that each class is dynamically given a randomised name, so the awfulness caused by obj-C name clashes should be a thing of the past, and I’ve been able to remove the JUCE_ObjCExtraSuffix flag, which is no longer needed. It also means that even if the same DLL is loaded multiple times (e.g. if a host loads the same binary as an AU and VST simultaneously), there should be no cross-linkage problems.

I’ve had to do a lot of internal hacking in this check-in, so would appreciate anyone letting me know if I’ve broken anything! Thanks!


#2

Great,
I’ve read on the VST list that there was 2 approach to solve this kind of problem, the suffix and the runtime generation.
I was wondering how to do that, I will have a look at your implementation. :slight_smile:


#3

There’s no need to do anything at all now, it’s all handled internally.


#4

Yes I know this is just for my self-improvement :slight_smile:


#5

i just tried to compile the latest audio plugin demo (to look for the logic/font-issue) but i get some compiler errors:

In file included from /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/juce_gui_basics.cpp:266, from /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/juce_gui_basics.mm:26: /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm: In constructor 'juce::JuceNSViewClass::JuceNSViewClass()': /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:1201: error: class 'juce::JuceNSViewClass' does not have any field named 'ObjCClass' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:1201: error: no matching function for call to 'juce::ObjCClass<NSView>::ObjCClass()' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:141: note: candidates are: juce::ObjCClass<SuperclassType>::ObjCClass(const juce::ObjCClass<SuperclassType>&) [with SuperclassType = NSView] /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:61: note: juce::ObjCClass<SuperclassType>::ObjCClass(const char*) [with SuperclassType = NSView] /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm: In constructor 'juce::JuceNSWindowClass::JuceNSWindowClass()': /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:1610: error: class 'juce::JuceNSWindowClass' does not have any field named 'ObjCClass' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:1610: error: no matching function for call to 'juce::ObjCClass<NSWindow>::ObjCClass()' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:141: note: candidates are: juce::ObjCClass<SuperclassType>::ObjCClass(const juce::ObjCClass<SuperclassType>&) [with SuperclassType = NSWindow] /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:61: note: juce::ObjCClass<SuperclassType>::ObjCClass(const char*) [with SuperclassType = NSWindow] In file included from /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/juce_gui_basics.cpp:268, from /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/juce_gui_basics.mm:26: /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_MainMenu.mm: In constructor 'juce::JuceMainMenuHandler::JuceMenuCallbackClass::JuceMenuCallbackClass()': /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_MainMenu.mm:398: error: class 'juce::JuceMainMenuHandler::JuceMenuCallbackClass' does not have any field named 'ObjCClass' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_MainMenu.mm:398: error: no matching function for call to 'juce::ObjCClass<NSObject>::ObjCClass()' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:141: note: candidates are: juce::ObjCClass<SuperclassType>::ObjCClass(const juce::ObjCClass<SuperclassType>&) [with SuperclassType = NSObject] /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:61: note: juce::ObjCClass<SuperclassType>::ObjCClass(const char*) [with SuperclassType = NSObject] In file included from /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/juce_gui_basics.cpp:272, from /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/juce_gui_basics.mm:26: /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_FileChooser.mm: In constructor 'juce::FileChooserDelegateClass::FileChooserDelegateClass()': /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_FileChooser.mm:30: error: class 'juce::FileChooserDelegateClass' does not have any field named 'ObjCClass' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/native/juce_mac_FileChooser.mm:30: error: no matching function for call to 'juce::ObjCClass<NSObject>::ObjCClass()' /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:141: note: candidates are: juce::ObjCClass<SuperclassType>::ObjCClass(const juce::ObjCClass<SuperclassType>&) [with SuperclassType = NSObject] /Users/Christian/Downloads/julianstorer-JUCE-1089e3d/extras/audio plugin demo/Builds/MacOSX/../../../../modules/juce_gui_basics/../juce_core/native/juce_osx_ObjCHelpers.h:61: note: juce::ObjCClass<SuperclassType>::ObjCClass(const char*) [with SuperclassType = NSObject]


#6

Ah, I didn’t try it with GCC. Thanks, I’ll check that.

(Why are you still using GCC? Try LLVM, you’ll never look back!)


#7

(Why are you still using GCC? Try LLVM, you’ll never look back!)

(intuition, Im just using XCode 3.2.6 out-of-the-box and anything! (including VST64 with assembler etc…) works and i don’t want change the platform to early - because i’m just finishing a project.
new plattfom - new (obscure, hard to find) issues- you never now :slight_smile:


#8

Fair enough. I’ve tweaked the code now if you want to try again.


#9

No problems when compile with LLVM GCC 4.2 as 64 bit. The suffix seems to work. Testet it in reaper 64bit.

Have some problems while compiling it as 32bit with GCC 4.2 (Platform 10.6 and target 10.5):

In file included from /Users/patrickkunz/Develop/tal/tal-u-no-lxx/Builds/MacOSX/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm:80: /Users/patrickkunz/Develop/tal/tal-u-no-lxx/Builds/MacOSX/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU/../../juce_core/native/juce_osx_ObjCHelpers.h: In static member function 'static juce::String ObjCClass<SuperclassType>::getRandomisedName(const char*)': /Users/patrickkunz/Develop/tal/tal-u-no-lxx/Builds/MacOSX/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU/../../juce_core/native/juce_osx_ObjCHelpers.h:138: error: reference to 'Random' is ambiguous /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/ApplicationServices.framework/Frameworks/QD.framework/Headers/QuickdrawAPI.h:2266: error: candidates are: short int Random() /Users/patrickkunz/Develop/tal/tal-u-no-lxx/Builds/MacOSX/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU/../utility/../../juce_gui_basics/../juce_graphics/../juce_core/maths/juce_Random.h:39: error: class juce::Random

It looks as Apples Quick draw API has a Random definition too. Adding the juce namespace (juce::Random) fixes the issue:

static String getRandomisedName (const char* root) { return root + String::toHexString (juce::Random::getSystemRandom().nextInt64()); }

Could not successfully compile PPC binaries with LLVM.


#10

Ah, thanks, I’ll add that namespace thing…

Fair point!


#11

You can keep using LLVM and just add this Xcode flag :
GCC_VERSION_ppc = 4.0

So it’ll still speed up the intel 32 & 64 build.

Salvator


#12

Sorry for opening this annoying issue again. I don’t know if this is really suffix issue. I never had a crash again, but it looks that some GUI components still have problems related to similar code (classes).

I do following:

  1. I load the VST Instrument Plugin in Ableton Live (drag it to a midi channel) --> GUI shows up.
  2. I replace the instrument with the AU version (drag it to the same midi channel) --> No GUI anymore, no crash.

I can also load the AU first, then the UI disapears after a few switches of the AU and VST plugin.

I made a version 2 of the same plugin with another plugin code and only a few modifications and i’m having the same issues also in this case. It looks that hosts like Reaper does not have this issue… maybe its the way ableton handles plugins.

Could someone reproduce this and is there an easy solution available for this?

EDIT: I found that the problem with the 2 similar versions was an identical AU view name and bundle identifier in the juce AppConfig file. The other problem that occours while switching VST and AU versions of the same plugin still exists, but its not an important issue for me and maybe an ableton live bug.


#13

This isn’t actually working, at least in Live 9 x64, same as described in the previous post. I’m testing with the tip, and am running into this issue with the example plugin. I had to make some minor project tweaks to get it to build 64-bit on OS X 10.6.8 with Xcode 3.2.6, but I don’t think those changes have anything to do with the issue.

The simple repro case is to instantiate the VST version of the plugin in LIve 9 x64 (UI shows up), then instantiate the AU version. At this point, the AU UI does not show up, and you will see a warning in the OS X Console about the Obj-C view class being defined in both binaries, and it being undefined which one will be used.

Actually, turns out the same issue happens in Live 8, you just have to try a bit harder to repro. If you tweak the example project to build 32-bit, and #define BUILD_AU_CARBON_UI to 0, then do the same test in Live 8 32-bit, you’ll get the same result. This is the console message:

This same issue would happen in any host that supports loading AU and VST plugins simultaneously. Not sure what other ones are out there besides Live (Studio One? Possibly Digital Performer?), but there are probably more on the way. Also, sometimes it actually works OK and displays the AU UI, even though the warning is still there in the console. In this case, it just happened to pick the “right” implementation, though it’s working by accident. In any case, it’s not a Live bug.

Looking at the AU wrapper, it looks like the problem because the AU view creation class is not using the new JUCE ObjCClass way of defining the class dynamically (interestingly, the actual AU view class is using the new dynamic method). So I added some code to do that, using other code that uses ObjCClass as an example. Initially it didn’t work (and maybe this is why it wasn’t done this way to begin with), but I finally figured out that this was because when you register a class dynamically, it is typically associated with the host application NSBundle, NOT the actual AU component bundle. Typically the mCocoaAUViewBundleLocation returned for the kAudioUnitProperty_CocoaUI property is just the location of the component DLL, but in this case it needs to be the host application. I fixed that, and now it actually works!

Here’s how I changed JuceUICreationClass in juce_AU_Wrapper.mm (had to move a few other things around in the file to get everything to compile, mostly related to having all the class method definitions within the class declarations themselves, which I hate, but that’s a whole 'nother story… :)):

[code]// Dynamically register UI view creation Obj-C class
#define DYNAMIC_AU_UI_VIEW_CLASS

#ifdef DYNAMIC_AU_UI_VIEW_CLASS

struct JuceUICreationClass : public ObjCClass
{
JuceUICreationClass() : ObjCClass (“AUCocoaViewClassName_”)
{
addMethod (@selector (interfaceVersion), interfaceVersion, @encode (unsigned), “@:”);
addMethod (@selector (description), description, @encode (NSString*), “@:”);
addMethod (@selector (uiViewForAudioUnit:withSize:), uiViewForAudioUnit, @encode (NSView*), “@:”, @encode (AudioUnit), @encode (NSSize));

	addProtocol (@protocol (AUCocoaUIBase));
	
	registerClass();
}

private:
static unsigned interfaceVersion (id self, SEL)
{
return 0;
}

static NSString* description (id self, SEL)
{
	return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)];
}

static NSView* uiViewForAudioUnit (id self, SEL, AudioUnit inAudioUnit, NSSize inPreferredSize)
{
	void* pointers[2];
	UInt32 propertySize = sizeof (pointers);

	if (AudioUnitGetProperty (inAudioUnit,
							  juceFilterObjectPropertyID,
							  kAudioUnitScope_Global,
							  0,
							  pointers,
							  &propertySize) != noErr)
		return nil;

	AudioProcessor* filter = (AudioProcessor*) pointers[0];
	JuceAU* au = (JuceAU*) pointers[1];

	if (filter == nullptr)
		return nil;

	AudioProcessorEditor* editorComp = filter->createEditorIfNeeded();
	if (editorComp == nullptr)
		return nil;

	return EditorCompHolder::createViewFor (filter, au, editorComp);
}

};

#else
[… original JuceUICreationClass implementation here …]
#endif
[/code]

Then I changed JuceAU::GetProperty() for kAudioUnitProperty_CocoaUI as follows:

		   #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
			// (On 10.4, there's a random obj-c dispatching crash when trying to load a cocoa UI)
			if (SystemStats::getOperatingSystemType() >= SystemStats::MacOSX_10_5)
		   #endif
			{
				JUCE_AUTORELEASEPOOL

				AudioUnitCocoaViewInfo* info = (AudioUnitCocoaViewInfo*) outData;

			#ifdef DYNAMIC_AU_UI_VIEW_CLASS
				// Added this to create UI class name dynamically.  Note that you
				// don't need to call createInstance() on this class, since the caller
				// (the AU host) will dynamically create the class as needed.
				static JuceUICreationClass cls;
				
				// Get the bundle location corresponding to the new dynamically
				// created class.  Note that this will typically be the host
				// application, NOT the component DLL.  We need this for correctly
				// specifying the mCocoaAUViewBundleLocation for the AU host.
				NSBundle* b = [NSBundle bundleForClass:cls.cls];
				
				// Set the returned fields appropriately so AU host can create the UI view factory
				info->mCocoaAUViewClass[0] = (CFStringRef) [nsStringLiteral (class_getName(cls.cls)) retain];
				info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [b bundlePath]] retain];
				
			#if 0
				printf("AU view class name: %s\n", class_getName(cls.cls));
				printf("AU view bundle location: %s\n", [[b bundlePath] UTF8String]);
				printf("\n");
			#endif

			#else
				// Old non-dynamic way
				const File bundleFile (File::getSpecialLocation (File::currentApplicationFile));
				NSString* bundlePath = [NSString stringWithUTF8String: (const char*) bundleFile.getFullPathName().toUTF8()];
				NSBundle* b = [NSBundle bundleWithPath: bundlePath];
				info->mCocoaAUViewClass[0] = (CFStringRef) [nsStringLiteral (JUCE_STRINGIFY (JuceUICreationClass)) retain];
				info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [b bundlePath]] retain];
			#endif
			
			#if 0
				// Print all classes in Obj-C runtime
				printf("*** Printing Obj-C classes\n");
				int numberOfClasses = objc_getClassList(NULL, 0);
				Class *classes = (Class *) calloc(sizeof(Class), numberOfClasses);
				numberOfClasses = objc_getClassList(classes, numberOfClasses);
				for (int i = 0; i < numberOfClasses; ++i)
				{
					Class c = classes[i];
					const char *bundlePath = [[[NSBundle bundleForClass:c] bundlePath] UTF8String];
					printf("Bundle: %s,\tClass: %s\n", bundlePath, class_getName(c));
				}
				printf("*** Done printing Obj-C classes\n");
				free(classes);
			#endif
				
				return noErr;
			}

There’s some useful code at the end there to print out all the classes loaded into the Obj-C runtime, and what NSBundle each class is associated with. This was helpful in figuring out that the dynamically loaded class was associated with the host app.

Another nice thing about this approach is that the AU view creation class will now only be added to the namespace if the bundle is actually being loaded as an AU, since the code to register it only happens for the kAudioUnitProperty_CocoaUI property. So the class will never even get registered in the VST case (but if it did, it would still have a unique name anyway).

Seems to me that all JUCE-based plugins using the current single-binary-with-different-entry-points-per-format would be running into this same issue in Live 9, and possibly in other hosts that support simultaneous VST/AU. Hopefully this will help some folks out.


#14

Well spotted! I hope jules will integrate this into the library soon!


#15

Well spotted indeed! I didn’t realise there was still an obj-C class lurking in there… Thanks for the tip-off, I’ll eliminate it asap!


#16

Sanity-checking welcome!

BTW, I noticed in your suggested code that you changed the way the bundle URL was loaded - that wasn’t actually correct, because it ended up pointing to the host’s bundle, not the plugin’s… I’ve kept my original code there, but wondered why you changed it?


#17

This is now checked into the tip? I’ll give it a try, but…

Actually, it does have to be that way. As noted in my original message:

If you leave mCocoaAUViewBundleLocation pointing to the plugin DLL, it won’t work, because the host will look there for the class, but when registered dynamically the class appears to be getting associated with the host’s bundle instead of the plugin’s bundle. The code that I posted will always work, because it calls [NSBundle bundleForClass:cls.cls] to get the actual bundle associated with the dynamically-registered class. So if Apple decides to change how this works in some future OS release, it will still work.


#18

This is now checked into the tip? I’ll give it a try, but…[/quote]
I tested it out on the tip, and as suspected it’s broken. To fix it, change these lines:

const File bundleFile (File::getSpecialLocation (File::currentApplicationFile)); NSString* bundlePath = [NSString stringWithUTF8String: (const char*) bundleFile.getFullPathName().toUTF8()]; NSBundle* b = [NSBundle bundleWithPath: bundlePath];

To this (maybe even including the comment so we know why it’s done this way! :)):

// Get the bundle location corresponding to the new dynamically // created class. Note that this will typically be the host // application, NOT the component DLL. We need this for correctly // specifying the mCocoaAUViewBundleLocation for the AU host. NSBundle* b = [NSBundle bundleForClass:cls.cls];


#19

Ah… ok… (Sorry I didn’t notice your nice explanatory comment, doh! If only I had time to slow down and pay more attention!)

Funny thing is, I remember actually writing it the way I did because I had problems getting it to work. But thinking back, that might have been because of obj-C name clashes or something… Anyway, thanks for the heads-up, I’ll take your advice on that one!


#20

I figured it was written the way it was (not converted to the new dynamic way) because of some problem getting it to work. In fact I was just about to give up on it… then I figured out that the problem was… wait for it… I was still specifying the plugin bundle for the location! :slight_smile: The other issue was correctly specifying the signature for uiViewForAudioUnit:withSize in the dynamic case. Easy to mess things up there, and then you end up getting a message about an unhandled selector being sent to your class instance, then the app crashes.