The ball is now in the hands of the plugin itself (inside the PF_initPlugin code). If the version negotiation went well, the plugin should register all the object types it supports with the plugin manager. The purpose of the registration is to provide to the application functions like PF_CreateFunc and PF_DestroyFunc that it can use later on to create and destroy plugin objects. This arrangements allows the plugin to control the actual creation and destruction of objects including any resources they manage (like memory), but lets the application control the number of objects and their lifetime. Of course, a plugin may implement singletons by always returning the same object instance.
The registration is done by preparing for each object type registration record (PF_RegisterParams) and calling the registerObject() function pointer provided in the PF_PlatformServices struct (that was passed as argument to PF_initPlugin). The registerOBject() function accepts a string that uniquely identifies the object type or a wildcard "*" and the PF_RegisterParams struct. I'll explain the purpose of the type string and how it is used in the next section. The reason a type string is necessary is because different plugins may support multiple types of objects.
You can see in Listing Two that the C++ plugin registers two monster types -- "KillerBunny" and "StationarySatan".
Now, the shoe is on the other foot. Once the plugin calls registerObject() control goes back to the PluginManager. The PF_RegisterParams contains also a version and a programming language fields. The version field lets the PluginManager make sure it can work with this object type. If there is a version mismatch it will not register the object. It is not a fatal error. This allows fairly flexible negotiations, where the plugin tries to register multiple versions of the same object type in order to take advantage of newer interfaces if they exist and fallback to older interfaces. The programming language field will be explained soon. If the plugin manager is happy with the PF_RegisterParams struct, it just stores it in an internal data structure that maps the object type to the PF_RegisterParams struct.
After the plugin registered all its object types, it returns a function pointer to a PF_ExitFunc. This function is called before the plugin is unloaded and lets the plugin clean up any global resources it acquired during its life time.
If the plugin decides that it can't function properly (can't allocate some resource, crucial object type registration failed, version mismatch) it should cleanup after itself and return NULL. This signals the PluginManager that the plugin initialization failed. The PluginManager will also remove all registrations performed by the failed plugin.
Plugin Object Creation by the Application
At this point all the dynamic plugins have been loaded and both static and dynamic plugins have been initialized and registered all the object types they support. The application can now create object instances by calling the PluginManager's createObject() method. This method accepts an object type string and an IObjectAdapter interface. I'll discuss object adaptation in the next section, so let's focus on the object type string. The application needs to know what object types are supported. This knowledge can be hard-coded into the application or it can query the plugin's manager registration map and find out at runtime what object types are currently registered.
If you recall, the type string can be either a unique type identifier or a wildcard "*". When the application calls createBbject() with a type string ("*" is an invalid type string) the PluginManager looks for an exact match in its registration map. If it find a match it will invoked the registered PF_CreateFunc and return the result to the application (possibly after adaptation). If it can't find a match it will go over all the wild card registrations (plugins that registered with "*" type string) and let them try by invoking their registered PF_CreateFunc. If any plugin returns a non-NULL result it is returned to the application.
What is the purpose of the wildcard registration? It lets plugins create objects they don't know about at registration time. What? Yes. In Numenta, we used it to allow Python plugins. A single C++ plugin registered with a "*" type string. If the application requested a Python class (the type was the actual qualified import path of a Python class) then the C++ plugin who had an embedded Python interpreter created a special object that held an instance of the Python class and forwarded plugin requests to its internal Python object (via the Python C API). To the application it appeared as a standard C++ object. This allows great flexibility because it is possible to just drop a Python class in the right place even while the system is running and the Python object is immediately available.