Using Factory Functions to Export C++ Objects from Shared Libraries / DLLs, but also applying Factory Class Design Pattern.

(Edited 3/15/2021, 22h45: )

The main reason for which factory functions are sometimes used is the fact that, while object-oriented code can in fact be compiled into shared libraries, situations exist in which the application programmer is not aware of specific libraries that will need to be loaded at run-time. As a result, this developer cannot specify them during the linking stage of his or her application.

(End of Edit, 3/15/2021, 22h45.)

What one does in practical cases is, to define “factory functions” in the shared library, such as the function “maker()” in the example below, and to give the compiler directive ‘extern “C”‘ when doing so:

https://www.linuxjournal.com/article/3687

I think that the example I’ve just linked to suffers from some seriously bad typesetting. But to my mind, it gets the point across. The methods that belong to C++ classes have mangled names, which are difficult to load from shared libraries directly (using the ‘dlsym()’ function). In principle, one could try to predict the name mangling in the client program, but in practice, this is avoided. Instead, a factory function is exported from the shared library to the client program, the name of which is explicitly not mangled, due to this compiler directive, and what that function does when called is, to create an object of the specified type. The client program’s C++ ‘knows’ what methods of the object it can call because of header files…

When I was studying System Software at Concordia University, an exercise the whole class was required to carry out was, to define a function that was declared with ‘extern “C”‘, to store that in a shared library, and, to write a client program which loaded that function. We were never required to load C++ objects from that shared library. But, the article which I just linked to above, explains how to do that, at least well enough so that I can follow it.

(…)

If you’re one of those people like me, who have seen C++ with many ‘Create…()’ function-names, even though we know that in general, in C++, the constructors of class-objects have the same name as the class, what is written above is likely the reason, for which so many Creator functions have been defined.

I think, though, that there is one way in which I must second-guess the author of the article I just linked to. He ended up using the ‘extern “C”‘ directive in more places than he needed to. If it was something his project set out to do from the beginning, for the source, shared libraries to register their factory functions in an array of function-pointers automatically, then there is really no longer any reason, why their names should not be mangled. In fact, in certain cases conflicts could result, as soon as two functions have the same name, such as “proxy()”. And so, in such cases, there is really only one object in the whole process, the name of which must not be mangled, and in the case cited above, that would be the associative array.

Another fact which the cited article does not mention is simply, that it might result in tedious code, if the factory function was defined separately, for every class of objects that a shared library can export. It might simplify things, if a template Creator class could be defined, which has a factory method as one of its methods, which can be applied to a series of classes that have default constructors… The following article describes a slightly different use:

https://refactoring.guru/design-patterns/factory-method/cpp/example

Of course, the cited function ‘SomeOperation()’ could not be used…

 

//  The following lines should probably go into some header file.

#include <map>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

using std::string;

//  If the library existed, either IMPORT or WINDLL would be defined...

typedef std::map<string, void *> function_array;

function_array *reg_factories_p = NULL;

#ifdef WIN32
#define DLL_EXPORT extern "C" __declspec(dllexport)
#define DLL_IMPORT extern "C" __declspec(dllimport)
#else
#define DLL_EXPORT extern "C"
#define DLL_IMPORT extern "C"
#endif

#ifdef WINDLL
DLL_EXPORT function_array reg_factories;
function_array reg_factories;
#else
#ifndef IMPORT
function_array reg_factories;
#endif
#endif

//  What comes below this line, goes into the shared library / DLL.

class Obj_1 {
public:
	Obj_1() {}
};

class Obj_2 {
public:
	Obj_2() {}
};

class Obj_3 {
public:
	Obj_3() {}
};

 
//  Template definition (can't be compiled).
 
template<class T>
class Creator {
public:

	typedef T * (Creator<T>::*method)() const;
	method m_ptr;
	
    Creator(string object_name) {
		m_ptr = &Creator<T>::FactoryMethod;
#ifndef IMPORT
		if (! reg_factories_p) {
			reg_factories_p = &reg_factories;
		}
		reg_factories[object_name] = (void *) this->m_ptr;
#endif
	}
 
    T *FactoryMethod() const {
        return new T;
    }
 
};
 
 
//  Template instantiations (can be compiled).

Creator<Obj_1>  mk1("Object1");
Creator<Obj_2>  mk2("Object2");
Creator<Obj_3>  mk3("Object3");

//  Static objects will be constructed, if the shared library
//  has been opened with 'RTLD_NOW'.
//  This will cause the constructor within 'Creator' to be
//  called, and therefore, the associative array to be populated.


