4

I'm working on a small hobby project of mine where I have a big structure that takes care of all core tasks. This core won't do much by itself, it needs a dozen subsystems that actually do the hard work. I've currently written only one subsystem so it's still easy to change things.

I have a lot of code locations where the core interfaces with the subsystems, and I don't want to have to change the core every time I add a new subsystem. My idea was to make it modular.

A long time I saw something similar in a game engine, where a new console command could be defined by using some preprocessor macro. That was all you had to do - after compiling it instantly worked in the game.

So let's take the game engine as an example for my case. I've placed comments in the code below that should make my question more obvious.

My question: How do I implement a modular system in Objective-C, that's built at compile time, and does not involve changing anything other than the modules themselves?

And now some code

-(void)interpretCommand:(NSString*)command {
    // Find the space in the command
    NSRange pos = [command rangeOfString:@" "];
    if (pos.length == 0) return; // No space found
    
    NSString *theCommand = [command substringToIndex:pos.location];
    
    // !!! Here comes the important code !!!
    // Get all the available commands - this is what my question is about!
    NSDictionary *allCommands = nil;
    
    // Find the command in the dictionary
    NSString *foundCommand = [allCommands objectForKey:theCommand];
    
    // Execute the command
    if (foundCommand != nil) {
        [[NSClassFromString(foundCommand) new] execute];
    }
}

I want to be able to add a new command with something like :

REGISTER_COMMAND(MyClassName, "theCommand")

Remember, the above code isn't my specific case. Also, I don't want external modules, they have to be compiled as if they were implemented natively. Objective-C is fine, so is C++ or C.

Update
Clarification: I know how to do this with a plist file, but if I chose that I might just as well store them in my actual code. I'm looking for a C/C++/Objective-C solution that allows me to simply add the module with a preprocessor macro.

Update 2
Adding a bounty - I'd really like some good ideas for this.

Community
  • 1
  • 1
Tom van der Woerdt
  • 29,532
  • 7
  • 72
  • 105
  • What platform are you working on -- iOS or MacOS X? – Caleb Jan 01 '12 at 23:31
  • @Caleb Actually, both. Shouldn't really influence the answer though, as this is a compiler question. – Tom van der Woerdt Jan 01 '12 at 23:32
  • Okay, can you explain a bit more about what you're trying to accomplish? You wrote: *I know how to do this with a plist file, but if I chose that I might just as well store them in my actual code.* I'm confused -- if you use a preprocessor macro, you ARE storing the commands in your "actual code." Seems like a plist is a better solution. Also, *where* do you want to use this macro? Can it go inside `main()`, for example? – Caleb Jan 01 '12 at 23:35
  • The difference between iOS and MacOS X is that all your modules in iOS have to be statically linked -- dynamic linking isn't allowed. (Can't find a reference for that just now, but I believe it's still true.) – Caleb Jan 01 '12 at 23:37
  • Every module would call the `REGISTER_COMMAND()` macro from its own code, which would then somehow magically be found by the compiler or linker which puts the modules together. I want to avoid editing any core code and I want to avoid using scripts. I just want a very streamlined build process - I know it's easy when using either of these (or at runtime) but I'm wondering whether it would be possible to do it *without* these. Also, on static/dynamic linking, all my code is put together by the normal linker, so that's not a problem. – Tom van der Woerdt Jan 01 '12 at 23:39
  • Is it ok for the modules to all have a common superclass? – nielsbot Jan 08 '12 at 00:34

7 Answers7

5

I don't fully understand the problem. However, from what I can gather the sticking point is finding a hook at runtime that you can register your modules.

One such hook is the +(void)load class method. load is called on every class and category that is loaded. For statically link classes/categories this will be when your app starts. Even if you choice not to use Objective-C you can still create a class simply for the hook its' load method provides.

Benedict Cohen
  • 11,912
  • 7
  • 55
  • 67
  • Hmm, `+(void)load` looks very interesting and it's almost what I want, but the problem is that implementing this makes it load during initialization. I'm looking for a solution where I can fill the `NSDictionary` (see my example) during the compilation instead of initialization. – Tom van der Woerdt Jan 01 '12 at 22:58
  • What's the reason for wanting to do this at compile time instead of launch time? – Benedict Cohen Jan 02 '12 at 14:38
  • Fun, I guess. And maybe a very minor performance bonus, but I'm primarily interested in how such a thing would be achieved. – Tom van der Woerdt Jan 02 '12 at 14:40
  • @Tom van der Woerdt: How can an `NSDictionary` be filled before runtime? Objects are a runtime concept. – Nikolai Ruhe Jan 03 '12 at 14:35
  • @NikolaiRuhe They can't be, but you can hardcode the values in the source. Which is what I want, but without the actual code. – Tom van der Woerdt Jan 03 '12 at 14:58
