[extension] Concatenating factory_map objects

Hi, I'm developing a plugin system and I will be loading several DLLs. Basically, as far as I know, this means having one factory_map per DLL (on Windows). I need to concatenate either the factory_map objects or their maps so that I can do lookups in one map instead of several maps. Is there a recommended way of doing this? Right now this requires keeping all of the factory_map objects around (for cleanup purposes only) and having my additional global std::map, that contains the concatenated std::map's from the factory_map objects (it's essentially one large copy).

Robert,
I'm developing a plugin system and I will be loading several DLLs. Basically, as far as I know, this means having one factory_map per DLL (on Windows).
This depends on how you want to use the factory_maps. Do you want to close the shared libraries that you aren't using? If not, just use the same factory_map for every shared library. Unless you have a LOT of separate shared libraries, this is usually a good solution (if you have a lot, you may not want the unused libraries taking up RAM). If you do want to close unused shared libraries, it's a bit trickier. I had some code that did this, but have let it languish because it didn't cover a number of special cases. Here is more or less how it worked: - Create a counted_factory_map instead of a factory_map. Internally, a counted_factory_map contains a separate map and shared_library object for each shared library. - Whenever you ask it to add a constructor for a class T, it instead adds a constructor for a class counted<T> (which is a derived class of T), which increments and decrements a counter whenever it is created or destroyed. - There is a function in the shared library that can be called to see how many objects are currently live from the given shared library. - You can call a function on the counted_factory_map to do garbage collection - ie, close shared libraries with no live objects. The reasons this code is not currently used in Extension: - It requires a duplicate of almost every class in Extension - a counted version and a non-counted version. - The counted versions have very high coupling - whereas the uncounted versions of shared_library and factory_map are very loosely coupled. It also makes it impossible to use parameter_map in its current form. - It requires, in the common case, the use of mutexes to protect the counters. This requires linking in the Thread library. - It only works if all you are doing in a shared library is constructing objects using counted_factory. If you take a reference to anything else in a shared library, you risk having a dangling reference when all of the counted<T> objects are destroyed. Note that this can be alleviated by using a separate shared_library instance if you want to do anything besides populate a counted_factory_map, since a shared library is only closed when all shared_library instances referencing it are destroyed (it is also possible to force a shared library to remain open until program termination - by passing false as the second parameter to the shared_library constructor). There were a few other special cases that broke it as well - but I can't remember what they were right now. I can update that code if people are interested. I certainly wouldn't mind suggestions about how to avoid some of the problems that I listed, or a different way of handling this sort of "garbage collection" of unused shared libraries. Thanks! Jeremy Pack

On Mon, Jul 7, 2008 at 9:42 PM, Jeremy Pack <rostovpack@gmail.com> wrote:
Robert,
I'm developing a plugin system and I will be loading several DLLs. Basically, as far as I know, this means having one factory_map per DLL (on Windows).
This depends on how you want to use the factory_maps. Do you want to close the shared libraries that you aren't using? If not, just use the same factory_map for every shared library. Unless you have a LOT of separate shared libraries, this is usually a good solution (if you have a lot, you may not want the unused libraries taking up RAM).
If you do want to close unused shared libraries, it's a bit trickier. I had some code that did this, but have let it languish because it didn't cover a number of special cases. Here is more or less how it worked:
- Create a counted_factory_map instead of a factory_map. Internally, a counted_factory_map contains a separate map and shared_library object for each shared library. - Whenever you ask it to add a constructor for a class T, it instead adds a constructor for a class counted<T> (which is a derived class of T), which increments and decrements a counter whenever it is created or destroyed. - There is a function in the shared library that can be called to see how many objects are currently live from the given shared library. - You can call a function on the counted_factory_map to do garbage collection - ie, close shared libraries with no live objects.
The reasons this code is not currently used in Extension:
- It requires a duplicate of almost every class in Extension - a counted version and a non-counted version. - The counted versions have very high coupling - whereas the uncounted versions of shared_library and factory_map are very loosely coupled. It also makes it impossible to use parameter_map in its current form. - It requires, in the common case, the use of mutexes to protect the counters. This requires linking in the Thread library. - It only works if all you are doing in a shared library is constructing objects using counted_factory. If you take a reference to anything else in a shared library, you risk having a dangling reference when all of the counted<T> objects are destroyed. Note that this can be alleviated by using a separate shared_library instance if you want to do anything besides populate a counted_factory_map, since a shared library is only closed when all shared_library instances referencing it are destroyed (it is also possible to force a shared library to remain open until program termination - by passing false as the second parameter to the shared_library constructor).
There were a few other special cases that broke it as well - but I can't remember what they were right now.
I can update that code if people are interested. I certainly wouldn't mind suggestions about how to avoid some of the problems that I listed, or a different way of handling this sort of "garbage collection" of unused shared libraries.
Thanks for responding Jeremy. I'm a little confused about your response, but perhaps that's because I don't know as much about shared libraries as I should. Basically, I'm assuming that the shared libraries must remain open in order to continue to use the factories. I'm not sure what you mean by closing the shared libraries I'm not using. I'm technically using the shared libraries every time I call factory::create(). However, once I concatenate the factory_map for each shared library, I do not use the factory_map any longer. I simply keep them stored in a boost::ptr_vector to maintain lifetime, and when I no longer need access to my concatenated list of factories, I simply destroy the ptr_vector of factory_map objects. Right now this is what I have as members in a wrapper class of mine: typedef std::map<unsigned int, boost::extensions::factory<Aspect> > MapType; boost::ptr_vector<boost::extensions::factory_map> m_factories; MapType m_aspectMap; Say, for example, I load 3 shared libraries. As I add more shared libraries, I am capable of creating more types, since each shared library provides a way for me to extend the capabilities of my factory (lets it create more types). In the case of having 3 shared libraries, the m_factories member would simply be 3 in size. I do not use the m_factories member anywhere, it simply serves the purpose of maintaining the lifetime of the shared libraries. m_aspectMap is the concatenation of the 3 maps returned by factory_map::get(). I concatenate them so that instead of doing an O[log(n)] three times, I only do it once, which is slightly more efficient. The main issue I have is that the overhead of the 3 factory_map objects must remain in memory, because I can't keep the 'factory' objects alive without them, according to my research. I would simply like to eliminate the m_factories member of my class and keep the 'factory' objects by themselves, and have the shared libraries they're each associated with remain alive until all associated factory objects have been destroyed, almost like the shared library is reference counted (Just an initial idea). I was looking for your opinion on the best way to approach this issue. I figured that I perhaps was making this more complicated than it needed to be, due to a feature I missed or something. I hope I've properly emphasized my situation. I do apologize for any lack of information in my original post. If you could explain in more detail what you mean by using the same shared_library for multiple libraries, I would appreciate it. I figured there was a 1-to-1 mapping between factory_map and DLLs (on windows).

Robert, The first answer in my e-mail applies then - you can use the same factory_map for as many shared libraries as you'd like. Other answers below:
Thanks for responding Jeremy.
I'm a little confused about your response, but perhaps that's because I don't know as much about shared libraries as I should. Basically, I'm assuming that the shared libraries must remain open in order to continue to use the factories.
Yes.
I'm not sure what you mean by closing the shared libraries I'm not using.
If you have no need to close the shared libraries (ie, you aren't getting low on RAM), then don't. You should only need that if you are loading many, many shared libraries. By default, they remain open.
I'm technically using the shared libraries every time I call factory::create(). However, once I concatenate the factory_map for each shared library, I do not use the factory_map any longer. I simply keep them stored in a boost::ptr_vector to maintain lifetime, and when I no longer need access to my concatenated list of factories, I simply destroy the ptr_vector of factory_map objects.
1 - The factory object is copyable - so you can just copy all of the std::map objects returned by factory_map::get into another map. You can then safely destroy the factory_map. 2 - You can just use a single factory_map anyway. Say, for example, I load 3 shared libraries. As I add more shared
libraries, I am capable of creating more types, since each shared library provides a way for me to extend the capabilities of my factory (lets it create more types). In the case of having 3 shared libraries, the m_factories member would simply be 3 in size. I do not use the
m_factories member anywhere, it simply serves the purpose of
maintaining the lifetime of the shared libraries.
You don't need to do that to maintain the lifetime of the shared libraries. factory_map does not open or close shared libraries. Only the shared_library object does that. By default, shared libraries are left open forever (this is an option in the shared_library constructor). Feel free to delete factory_maps as soon as you have retrieved the factories that you need. If you could explain in more detail what you mean by using the same
shared_library for multiple libraries, I would appreciate it. I figured there was a 1-to-1 mapping between factory_map and DLLs (on windows). ____________________________
Check out the example in libs/extension/examples/multiple_inheritance/main_mi.cpp. It does something like this: factory_map my_map; load_single_library(my_map, "first_library.extension", "extension_export"); load_single_library(my_map, "second_library.extension", "extension_export"); load_single_library(my_map, "third_library.extension", "extension_export"); Thus, it uses the same factory_map for every shared library. If you only want the factories of a certain type (and delete the rest of the factory_map), you can then do the following: factory_map *my_map; load_single_library(*my_map, "first_library.extension", "extension_export"); load_single_library(*my_map, "second_library.extension", "extension_export"); std::map<std::string, factory<computer> > factory_list; factory_list.swap(my_map->get<computer, std::string>()); delete my_map; I think that in your case, this is what you want to do. Does that make more sense? Jeremy Pack

On Tue, Jul 8, 2008 at 5:34 PM, Jeremy Pack <rostovpack@gmail.com> wrote:
Robert,
The first answer in my e-mail applies then - you can use the same factory_map for as many shared libraries as you'd like.
Other answers below:
Thanks for responding Jeremy.
I'm a little confused about your response, but perhaps that's because I don't know as much about shared libraries as I should. Basically, I'm assuming that the shared libraries must remain open in order to continue to use the factories.
Yes.
I'm not sure what you mean by closing the shared libraries I'm not using.
If you have no need to close the shared libraries (ie, you aren't getting low on RAM), then don't. You should only need that if you are loading many, many shared libraries. By default, they remain open.
I'm technically using the shared libraries every time I call factory::create(). However, once I concatenate the factory_map for each shared library, I do not use the factory_map any longer. I simply keep them stored in a boost::ptr_vector to maintain lifetime, and when I no longer need access to my concatenated list of factories, I simply destroy the ptr_vector of factory_map objects.
1 - The factory object is copyable - so you can just copy all of the std::map objects returned by factory_map::get into another map. You can then safely destroy the factory_map. 2 - You can just use a single factory_map anyway.
Say, for example, I load 3 shared libraries. As I add more shared
libraries, I am capable of creating more types, since each shared library provides a way for me to extend the capabilities of my factory (lets it create more types). In the case of having 3 shared libraries, the m_factories member would simply be 3 in size. I do not use the
m_factories member anywhere, it simply serves the purpose of
maintaining the lifetime of the shared libraries.
You don't need to do that to maintain the lifetime of the shared libraries. factory_map does not open or close shared libraries. Only the shared_library object does that. By default, shared libraries are left open forever (this is an option in the shared_library constructor). Feel free to delete factory_maps as soon as you have retrieved the factories that you need.
If you could explain in more detail what you mean by using the same
shared_library for multiple libraries, I would appreciate it. I figured there was a 1-to-1 mapping between factory_map and DLLs (on windows). ____________________________
Check out the example in libs/extension/examples/multiple_inheritance/main_mi.cpp.
It does something like this:
factory_map my_map; load_single_library(my_map, "first_library.extension", "extension_export"); load_single_library(my_map, "second_library.extension", "extension_export"); load_single_library(my_map, "third_library.extension", "extension_export");
Thus, it uses the same factory_map for every shared library.
If you only want the factories of a certain type (and delete the rest of the factory_map), you can then do the following:
factory_map *my_map; load_single_library(*my_map, "first_library.extension", "extension_export"); load_single_library(*my_map, "second_library.extension", "extension_export"); std::map<std::string, factory<computer> > factory_list; factory_list.swap(my_map->get<computer, std::string>()); delete my_map;
I think that in your case, this is what you want to do. Does that make more sense?
Yes, your explanation helps out tremendously. However, I noticed that load_single_library() simply deletes (by allowing it to fall out of scope) the shared_library object it creates as the function returns. Does this mean that it does not close the library when it is destroyed? So I will need to be responsible for calling shared_library::close() to release the DLL when I am done using it? I noticed also (you mentioned this) that there's an auto_close construction parameter in shared_library, however this value does not seem to get used anywhere. In fact, shared_library does not have a destructor that I can see. Does automatic unloading of shared libraries not work on destruction of shared_library objects even when setting auto_close to true? I'm assuming that by closing a shared_library, any factory objects created from that shared_library will not function anymore, since it won't have access to the classes it is creating. Thanks again for your continued help! PS: Do you have an online version of the trunk's documentation for Boost.Extension? I haven't been able to find this. Thank you.

Robert, On Tue, Jul 8, 2008 at 4:12 PM, Robert Dailey <rcdailey@gmail.com> wrote:
<snip>
Yes, your explanation helps out tremendously. However, I noticed that load_single_library() simply deletes (by allowing it to fall out of scope) the shared_library object it creates as the function returns. Does this mean that it does not close the library when it is destroyed? So I will need to be responsible for calling shared_library::close() to release the DLL when I am done using it?
The load_single_library function is a convenience function designed for the simple case in which you don't need to close the shared library until program termination. For more complex usage, just use a shared_library object.
I noticed also (you mentioned this) that there's an auto_close construction parameter in shared_library, however this value does not seem to get used anywhere. In fact, shared_library does not have a destructor that I can see. Does automatic unloading of shared libraries not work on destruction of shared_library objects even when setting auto_close to true?
You're right - I must have accidentally removed the destructor when I last refactored. Thanks! I'll fix it.
I'm assuming that by closing a shared_library, any factory objects created from that shared_library will not function anymore, since it won't have access to the classes it is creating.
Yep. Your program will crash or do something worse. Thanks again for your continued help!
PS: Do you have an online version of the trunk's documentation for Boost.Extension? I haven't been able to find this. Thank you.
Docs are available at: http://redshoelace.dlinkddns.com:9080/boost/doc/extension/index.html Jeremy Pack

On Tue, Jul 8, 2008 at 6:46 PM, Jeremy Pack <rostovpack@gmail.com> wrote:
Robert,
On Tue, Jul 8, 2008 at 4:12 PM, Robert Dailey <rcdailey@gmail.com> wrote:
<snip>
Yes, your explanation helps out tremendously. However, I noticed that load_single_library() simply deletes (by allowing it to fall out of scope) the shared_library object it creates as the function returns. Does this mean that it does not close the library when it is destroyed? So I will need to be responsible for calling shared_library::close() to release the DLL when I am done using it?
The load_single_library function is a convenience function designed for the simple case in which you don't need to close the shared library until program termination. For more complex usage, just use a shared_library object.
I noticed also (you mentioned this) that there's an auto_close construction parameter in shared_library, however this value does not seem to get used anywhere. In fact, shared_library does not have a destructor that I can see. Does automatic unloading of shared libraries not work on destruction of shared_library objects even when setting auto_close to true?
You're right - I must have accidentally removed the destructor when I last refactored. Thanks! I'll fix it.
I'm assuming that by closing a shared_library, any factory objects created from that shared_library will not function anymore, since it won't have access to the classes it is creating.
Yep. Your program will crash or do something worse.
Thanks again for your continued help!
PS: Do you have an online version of the trunk's documentation for Boost.Extension? I haven't been able to find this. Thank you.
Docs are available at: http://redshoelace.dlinkddns.com:9080/boost/doc/extension/index.html
Thank you, this cleared up all of my confusion. What I'm doing now is keeping a single factory_map object as a member of my class, and each time the user wants to "concatenate" factories, I simply call load_single_library() and pass in my existing factory_map member. Things seem to be working well so far. I appreciate your help! Great library!

On Tue, Jul 8, 2008 at 7:15 PM, Robert Dailey <rcdailey@gmail.com> wrote:
On Tue, Jul 8, 2008 at 6:46 PM, Jeremy Pack <rostovpack@gmail.com> wrote:
Robert,
<snip>
Docs are available at: http://redshoelace.dlinkddns.com:9080/boost/doc/extension/index.html
Thank you, this cleared up all of my confusion.
What I'm doing now is keeping a single factory_map object as a member of my class, and each time the user wants to "concatenate" factories, I simply call load_single_library() and pass in my existing factory_map member. Things seem to be working well so far.
I appreciate your help! Great library!
Jeremy, I wanted to let you know that the documentation you linked me to seems out of date. If you look at Tutorial 1, you're still using std::list as a return value from factory_map::get() instead of std::map.
participants (2)
-
Jeremy Pack
-
Robert Dailey