//  Code below this line is meant to go into the client program...

//  The following template class will serve to cast void pointers back
//  to Maker objects, each with a method that makes the object...

#ifndef WINDLL

#define LIB_NAME "./libfunnylib.so"

template<class T>
class Maker {

public:
	typedef T * (*obj_maker) ();
	
	obj_maker m_fptr;

	Maker(void *fptr) {
		m_fptr = reinterpret_cast<obj_maker> (fptr);
	}
	
	T * Make() {
		return (*m_fptr) ();
	}
	
};


int main(int argc, char* argv[]) {

	void *hndl = dlopen(LIB_NAME, RTLD_NOW);
	if(hndl == NULL) {
		printf("%s\n\n", dlerror());
	} else {
		void *symbol = dlsym(hndl, "reg_factories");
		if (symbol) {
			reg_factories_p = (function_array *) symbol;
		} else {
			printf("%s Did not export symbol reg_factories.\n\n", LIB_NAME);
			return -1;
		}
	}
	
    char *buffer;
    size_t bufsize = 32;

	printf("The purpose here is, to find out whether\n");
	printf("I was able to store non-trivial adresses in the\n");
	printf("associative array.\n");
	printf("\n");
	printf("Object1 factory address: %p\n", (*reg_factories_p)["Object1"]);
	printf("Object2 factory address: %p\n", (*reg_factories_p)["Object2"]);
	printf("Object3 factory address: %p\n", (*reg_factories_p)["Object3"]);
	printf("\n");
	printf("Now attempting to execute those factories...\n");
	
	//  Exploiting the compiler's willingness to create temporary
	//  objects, invoked directly by their class-name...
	try {
		Maker<Obj_1>((*reg_factories_p)["Object1"]).Make();
		Maker<Obj_2>((*reg_factories_p)["Object2"]).Make();
		Maker<Obj_3>((*reg_factories_p)["Object3"]).Make();
	} catch (...) {
		printf("Some type of error took place!\n");
		return -1;
	}
	
	printf("All pointer casts executed.\n\n");
	
	printf("Press Enter to quit program.\n");
	
    buffer = (char *) malloc(bufsize * sizeof(char));
    if( buffer == NULL)
    {
        perror("Unable to allocate buffer");
        exit(-1);
    }
	
	getline(&buffer,&bufsize,stdin);
	

	//  We're done. Cleaning up.
	
	free(buffer);
	
	return 0;
}
#endif

 

One fact which must be kept in mind, if this code is ever to be used ‘in the real world’, is, that The associative array should be declared as ‘extern “C”‘ in a header file, but additionally allocated wherever it’s going to be used.  This results in two similar-looking C++ statements, when building the shared library.

Also, compiling this code will generate one warning per factory method, unless, under ‘g++’, the flag ‘-Wno-pmf-conversions‘ is set. Additionally, on Linux computers, linking requires that the flag ‘-ldl‘ be set.

Now, an astute reader will ask, ‘Mainstream code can be compiled without requiring special flags. Why does this guy’s code require that special flags be set?‘ And the answer to that question is, ‘Because this code cheats. The warning which up-to-date compilers will generate – at best – exists, because on some platforms, a pointer-to-method cannot be cast to a pointer-to free function, in the form of a single address, and always work. Therefore, this practice is actually shunned.’

At the end of this posting, I will show compatible code, that does not cheat…

(Updated 3/22/2021, 3h55… )

(As of 8h00 or so, 3/10/2021… )

There’s an admission which I need to make, about the path I took, in writing the code above. I had read elsewhere, that specifically the (Linux) ‘gcc’ compiler will react to the following code:

 


    Creator(string object_name) {
		m_ptr = &Creator::FactoryMethod;
		reg_factories[object_name] = (void *) this->m_ptr;
	}
 
    virtual T *FactoryMethod() const = 0;

    }


 

…By fetching the real address of the (overridden) virtual method. Therefore, an earlier incarnation of my code attempted to make use of this. What I found, however, was that when I next tried to print out the void pointer, the value was repeatedly ‘0x1′. Later I read, that this was probably the position of the address in the VTABLE. This made the use of virtual function-pointers worse than just specific to Linux. It made them unusable throughout!

This is how the later versions of my code came into being. They next used no virtual functions, and should work under Windows as well. However, my arbitrary choice to use the macro ‘WINDLL’ has no specific meaning under Linux, where any arbitrary method must be used in the header files, to distinguish whether a shared library is being built or linked to…

 


 

