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… )

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

Certain wrong places, to put recursion, into a program?

One of the subjects which I was pursuing recently, was not just of why, before March of 2019, I had gotten some error messages, when trying to compile a certain program, but also of why or if, other people, using other types of computers, might continue to obtain error messages, long after I was no longer obtaining them.

And a basic concept which I had referenced was that C++ compilers, when not given an exact match in the data-types of a function prototype, to the data-type of the arguments passed to those functions, will first try to perform a type-conversion which is referred to as a “promotion”, and if that fails, will attempt what’s referred to as a “standard conversion”, the latter of which can transform a ‘higher’ type of built-in number to a ‘lower type’, etc.. There was a basic question which I had not provided any sort of answer to, nor which I even acknowledged explicitly could exist. That question has to do with what happens, when more than one type-conversion has the ability to go from the argument-type, to the parameter-type of a function prototype.

Theoretically, it would be possible to design a compiler such, that every time a type-conversion is being sought, both types are tried, in a pattern which is referred to as ‘recursion’, but which can also just be referred to as an ‘exhaustive search’. There’s every possibility that this is not how the compiler is programmed in fact. What can happen instead would be, that the compiler is willing to perform a promotion, in the service of a potential, standard conversion, but that it will go no further.

And in that context, as well as in the mentioned context of recursive template definitions, casting a derived class to one of its parent classes, counts as a promotion.

There’s every possibility that if recursion was placed in the code of the compiler, to re-attempt a standard conversion, to execute prior to another standard conversion that will not fit yet, the result could become some sort of endless loop, while the real behaviour of a compiler needs to be more stable. And this would be a valid reason, for which certain standard template declarations will first try to instantiate the templates using a ‘float’, then a ‘double’, and then a ‘long double’, in the form of specializations. The programmers will assume that a standard conversion needs to receive a value, that can be the result of a promotion. And in that case, the first template specialization may not work, while a later one might, just because to convert, say, a ‘double_t’ to a ‘long double’ will be a promotion, while to convert a ‘double_t’ to a ‘float’ would not, but would in fact be a standard conversion, in the service of another, standard conversion (that should not happen).

Dirk

 

I’ve finally figured out, why I was having so many problems, with complex numbers, in a C++ program.

In This earlier posting, I had written that many things could go wrong, with the way C++ templates define complex numbers. Well, after studying the programming exercise which I was basing that posting on, I think I’ve finally found out, what the single problem in fact was.

In my programming exercise, I had defined the data-type ‘complex<double_t>‘. This in itself caused a lot of problems, without being obvious to me as the culprit. The way C++ templates define complex numbers, will often derive the base-type of one complex number, from the base-type of another, preexisting one, just by transferring a template parameter. However, there are two situations where the templates, defined in the headers, can run into trouble with this:

  1. They can try to convert a ‘real number’ to a complex number, where the base-type of the derived complex number was never declared,
  2. They can try to mix mathematical operations between complex numbers and ‘real numbers’, in such a way that the type of the real number must match the base-type of the complex number exactly.

Specifically in situation (1) above, the templates will try the specializations of ‘float‘, ‘double‘, and ‘long double‘, as educated guesses, for what type of complex number is required. And the problem may well be, that only these 3 template-specializations are attempted, not, ‘double_t’.

And in situation (2) above, I was not taking into consideration that my code was often providing literal numbers, that were of type ‘double‘ by nature, not of type ‘double_t‘. This created a mismatch.

In any case, now that I realize what my mistake was, as well as having removed all the ‘mixed computations, between complex and real numbers’, resulting in code that no longer generates errors, I am more confident that I only was days ago, in the C++ template-definitions, for complex numbers.

 


 

(Update 9/18/2019, 12h55 : )

An added observation would be, that when my code tried to find the absolute, of the imaginary component, of a complex number, that component was also a ‘double_t‘ (deterministically), but the absolute function that’s predefined, again, only recognizes parameter-types ‘int‘, ‘float‘, ‘double‘, and ‘long double‘, in certain versions of the GCC compiler, which can again result in an incorrect match, with a working version of the absolute function.

Dirk

 

What can go wrong, when implementing complex numbers in C++ (Possible Solution).

One of the ideas which exist in computer programming, and with object-oriented languages such as C++, is that a header file can define a ‘complex’ data-type, which has a non-complex base-type, such that the Mathematical definition of Complex Numbers is observed, that define them as:

( a + b i )