3

This is somewhat like whet @verec posted: You could add a special class to your project, called ModuleList. Every module wishing to register itself could do so by adding a category to ModuleList. You can put this in a macro. Using objc/runtime functions you can loop over the added properties or methods. (i.e. all properties/methods that don't originate in NSObject)

The benefit would be that you don't have to loop over all classes.

mvds
  • 45,755
  • 8
  • 102
  • 111
2

I'm doing something sort of along these lines in a new project I'm working on. I store information about modules (classes) in an XML file (a plist would work well too), including capabilities of the class, its name, etc. At runtime, I load the XML file, and when a particular class is needed, I instantiate it based on its name. For ease of use/good encapsulation, I have a BaseModule class from which the "modules classes" inherit. BaseModule has an initWithModuleName: method that takes a module name (as spelled out in the XML file):

- (id)initWithModuleName:(NSString *)moduleName
{
    if ( ![self isMemberOfClass:[BaseModule class]] ) 
    {
        THROW_EXCEPTION(@"MethodToBeCalledOnBaseClassException", @"-[BaseModule initWithModuleName] must not be called on subclasses of BaseModule.");
    }

    [self release]; // always return a subclass
    self = nil;

    if ([BaseModule canInitWithModuleName:moduleName]) 
    {
        ModuleDefinition *moduleDefinition = [BaseModule moduleDefinitionForModuleName:moduleName];

        Class moduleClass = NSClassFromString(moduleDefinition.objectiveCClassName);
        self = [(BaseModule *)[moduleClass alloc] initWithModuleDefinition:moduleDefinition];
    }

    return self;
}

There's a lot more to this system than I've mentioned here and this is based on my code, but not copy/pasted from it. For one thing, I use Objective-C's ability to do method name lookup at runtime and dynamic dispatch to invoke module methods that are declared/defined in BaseModule subclasses but not in BaseModule itself. These methods are described in the XML file.

But the end result is that all I have to do to add a new module is create its definition in the "ModuleDefinitions.xml" file in my project, and add implementation classes for it to the project. The rest of the program will automatically pick up on its presence and start using it.

Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97
2

Ok, if it must be by macro, this solution works:

    // the macro:
#define REGISTER_COMMAND( __THE_CLASS, __COMMAND )\
@interface __THE_CLASS##Registration @end\
@implementation __THE_CLASS##Registration \
+(void)load { [ Commands registerHandler:NSClassFromString(@""#__THE_CLASS) command:(__COMMAND) ] ; } \
@end

    // Bookkeeping/lookup class:
@interface Commands 
@end

@implementation Commands
static NSMutableDictionary * __commands = nil ;

+(void)load
{
    __commands = [[ NSMutableDictionary alloc ] init ] ;
}

+(void)registerHandler:(Class)theClass command:(NSString*)command
{
    if ( theClass && command.length > 0 )
    {
        [ __commands setObject:theClass forKey:command ] ;
    }
}

+(id)handlerForCommand:(NSString*)command
{
    Class theClass = [ __commands objectForKey:command ] ;
    return [ [ [ theClass alloc ] init ] autorelease ] ;
}

@end

    // map the command 'doit' to handler 'MyCommand', below
REGISTER_COMMAND( MyCommand, @"doit" )

    // one of our command handling objects, 'MyCommand'
@interface MyCommand : NSObject
@end

@implementation MyCommand
@end

    // test it out:
int main (int argc, const char * argv[])
{   
    @autoreleasepool 
    {
        NSLog(@"command %@ found for 'doit'\n", [ Commands handlerForCommand:@"doit" ] ) ;

    }
    return 0;
}
nielsbot
  • 15,922
  • 4
  • 48
  • 73
  • thanks! it's possible that the linker may optimize out any Obj-C class you don't actually refer to... so it could be really close to what you need. – nielsbot Jan 09 '12 at 02:32
1

If we start from your interpretCommand sample code, the key structure is the allCommands dictionary.

You are looking for some means to populate it such that when asked for a string (your command) it returns another string to be the class name of an objective-c class that you can then create instances of.

How do you want your dictionary being populated?

If it is at compile time, either yourself, in some file, or some tool you write will have to write a bit of source code that inserts

[allCommands addObject: @"ThisNewClass" forKey: @"ThisNewCommand"] ;

If that's all you are after, then you don't even need a macro, but some kind of registry class whose sole job is to populate your dictionary with whatever new class/command pair you wan to add.

Something as simplistic as:

@interface Registry : NSObject {
    NSMutableDictionary * allCommands ;
}

- (id) init ;
- (NSDictionary *) allCommands ;

@end 

@implementation Registry

- (id) init {
    if (self = [super init]) {
        allCommands = [[NSMutableDictionary alloc] initWithCapacity: 20] ;

        // add in here all your commands one by one
        [allCommands addObject: @"ThisNewClass" forKey: @"ThisNewCommand"] ;
        [allCommands addObject: @"ThisNewClass2" forKey: @"ThisNewCommand1"] ;
        [allCommands addObject: @"ThisNewClass3" forKey: @"ThisNewCommand2"] ;
        [allCommands addObject: @"ThisNewClass3" forKey: @"ThisNewCommand3"] ;
    }
    return self ;
}

- (NSDictionary *) allCommands {
    return allCommands ;
}
@end

If this is not the answer you are looking for, please could you elaborate and point out how this example doesn't precisely match your question?

verec
  • 5,224
  • 5
  • 33
  • 40
  • Not quite what I'm looking for either - I can write a script that extracts all `REGISTER_COMMAND()` and place them in a .h file that gets `#import`ed, but I was really hoping for a way to do this without the script. – Tom van der Woerdt Jan 01 '12 at 23:25
  • "My question: How do I implement a modular system in Objective-C, that's built at compile time, and does not involve changing anything other than the modules themselves?" You specify "compile time" in your question. Compiling is about transforming source code into something else. That source code has to *exist*. With respect to what you want, the only way the SOURCE code can know about any new module you add is because the SOURCE code is modified. If your question accepted "runtime" then we would have an entirely different answer. – verec Jan 01 '12 at 23:27
  • Such a file would be outside the module and is therefore technically a file outside the module that's changed. It's probably nitpicking, but I really want a very streamlined build process that doesn't involve scripts. – Tom van der Woerdt Jan 01 '12 at 23:29
  • There is a possibilty to do this at BUILD time but that involves RUNTIME anyway. The build process, when you add a 'module', would create some extra entry for it, say, just one of your new module object files produced by the compiler, maybe the one you deem the entry point of your module or "command class". Then the build process could populate some direcory somewhere for your code to read AT RUNTIME and parse and instantiate all the given 'commands' But note that no COMPILE time is involved here. Just BUILD time and, by necessity RUNTIME in your code. – verec Jan 01 '12 at 23:39
  • If you mean splitting my code into .so files, that's not allowed by Apple. – Tom van der Woerdt Jan 01 '12 at 23:42
  • --- There are countless plugin architectures out there, and by the sound of it, that is what you are after. – verec Jan 01 '12 at 23:43
  • True - you could call it a plugin architecture. However, I don't want any initialization code that puts all the modules together. You could say that I want to save the state of the modules after loading all their entry points into the arrays (or dictionaries), and boot the application with those pre-filled. – Tom van der Woerdt Jan 01 '12 at 23:46
  • If at t0 you have "modules" A, B, and C that you app knows about, and at t1 you app knows about A, B, C and D, and the only thing that happened between t0 and t1 is a re-COMPILE, this means that some SOURCE code has changed in such a way that D is now known to exist. If between t0 and t1, other processes are allowed (build, aka, run the linker, other scripts, whatnot) then your execution environment could be modified in such a way that at RUNTIME your code could spot differences in its environment and react accordingly. – verec Jan 01 '12 at 23:51
  • http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/LoadingCode/Concepts/Plugins.html – verec Jan 02 '12 at 00:10
  • I don't think you really understood my question. I don't want a plugin system where they are plugged in at runtime, I want the linker to plug them in. – Tom van der Woerdt Jan 02 '12 at 00:12
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/6307/discussion-between-verec-and-tom-van-der-woerdt) – verec Jan 02 '12 at 10:45
1

I'm adding another answer to elaborate on what was said in the chat above as this might prove useful to anyone having a similar issue.

The assumption this solution relies on are:

  • all modules and the core are part of the same executable and are compiled and linked together, like in any standard project, and
  • there is a naming convention between commands and module class names.

With this is mind, the following code (in "core") returns the list of all the classes in the executable whose mame match the given prefix:

#import <objc/runtime.h>

- (NSSet *) findAllClassesWithPrefix: (NSString *) prefix {
    NSMutableSet * matches = [NSMutableSet setWithCapacity:2] ;
    size_t classCount = 0 ;
    Class * classes = 0 ;

    Class nsObjectClass = objc_getClass("NSObject") ;

    classCount = (size_t) objc_getClassList(0, 0) ;

    if (classCount > 0) {
        classes = (Class *) calloc(classCount, sizeof(Class)) ;
        classCount = (size_t) objc_getClassList(classes, (int) classCount) ;

        for (int i = 0 ; i < classCount ; ++i) {
            Class  c = classes[i] ;
            if (c == nil) {
                continue ;
            } else {
                // filter out classes not descending from NSObject
                for (Class superClass = c ; superClass ; superClass = class_getSuperclass(superClass)) {
                    if (superClass == nsObjectClass) {
                        const char * cName = class_getName(c) ;
                        NSString * className = [NSString stringWithCString:cName
                                                                  encoding:NSUTF8StringEncoding] ;                        
                        if ([className hasPrefix: prefix]) {
                            [matches addObject: className] ;
                        }
                    }
                }
            }
        }

        free(classes) ;
    }

    return matches ;
}

Now to retrieve all the classes whose name start with "PG":

NSSet * allPGClassNames = [self findAllClassesWithPrefix:@"PG"] ;

for (NSString * string in allPGClassNames) {
    NSLog(@"found: %@", string) ;
}

prints:

2012-01-02 14:31:18.698 MidiMonitor[1167:707] found: PGMidiDestination
2012-01-02 14:31:18.701 MidiMonitor[1167:707] found: PGMidi
2012-01-02 14:31:18.704 MidiMonitor[1167:707] found: PGMidiConnection
2012-01-02 14:31:18.706 MidiMonitor[1167:707] found: PGMidiAllSources

Using the simple convention that commands and module class names are the same, then that's all there is to it.

In other words, to add a new module, just add its classes sources to the project and you're done. The runtime in core will just pick it up, provided the module "main class" name matches whatever naming convention you have established between "commands" and module class names.

ie, there is no need to

REGISTER_COMMAND(MyClassName, "theCommand")

in any module code anymore, nor is there any need for any macro.

verec
  • 5,224
  • 5
  • 33
  • 40
1

aaaand.. here another solution built on load/initialize, no Macro--if you subclass Module, your command handler will be picked up at load time. You could probably make it work based on your class implementing some protocol if you wanted to...

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

//--------------------------------------------------------------------------------

@interface Module : NSObject

+(Module*)moduleForCommand:(NSString*)command ;
+(void)registerModuleClass:(Class)theClass forCommand:(NSString*)command ;


+(NSString*)command ;   // override this to returnthe command your class wants to handle

@end

@implementation Module

static NSMutableDictionary * __modules = nil ;
+(void)load
{
    @autoreleasepool 
    {
        __modules = [[ NSMutableDictionary alloc ] init ] ;
    }
}

+(void)initialize
{
    [ super initialize ] ;
    if ( self == [ Module class ] )
    {
        unsigned int count = 0 ;        
        Class * classList = objc_copyClassList( & count ) ;
        for( int index=0; index < count; ++index )
        {
            Class theClass = classList[ index ] ;
            if ( class_getSuperclass( theClass ) == self )
            {
                [ Module registerModuleClass:theClass forCommand:[ theClass command ] ] ;
            }
        }
    }
}

+(Module*)moduleForCommand:(NSString*)command
{
    Class theClass = [ __modules objectForKey:command ] ;
    return !theClass ? nil : [ [ [ theClass alloc ] init ] autorelease ] ;
}

+(void)registerModuleClass:(Class)theClass forCommand:(NSString*)command
{
    [ __modules setObject:theClass forKey:command ] ;
}

+(NSString *)command
{
    NSLog(@"override +command in your Module subclass!\n") ;
    return nil ;
}
+(BOOL)shouldLoad
{
    return YES ;    // override and set to NO to skip this command during discovery
}

@end

//--------------------------------------------------------------------------------

@interface MyModule : Module 
@end
@implementation MyModule

+(NSString *)command
{
    return @"DoSomething" ;
}
@end

//--------------------------------------------------------------------------------
int main (int argc, const char * argv[])
{
    @autoreleasepool 
    {       
        Module * m = [ Module moduleForCommand:@"DoSomething" ] ;
        NSLog( @"module for command 'DoSomething': found %@\n", m ) ;

    }
    return 0;
}
nielsbot
  • 15,922
  • 4
  • 48
  • 73