(Update 3/10/2021, 13h55: )

I am so glad that I was finally able to build the shared library on my terms, and then even link to it, after hours of work, that I’d also like to publish the build commands I used under Linux:

 


$ g++ -std=c++14 -Wno-pmf-conversions -fPIC -ldl -shared -DWINDLL  -o "libfunnylib.so" "Factory_Method.cpp"

$ g++ -std=c++14 -Wno-pmf-conversions -fPIC -ldl -DIMPORT -o "Factory_Method" "Factory_Method.cpp"

$ ./Factory_Method

 


 

(Update 3/11/2021, 14h05: )

Another fact about my demo project, which I feel I did not explain well enough in the comment-lines of the source-code, has to do with my use, either of the macro ‘WINDLL’, or of the macro ‘IMPORT’, when compiling either the shared library, or the executable, respectively. My choice has the side effect, that the executable can be compiled without either macro being set. I left this possibility open because at first, I was mainly interested in compiling a test version of the program, which will populate the associative array with factory function-pointers, even though the factory functions were being generated within the same executable. When building production-quality code, one does not do this, because it’s taken to be a matter of course that some programmer knew how to build the library, from which the required function-pointers can therefore be imported, into the executable.

Thus, the intent here was, that if the executable is built with the macro ‘IMPORT’, it will not bother to try to create its own array of factory function-pointers, and just use the ones that were taken from the shared library.

Yet, what my reader should be able to see is, that my ‘main()’ function will test for the availability of the shared library, and if it can be found, to import a pointer to its associative array anyway, even if the ‘IMPORT’ macro was not set when compiling.

To the best of my knowledge, ‘static’ or ‘global’ objects are built, before the ‘main()’ function is called.

Therefore, if the executable is compiled without the ‘IMPORT’ macro, runs, and finds that the library exists, what it should do is, first to create its own, local associative array, set its pointer to point to it, and then afterwards, to reset the pointer to point to the associative array which was imported from the shared library. The executable should then proceed with all its tests, as though there were only one version of the associative array (the one from the library).

The reason one doesn’t do this with production-quality code, is the fact that, in order for this to succeed, the functions which have been put into the associative array need to be defined in the same source file, as was used to build the executable. After all, I’ve used one source file to build both the library and the executable. Therefore, ‘in the field’, one avoids this form. The function-pointers in the associative array are supposed to come from the shared library alone, where any number of class objects could have been defined, unbeknownst to the executable.


 

 

(Update 3/11/2021, 14h45: )

There’s another elaboration I can make, for anybody who has no experience in building shared libraries or DLLs. I did at some point try to create source-code, in which the declaration of the ‘std::map<>’ object takes place with the ‘extern “C”‘ directive, but in which there was no actual definition of this global object in the executable, regardless of whether ‘WINDLL’ was set or not set. And the problem one runs in to, and learns about quickly, is, that such an object is actually worse than not allocated. The linker counts it as “Undefined“. And the reason is as follows (at least, under Linux):

The ‘extern’ keyword means, that the object is to be defined ‘in some other module, not necessarily in this module, but, without being redefined’. An ‘extern “C”‘ declaration keeps this meaning.

The object isn’t actually defined at all, unless the second form of the statement is given, without ‘extern “C”‘. Thus, when an earlier version of the code was compiled in the way suggested, the executable gave linker errors, because, even though only pointers to this wonderful object were to be used, a definition also needed to be supplied somewhere, from which the executable would ‘know’ how to make use of either. And the library also needs this information.

Hint: I suspect that this problem with the executable really stemmed, from the fact that my pointer pointed to a template instantiation.

Thus, the form of the demo project I gave was such, that memory was being wasted, in the form of one ‘std::map<>’ object, which contained zero elements (which was far less memory than would be wasted, if that unused object contained numerous elements.) Memory is still being ‘wasted’ somewhere by the static ‘Creator<>’ objects, each of which stores a pointer to its own factory-method, but none of which are going to be useful, after they have called their constructors.

Further, there is no real need to ‘extern’ this object when compiling the executable, because I’m not relying on any capability the compiler might provide, to obtain a pointer to it from the library. I’ve supplied my own code, to obtain that pointer, and for that reason, in this exercise, the ‘std::map<>’ object may actually have a mangled name in the executable.

 


 

(Update 3/11/2021, 18h00: )