Where (a) and (b) are of the base-type, which in pure Math is the set of Real Numbers. According to object-oriented programming, a mere header file can then overload how to perform the standard math operations on these complex objects, based on a super-set of math operations already being defined for the base-type. And the complex object can be defined as a template class, to make that as easy as possible.

Well I have already run in to a programming exercise, where I discovered that the header files that ship with Debian / Stretch (which was finally based on GCC v6.3.0), botched the job. The way in which a bug can begin, is that according to what I just wrote, (a) and (b) could be of the type ‘integer’, just because all the required math operations can be defined to exist entirely for integers, including the ‘integer square root’, which returns an integer even when its parameter is not a perfect square.

This type of complex object makes no sense according to real math, but does according to the compiler.

One of the things which can go wrong with this is, that when creating a special ‘absolute function’, only a complex object could be specified as the possible parameter-type. But, complex objects can have a set of ‘type-conversion constructors’, that accept first an integer, then a single-precision, and then a double-precision floating-point number, and which, depending on which type the parameter can match, convert that single parameter into a temporary complex object, that has this parameter as its real component, and that has zero as its imaginary component, so that the absolute-function-call can be computed on the resulting complex object.

When the compiler resorts to “Standard Conversions” (see first article linked to above), then it is willing to perform conversions between internal types as well as programmer-defined conversions.

If somebody did choose this inefficient way of implementing the absolute function of complex objects, in a way that also computes the absolute of ‘real numbers’, then one trap to avoid would be, only to define a type-conversion constructor, that can initialize the complex object from an integer, and never from a double-precision floating-point number. This first type-conversion to an integer would succeed, and would compute its absolute, resulting in a non-negative integer.

This is obviously totally counter to what a programmer would plausibly want his code to do, but one of the first facts which are taught in Programming Courses, is that compilers will choose non-obvious, incorrect ways to behave, if their code gives them an opportunity to do so.

If the programmer wants to do this deliberately, the conversion to ‘integer’ is referred to as ‘the floor function (of the initial floating-point number)’.

Yet, this type of error seems less likely in the implementation of square roots of complex numbers, that rely on square roots of real numbers, etc.

The correct thing to do is to declare a template function, which accepts the data-type of the parameter as its template variable. And then the programmer would need to write a series of template specializations, in which this template variable matches certain data-types. Only, in the case of the ‘absolute function’ under Debian / Stretch, the implementers seem to have overlooked a template specialization, to compute the absolute of a double-precision floating-point number.

However, actually solving the problem may often not be so easy, because The template-variable could indicate a complex object, which is itself of a template class, with a template variable of its own (that mentioned base-type)

One fact to note about all this is, that there is not one set of headers. There are many versions of headers, each of which ship with a different compiler version. Further, not all people use the GNU compilers; some people use Microsoft’s Visual Studio for example… I just happened to base much of my coding on GCC v6.3.0.

An additional fact to observe is, that the headers to be ‘#include’d are written ‘<complex>’, not, ‘<complex.h>’ . What the missing ‘.h’ means, is that they are “precompiled headers”, which do not contain any text. All this makes verification very difficult. GNU is currently based on GCC v9.2, but I was building my projects, actually using ‘STDC++2014′, which was an available command-line option.

Additionally, when programmers go to Web-sites like this one, the information contained is merely meant as a quick guide, on how to program, using these types of tools, and not an exact match of any code that was ever used to compile my headers.

One way in which I can tell that that code is not literally correct, is by the fact that no version information was provided on the Web-site. Another is by the fact that while the site uses data-types such as “double” and “float”, when programmers compile compilers, they additionally tend to use data-types like ‘double_t’, which will refer to the exact register-size on some FPUs, that may actually be 80-bit. Further, the types ‘int32′ and ‘int64′ would be less ambiguous at the binary level, than the declarations ‘int’ or ‘long int’ would be, if there was ever any explicit support for signed integers… Hence, if my code got ‘complex<double_t>’ to work, but that type was never specified on the site, then the site can just as easily have overlooked the type ‘int64′

According to what I read, C and C++ compilers are intentionally vague about what the difference between ‘double’ and ‘long double’ is, only guaranteeing that ‘long double’ will give at least as much precision as ‘double’. But, If the contents of an 80-bit (floating-point) register are stored in a 64-bit RAM location, then some least-significant bits of the significand are discarded, in addition to the power of two being given a new offset. In order to implement that, the compiler both uses and offers the type, that refers to the exact register-contents, which may be 80 bits or may be 64 bits, for a 64-bit CPU…

(Updated 9/17/2019, 18h00 … )

Continue reading What can go wrong, when implementing complex numbers in C++ (Possible Solution).