This page describes the possibilities how to create libraries with Lazarus/FPC and how to use them in projects and packages.
- Creating bindings for C libraries - How to convert C header files (.h) to pascal units
Static linking: FPC compiles and links as default a static executable. That means it tells the linker to put all .o files of the project and all packages into one big executable. Advantage: no external dependencies. Disadvantage: No code is shared between different programs on the same computer. And you can not load/unload a plugin.
Dynamic libraries: The idea of dynamic libraries is to share code between programs, saving the memory of the code, reducing the startup time for often used libraries and allowing plugins. The disadvantages of dynamic libraries are: they are slower for seldom used libs, their structure and internals are more complicated (this is mainly a problem for the compiler), their initialization is different (see below) and sharing code requires a version system to only mix compatible code.
|Operating System||Dynamic library||Static library|
|Mac OS X||.dylib||.a|
Mac OS X
A dynamic library filename has always the form 'lib'+packagename+'.so'+version. For example: libz.so.1 and libz.so.1.2.2.
Linux searches a library in the paths of the environment variable LD_LIBRARY_PATH, then in /lib, then /usr/lib and finally the paths of /etc/ld.so.conf.
To share memory (GetMem/FreeMem, strings) with other libraries (not written in FPC) under Linux you should use the unit cmem. This unit must be added as the very first unit in the uses section of the project main source file (typically .lpr), so that its initialization section is called before any other unit can allocate memory.
Windows searches a library in the current directory, the system directory and the environment variable PATH.
The exception handling when using an FPC made DLL with a non-FPC host application can impose some problems, workarounds are mentioned in the #Initialization section.
ppumove, .ppu, .ppl
FPC normally creates for every unit a .ppu and .o file. The .ppu file contains every important information of the .pas/.pp file (types, required filenames like the .o file), while the .o file contains the assembler code and the mangled names understood by the current system.
The ppumove tool included with every FPC installation, converts one or several .ppu and .o files into a dynamic library. It does this by calling the linker to gather all .o files into a .so (windows: .dll) file and removes the .o filename references from the .ppu file. These new .ppu files are normally called .ppl files.
You have the output directory of a package (where the .ppu files are):
ppumove -o packagename -e ppl *.ppu
This will convert all .ppu files into .ppl files and creates a libpackagename.so (windows: packagename.dll). Note that under Linux the prefix 'lib' is always prepended.
This new library can already be used by other programming languages like C. Or by FPC programs by using the external modifiers. But the initialization/finalization sections must be called automatically. This includes the initialization/finalization of the heap manager. This means no strings or GetMem. Of course FPC programmers are spoiled and they can get more.
Loadlibrary - loading a dynamic library
Loading a dynamic library is simple with the
dlopen Loadlibrary function of the unit dl unit dynlibs.
Since 1.9.4, dynlibs provides a portable alternative to unit dl. Note that pretty much any use of unit dl that can't be substituted by using dynlibs is typically already unportable amongst Unices. This alone makes it advisable to use unit dynlibs as much as possible.
The main problem is to get the filename, which depends on the version and the operating system. Since 2.2.2, a constant "sharedsuffix" is declared in unit dynlibs to ease this. It maps to the relevant extension (dll/so/dylib) without a point as prefix.
Every unit can contain an initialization section. The order of the initialization sections depend on the uses sections of each unit.
The initialization of the RTL itself (in system.pp) that is done before your initialization sections are entered slightly differs between a program and a shared library. Most notably there is a difference in how the exception handling is initialized:
On windows systems it has been reported that there won't be an exception handler installed to catch hardware exceptions like access violations or floating point errors, all other native Pascal exceptions that are raised with Raise will work however. If you really need to catch these hardware exceptions you have to install your own handler, on Windows (beginning with versions XP and later) this conveniently possible with AddVectoredExceptionHandler() and RemoveVectoredExceptionHandler(), there are some code examples in the above mentioned bug report. This seems to be a very platform specific thing, on other platforms there might not be a problem.
Free Pascal on i386 will for Delphi compatibility reasons unmask all FPU exceptions in the floating point unit (but without installing a handler for the dll, see above). This unmasking is done before your initialization section begins. If your DLL will be used with a host application that is made with a different compiler (almost all other compilers except FPC and Borland) this will badly interfere with the host application's exception handling. After the LoadLibrary() call the host application won't be able to catch any FPU exceptions anymore, the FPU will suddenly have gained the ability to throw hardware exceptions in this thread that called LoadLibrary() and the host application will most likely not expect this to happen and not be able to handle it properly. The workaround is to put
Set8087CW(Get8087CW or $3f);
into the initialization section to mask them again (for the thread that loaded the library). This won't have any unwanted side effects other than restoring what the host application had all the time, in your DLL you would not have been able to catch them anyways (at least not on Windows, see above). If you really need to catch zero-divide and friends in a DLL that is written for a non-fpc application you have to have a handler installed like mentioned above and then temporarily unmask the FPU before the try and mask it again before you return to the host application.
How to initialize a dynamic library: ToDo
Every unit can contain an finalization section. The order is the reverse order of the initialization sections.
After all finalization sections are executed it will call the procedure that you have previously assigned to Dll_Process_Detach_Hook.
Be very careful what you do in this procedure, all other units will already be finalized, parts of the RTL might not work as expected anymore, creating and freeing objects on the heap or even using pascal strings inside this procedure is a sure way to call for trouble. If you have to stop threads and free objects you must find a way to do this before the library unloading, it is too late to do it here.
Libraries tend to grow and change over time. Adding new features is no problem, but removing a public method or changing its parameters makes the library incompatible. That means either an installed library (.so, .dll, .dylib) is replaced by a compatible one or a new library must be added to the system. That's why every library contains a version.
To load a dynamic library (dlopen of unit dl) the correct filename must be known. Under Linux this means, you have to know the version number.
ToDo: proposal how the IDE should create version numbers