To make what I just said slightly less confusing, I’m going to show what the (Linux-specific) tool ‘nm’ can tell me, about the shared library which I just built…

 


dirk@Phosphene:~/Programs/Factory_Method_project$ which nm
/usr/bin/nm
dirk@Phosphene:~/Programs/Factory_Method_project$ nm libfunnylib.so | grep mk
000000000020a450 B mk1
000000000020a460 B mk2
000000000020a470 B mk3
dirk@Phosphene:~/Programs/Factory_Method_project$ nm libfunnylib.so | grep reg_
000000000020a420 B reg_factories
000000000020a400 B _Z15reg_factories_pB5cxx11
dirk@Phosphene:~/Programs/Factory_Method_project$ 

 

This tool exposes the fact that, in the library, the static objects ‘mk1′-‘mk3′ do not have mangled names. That’s very interesting, but not useful to me. What’s more important is the fact that there exists the symbol ‘reg_factories’, the name of which is not mangled, as well as the symbol ‘reg_factries_p’, the name of which is mangled. ‘reg_factories’ is going to be fetched by my ‘dlsym()’ function-call. Also of note, all the entries displayed above have the letter “B”. What this letter stands for is, ‘The object exists, but has not been initialized.’ If the library is built, only with the externed declaration of ‘reg_factories’, then the available symbol will actually have the letter “U”. This means, ‘A symbol has been declared, but its meaning is Undefined. We have no idea what this symbol stands for, and can therefore also never initialize it.’ This also means that the ‘dlopen()’ function-call will fail with an error message, because it was called with the ‘RTLD_NOW’ option, which means ‘Either resolve all the symbols, or fail.’

That undefined symbol will additionally have a mangled name, if our ‘extern’ directive was missing the ‘”C”‘…

 

In such a case, the executable which I created above will still keep trying to run, but will segfault, if it was built with the ‘IMPORT’ macro set, because its pointer will still be a Null Pointer (instead of pointing to the local ‘std::map<>’ object), and an attempt will be made to dereference it.

 


 

AFAIK, The existence of the ‘std::map<>’ object which had not been externed, in the executable, and which was wasting some small amount of memory, can be eliminated, by replacing it with a ‘typedef’, like so:

 


typedef std::map<string, void *> factory_array;

//  When building the library:
extern "C" factory_array reg_factories;     //  Declared
factory_array reg_factories;                //  Static object created

//  When loading by the executable:
factory_array *reg_factories_p;             //  Pointer with known methods

 

But, I’m just too lazy to refactor my code at this point…


 

 

(Update 3/11/2021, 19h45: )

At this point in time, I was no longer so lazy, and refactored my code, the latest version of which is now shown at the beginning of this post, to resolve all the issues I had with its efficiency.


 

(Update 3/11/2021, 21h35: )

Some Code that does not cheat:

 

//  The following lines should probably go into some header file.

#include <map>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

using std::string;

//  If the library existed, either IMPORT or WINDLL would be defined...

typedef std::map<string, void *> function_array;

function_array *reg_factories_p = NULL;

#ifdef WIN32
#define DLL_EXPORT extern "C" __declspec(dllexport)
#define DLL_IMPORT extern "C" __declspec(dllimport)
#else
#define DLL_EXPORT extern "C"
#define DLL_IMPORT extern "C"
#endif

#ifdef WINDLL
DLL_EXPORT function_array reg_factories;
function_array reg_factories;
#else
#ifndef IMPORT
function_array reg_factories;
#endif
#endif

//  What comes below this line, goes into the shared library / DLL.

class Obj_1 {
public:
	Obj_1() {}
};

class Obj_2 {
public:
	Obj_2() {}
};

class Obj_3 {
public:
	Obj_3() {}
};

Obj_1 * Create1() {
	return new Obj_1;
}

Obj_2 * Create2() {
	return new Obj_2;
}

Obj_3 * Create3() {
	return new Obj_3;
}

class Proxy {
public:
	Proxy() {
#ifndef IMPORT
		if (! reg_factories_p) {
			reg_factories_p = &reg_factories;
		}
	
		reg_factories["Object1"] = (void *) Create1;
		reg_factories["Object2"] = (void *) Create2;
		reg_factories["Object3"] = (void *) Create3;
#endif
	}
};

Proxy p;

//  Static objects will be constructed, if the shared library
//  has been opened with 'RTLD_NOW'.
//  This will cause the constructor within 'Proxy' to be
//  called, and therefore, the associative array to be populated.


//  Code below this line is meant to go into the client program...

//  The following template class will serve to cast void pointers back
//  to Maker objects, each with a method that makes the object...

#ifndef WINDLL

#define LIB_NAME "./libfunnylib.so"

template<class T>
class Maker {

public:
	typedef T * (*obj_maker) ();
	
	obj_maker m_fptr;

	Maker(void *fptr) {
		m_fptr = reinterpret_cast<obj_maker> (fptr);
	}
	
	T * Make() {
		return (*m_fptr) ();
	}
	
};


int main(int argc, char* argv[]) {

	void *hndl = dlopen(LIB_NAME, RTLD_NOW);
	if(hndl == NULL) {
		printf("%s\n\n", dlerror());
	} else {
		void *symbol = dlsym(hndl, "reg_factories");
		if (symbol) {
			reg_factories_p = (function_array *) symbol;
		} else {
			printf("%s Did not export symbol reg_factories.\n\n", LIB_NAME);
			return -1;
		}
	}
	
    char *buffer;
    size_t bufsize = 32;

	printf("The purpose here is, to find out whether\n");
	printf("I was able to store non-trivial adresses in the\n");
	printf("associative array.\n");
	printf("\n");
	printf("Object1 factory address: %p\n", (*reg_factories_p)["Object1"]);
	printf("Object2 factory address: %p\n", (*reg_factories_p)["Object2"]);
	printf("Object3 factory address: %p\n", (*reg_factories_p)["Object3"]);
	printf("\n");
	printf("Now attempting to execute those factories...\n");
	
	//  Exploiting the compiler's willingness to create temporary
	//  objects, invoked directly by their class-name...
	try {
		Maker<Obj_1>((*reg_factories_p)["Object1"]).Make();
		Maker<Obj_2>((*reg_factories_p)["Object2"]).Make();
		Maker<Obj_3>((*reg_factories_p)["Object3"]).Make();
	} catch (...) {
		printf("Some type of error took place!\n");
		return -1;
	}
	
	printf("All pointer casts executed.\n\n");
	
	printf("Press Enter to quit program.\n");
	
    buffer = (char *) malloc(bufsize * sizeof(char));
    if( buffer == NULL)
    {
        perror("Unable to allocate buffer");
        exit(-1);
    }
	
	getline(&buffer,&bufsize,stdin);
	

	//  We're done. Cleaning up.
	
	free(buffer);
	
	return 0;
}
#endif

 

As the reader can see, the proper methodology doesn’t use factory methods at all, but uses factory functions that are free functions. Those should be convertible. But, using a proper approach like this also requires that, when the library gets written, a separate entry gets put in the source code, for a free function for every object-class to be created, as well as an entry that refers to its address, in a proxy class. As before, the proxy class’s constructor gets called as a side effect, when one object of that class is created as a static object, in this case, given the meaningless name ‘p’.

It just takes a little more work, to do everything as it should be done. :-)  Furthermore, the shared library built this way, will be compatible with the same client-program that was built before.


 

(Update 3/14/2021, 4h20: )

Code that uses factory Methods, but still does not cheat:

So, one feat which I have been working towards, which actually makes my exercise special, remains, to be able to export each object class from the shared library – or, the DLL – using a method and not a free function, yet, not to get compiler warnings. I want to eat my cake and have it too! One concept which I can try to exploit would be that, ‘An associative array can have an abstract class as its element type, and then be populated with derived (template) class-objects, that implement the pure virtual function, which, in turn, can be the factory class.’

The only catch I find is, that the factory method needs to output a uniform data-type, which must therefore be a void pointer. This, in turn, adds slight complexity to the client program, which must cast this to a pointer to the requested, imported class.

Here is the code:

 

//  The following lines should probably go into some header file.

#include <map>
#include <string>
#include <cstdlib>
#include <new>
#include <stdio.h>

using std::string;

//  If the library existed, either IMPORT or WINDLL would be defined...

class Cr_B;
typedef std::map<string, Cr_B *> factories_array;

factories_array *factories_p = NULL;

#ifdef WIN32
#include <libloaderapi.h>
#define DLL_LIB_LOAD(fn) LoadLibraryA(fn)
#define DLL_SYM_LOAD(haendel, symname) GetProcAddress(haendel, symname)
#define DLL_FREE(haendel) FreeLibrary(haendel)
#define DLL_EXPORT extern "C" __declspec(dllexport)
#define DLL_IMPORT extern "C" __declspec(dllimport)
#else
#include <dlfcn.h>
#define DLL_LIB_LOAD(fn) dlopen(fn, RTLD_NOW)
#define DLL_SYM_LOAD(haendel, symname) dlsym(haendel, symname)
#define DLL_FREE(haendel) dlclose(haendel)
#define DLL_EXPORT extern "C"
#define DLL_IMPORT extern "C"
#endif

#ifdef WINDLL
DLL_EXPORT factories_array reg_factories;
factories_array reg_factories;
#else
#ifndef IMPORT
factories_array reg_factories;
#endif
#endif

/*  As a special extension, this project will react to the MERGE
 * macro by creating a cumulative associative array, into which
 * hypothetical mutliple libraries' arrays will be merged.
 */

#ifdef MERGE
#ifdef IMPORT
#define MERGEDO
factories_array multi_factories;
factories_array side_factories;
#endif
#endif


class Shareable {
public:
	Shareable () {}
	virtual ~Shareable() {}
	
	virtual void Action() = 0;
};

class Cr_B {
public:
	Cr_B() { }
	
	virtual Shareable * FactoryMethod() const = 0;

	Shareable * Make() {
		return this->FactoryMethod();
	}
};


//  What comes below this line, goes into the shared library / DLL.

class Obj_1 : public Shareable {
public:
	string m_string;
	Obj_1() : Shareable(), m_string("This is object-class 1.\n") {}
	void Action() {
		printf("%s", m_string.c_str());
	}
};

class Obj_2 : public Shareable {
public:
	string m_string;
	Obj_2() : Shareable(), m_string("This is object-class 2.\n") {}
	void Action() {
		printf("%s", m_string.c_str());
	}
};

class Obj_3 : public Shareable {
public:
	string m_string;
	Obj_3() : Shareable(), m_string("This is object-class 3.\n") {}
	void Action() {
		printf("%s", m_string.c_str());
	}
};


//  Template definition (can't be compiled).
 
template<class T>
class Creator : public Cr_B {	
public:	
    Creator(string object_name) : Cr_B() {
#ifndef IMPORT
		if (! factories_p) {
			factories_p = &reg_factories;
		}
		reg_factories[object_name] = this;
#endif
	}
 
    Shareable * FactoryMethod() const {
        return new(std::nothrow) T;
    }
 
};
 
 
//  Template instantiations (can be compiled).

Creator<Obj_1>  mk1("Object1");
Creator<Obj_2>  mk2("Object2");
Creator<Obj_3>  mk3("Object3");

//  Static objects will be constructed, if the shared library
//  has been opened with 'RTLD_NOW'.
//  This will cause the constructor within 'Creator<>' to be
//  called, and therefore, the associative array to be populated.


//  Code below this line is meant to go into the client program...

//  The base-class 'Cr_B' now has a Make() method,
//  which returns a void pointer.
//  The resulting void pointer can then be cast to a pointer, to
//  the requested class object...

#ifndef WINDLL

//  Let's just define a unified way to error out of the program...
int ErrorOut(void *hndl = NULL) {
	if (hndl) DLL_FREE(hndl);
	return -1;
}

#define LIB_NAME "./libfunnylib.so"

int main(int argc, char* argv[]) {

	void *hndl = DLL_LIB_LOAD(LIB_NAME);
	if(! hndl) {
		printf("Error loading shared library!\n\n");
	} else {
		void *symbol = DLL_SYM_LOAD(hndl, "reg_factories");
		if (symbol) {
			factories_p = (factories_array *) symbol;
		} else {
			printf("Error: %s did not export symbol 'reg_factories' !\n\n",
						LIB_NAME);
			return ErrorOut(hndl);
		}
	}
	
#ifdef MERGEDO

	//  First library to load 'factories_p' from...
	side_factories.clear();
	std::swap(multi_factories, side_factories);
	multi_factories.insert(factories_p->begin(), factories_p->end());
	multi_factories.insert(side_factories.begin(), side_factories.end());
	
	//  Potentially, merge more associative arrays...
	
	
	//  Then, make the demo test the resulting, merged array.
	factories_p = &multi_factories;

	printf("\nThe single associative array has been merged repeatably,\n");
	printf("replacing 'earlier' versions of keys with 'later' versions.\n");
	printf("Then, a used pointer was switched to the cumulative array.\n\n");

#endif
	
    char *buffer;
    size_t bufsize = 32;

	printf("The purpose is next, to find out whether\n");
	printf("the factories can be retrieved from the\n");
	printf("associative array.\n\n");
	
	try {
		printf("Attempting to exctract factory objects...\n");
		(*factories_p)["Object1"];
		(*factories_p)["Object2"];
		(*factories_p)["Object3"];
		printf("\n");
	} catch (...) {
		printf("Error: Unable to retrieve from associative array!\n\n");
		return ErrorOut(hndl);
	}

	printf("Success!\n\n");
	printf("Next, calling the Make(), then the Action() methods\n");
	printf("of all the objects...\n\n");
	
	Shareable * temp1 = NULL;
	Shareable * temp2 = NULL;
	Shareable * temp3 = NULL;
	
	try {
		temp1 = ((*factories_p)["Object1"])->Make();
		temp2 = ((*factories_p)["Object2"])->Make();
		temp3 = ((*factories_p)["Object3"])->Make();
	} catch (...) {
		printf("Error when calling Make() methods!\n");
		return ErrorOut(hndl);
	}

	printf("Adress of Object1 - %p\n", temp1);
	printf("Adress of Object2 - %p\n", temp2);
	printf("Adress of Object3 - %p\n", temp3);
	
	printf("\n");

	try {
		temp1->Action();
		temp2->Action();
		temp3->Action();
	} catch (...) {
		printf("Error: The extracted objects were unusable, when their\n");
		printf("Action() methods were to be called!\n");
		return ErrorOut(hndl);
	}
	
	printf("\nPress Enter to quit program.\n");
	
    buffer = (char *) malloc(bufsize * sizeof(char));
    if( buffer == NULL)
    {
        perror("Unable to allocate buffer");
        return ErrorOut(hndl);
    }
	
	getline(&buffer,&bufsize,stdin);
	

	//  We're done. Cleaning up.
	
	free(buffer);
	
	delete temp1;
	delete temp2;
	delete temp3;
	
	if (hndl) DLL_FREE(hndl);
	
	return 0;
}
#endif


 

This way, once the framework has been programmed, a person who wishes to export many object classes from his or her library, will only need to write one line of code again (in the library), for each object class. And, one (longer) line of code will also generate the requested object, from the associative array, in the client executable.

Warning: The associative array this library exports, will not be compatible with the one that the previous two examples used, because this last one has class objects as its base type, no longer, void pointers.

Note: I have in fact compiled, linked and tested this code. On my machine, it produces the desired output…

 


The single associative array has been merged repeatably,
replacing 'earlier' versions of keys with 'later' versions.
Then, a used pointer was switched to the cumulative array.

The purpose is next, to find out whether
the factories can be retrieved from the
associative array.

Attempting to exctract factory objects...

Success!

Next, calling the Make(), then the Action() methods
of all the objects...

Adress of Object1 - 0x55943e3438b0
Adress of Object2 - 0x55943e343910
Adress of Object3 - 0x55943e343970

This is object-class 1.
This is object-class 2.
This is object-class 3.

Press Enter to quit program.
  


------------------
(program exited with code: 0)
Press return to continue

 

(Update 3/14/2021, 11h05: )

One fact which has been obvious from the beginning of this exercise was, that it might be desirable for object-classes to be loaded from multiple libraries. Yet, each library creates its own associative array, and a pointer to it is merely imported into the executable via a ‘dlsym()’ function-call, thereby presenting an added step to the overall challenge.

The simplest way to solve that is, to create a cumulative associative array in the executable, which each shared library doesn’t ‘see’, but into which the associative array from each library can be merged. The latest version of the exercise, updated above, actually tries to simulate this.

Further, by using two cumulative associative arrays instead of one, and ‘std::swap()’, the result can be achieved, that later versions of a matching key replace earlier versions, which is the reverse of how ‘std::map<>’ works by default.

My demo adds a set of cumulative arrays, If the macro ‘MERGE’ has been set, in addition to the macro ‘IMPORT’. Then, the demo merges the imported associative arrays, in an operation which is easily repeatable for multiple shared libraries. Finally, If the relevant macros are set, the pointer in the executable client is switched, to the main cumulative array, and the same 3 classes are tested…


 

 

(Update 3/15/2021, 15h00: )

As it turns out, in the earlier versions of this exercise, I had made the same mistake that a person in This Bulletin-Board Posting had made. Like him, I wrote the mechanism by which objects could be loaded dynamically from the shared library, but, when I tested my code more closely, found that I obtained linker errors, when trying to build a standalone client program.

I find though, that the other people on that BB did not explain well enough to the OP, how he could resolve the issue. They simply told him to use the ‘-l’ flag when linking his client program, which basically left his next question unanswered, ‘What good is a plug-in, if a client program would need to be linked to it each time, when the client program is compiled and linked?’

Assuming for a moment that the client program is meant to be blind to the existence of the library when being built, the correct answer is, that an “abstract class” needs to be defined, which in my updated example above, is simply called ‘Shareable’, and each object to be imported from the plug-in needs to inherit that class. The abstract class needs to ‘warn’ the client program that the derived classes will implement certain, specific methods, that are pure virtual methods in the abstract class, which makes it an “interface”, which a plug-in would need to implement.

I find that, now that I also implemented this, I can obtain the standalone version of the client program, just by commenting out the lines of the demo, which are commented as belonging in the shared library.

This was not so much an error in my methodology, as it was a flat oversight of an issue.

 


 

(Update 3/15/2021, 21h20: )

Summary:

The fact to take note of here is that there are really 3 types of linkage, not 2:

  1. Statically aware, statically linked. Involves library files with names ending in ‘.a’ under Linux, or, ending in ‘.lib’ when using Microsoft Visual Studio.
  2. Statically aware, dynamically linked. Names ending in ‘.so’ under Linux, or, ending in ‘.dll’ under Windows.
  3. Dynamically aware, dynamically linked. Uses same type of library files as type (2) above.

Type (1) above is actually referred to by a euphemism. Binary code from the library is included in the executable, so that after the executable has been linked, no dependency on the library remains. Types (2) and (3) above result in executables, which remain dependent on the presence of the library at run-time.

In my posting, I took the exercise all the way to type (3) above. A real-world example of where this is done, is if a plug-in is being compiled, which the developer is not specifically aware of, when he or she is linking the application, that will scan for and load the plug-in at run-time.

But just to give enough attention to type (2) linkage above, this can easily be achieved by compiling the shared library, and then linking the main program to it, when that is itself being linked. It’s actually more efficient than type (3) linkage, when there are many different object classes, each with different and unpredictable method-names. And it’s achievable, if the shared library is being built using exactly the same compiler, as was used to build the client program. Thus, if the exercise of this posting needed to be performed with type (2) linkage as above, then what I would suggest is that my second example be used, not my third, but that the following be changed very subtly, for compatibility with Linux:

 

extern "C" Obj_1 * Create1();
extern "C" Obj_2 * Create2();
extern "C" Obj_3 * Create3();


Obj_1 * Create1() {
	return new Obj_1;
}

Obj_2 * Create2() {
	return new Obj_2;
}

Obj_3 * Create3() {
	return new Obj_3;
}

 

 

In this case, there would also be no need to define a proxy object, or a factory array. These function definitions are then meant to go into the shared library, while the ‘extern “C”‘ declarations need to be ‘#include’d both into the shared library’s source code, and into that of the client program.

A valid question which should also be asked, then, could be, whether corresponding ‘Destroy()’ free functions should be externed. And my impression is, that it would be more efficient to give the classes ‘Obj_1′-‘Obj_3′ a ‘::Destroy()’ method, either individually, or as part of an inherited class, which would call ‘delete this;as the last thing it does.

 


 

Another fact which could be pointed out about the type of linkage I numbered (3) above is, that the file-names do not need to adhere as strictly to those of type (2), the latter of which are dictated greatly by the operating system…

Under Linux, shared library -names must be of the form ‘lib*.so’, and they must be located wherever the shared library path is, at the time the program is executed. This is usually decided by the O/S. Under Windows, their names must be of the form ‘*.dll’, and if they’re properly installed, they’ll reside in the folders ‘C:\\Windows\\System32′, etc. However, Windows will additionally look for them in the folder of the ‘.exe’ file first.

Well, with the type of the linkage I’ve numbered (3), the executable can decide both the full name of the file to open, and, in which directory to search for it. For that reason, it would also be possible just to rename it ‘*.8bf’. However, a sensible precaution would be, to avoid naming it in such a way, that it might accidentally get opened in a text editor. A person who innocently edits it in any way, will also render it useless.


 

 

(Update 3/22/2021, 3h55: )

One of the ways in which earlier versions of the third version, of the C++, were lacking, was, that they were not Windows-compatible. I have finally gotten around to making them as close to Windows-compatible as I could, without actually testing them on MS Visual Studio. And, I’ve updated that last out of three code samples above.

 

Enjoy,

Dirk

 

Print Friendly, PDF & Email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>