Packaging stubs into DLLs

# Chapter 12 Packaging stubs into DLLs

omniORB’s stubs can be packaged into shared libraries or DLLs. On Unix platforms this is mostly painless, but on Windows things are slightly more tricky.

## 12.1 Dynamic loading and unloading

As long as your platform supports running static initialisers and destructors as libraries are loaded and unloaded, you can package stubs into shared libraries / DLLs, and load them dynamically at runtime.

There is one minor problem with this, which is that normally nil object references are heap allocated, and only deallocated when the ORB is destroyed. That means that if you unload a stub library from which nil references have been obtained (just by creating an object reference _var for example), there is a risk of a segmentation fault when the ORB is destroyed. To avoid that problem, define the `OMNI_UNLOADABLE_STUBS` C pre-processor symbol while you are compiling the stub files. Unfortunately, with that define set, there is a risk that object reference _vars at global scope will segfault as they are unloaded. You must not create _vars at global scope if you are using `OMNI_UNLOADABLE_STUBS`.

## 12.2 Windows DLLs

On Unix platforms, the linker figures out how to link the symbols exported by a library in to the running program. On Windows, unfortunately, you have to tell the linker where symbols are coming from. This causes all manner of difficulties.

### 12.2.1 Exporting symbols

To (statically) link with a DLL file in Windows, you link with a LIB file which references the symbols exported from the DLL. To build the LIB and DLL files, the correct symbols must be exported. One way to do that is to decorate the source code with magic tags that tell the compiler to export the symbols. The alternative is to provide a DEF file that lists all the symbols to be exported. omniORB uses a DEF file.

The question is, how do you create the DEF file? The answer is to use a Python script named `makedeffile.py` that lives in the `bin\scripts` directory in the omniORB distribution. `makedeffile.py` runs the dumpbin program that comes with Visual C++, and processes its output to extract the necessary symbols. Although it is designed for exporting the symbols from omniORB stub files, it can actually be used for arbitrary C++ code. To use it to create a DLL from a single source file, use the following steps:

1. Compile the source:

   `cl -c -O2 -MD -GX -Fofoo.o -Tpfoo.cc`

2. Build a static library (It probably won’t work on its own due to the -MD switch to cl, but we just need it to get the symbols out):

   `lib -out:foo_static.lib foo.o`

3. Use the script to build a .def file:

   `makedeffile.py foo_static.lib foo 1.0 foo.def`

4. Build the .dll and .lib with the def file.

   `link -out:foo.dll -dll -def:foo.def -implib:foo.lib foo.o`

Of course, you can link together many separate C++ files, rather than just the one shown here.

### 12.2.2 Importing constant symbols

As if exporting the symbols from a DLL was not complicated enough, any constant values exported by a DLL have to be explicitly imported into the code using them. omniORB’s stub files declare a number of such constants. This time, the constant declarations in the generated header files are decorated in a way that tells the compiler what to do. When the stub headers are #included, the correct pre-processor defines must be set. If things are not set correctly, the code all links without problems, but then mysteriously blows up at run time.

Depending on how complex your situation is, there are a range of solutions. Starting with the simplest, here are some scenarios you may find yourself in:

1. All stub code, and all code that uses it is wrapped up in a single DLL.

   Do nothing special.

2. All stub code is in a single DLL. Code using it is in another DLL, or not in a DLL at all.

   `#define USE_stub_in_nt_dll` before `#include` of the stub headers.

3. The stubs for each IDL file are in separate DLLs, one DLL per IDL file.

   In this case, if the IDL files `#include` each other, when the stub files are compiled, import declarations are needed so that references between the separate DLLs work. To do this, first compile the IDL files with the `-Wbdll_stubs` flag:

   `omniidl -bcxx -Wbdll_stubs example.idl`

   Then define the `INCLUDED_stub_in_nt_dll` pre-processor symbol when compiling the stub files. As above, define `USE_stub_in_nt_dll` when including the stub headers into application code.

4. Stubs and application code are packaged into multiple DLLs, but DLLs contain the stubs for more than one IDL file.

   This situation is handled by ‘annotating’ the IDL files to indicate which DLLs they will be compiled into. The annotation takes the form of some `#ifdefs` to be inserted in the stub headers. For example,

   // one.idl #pragma hh #ifndef COMPILING_FIRST_DLL #pragma hh # ifndef USE_stub_in_nt_dll #pragma hh # define USE_stub_in_nt_dll #pragma hh # endif #pragma hh #endif #include <two.idl> module ModuleOne { ... }; // two.idl #pragma hh #ifndef COMPILING_SECOND_DLL #pragma hh # ifndef USE_stub_in_nt_dll #pragma hh # define USE_stub_in_nt_dll #pragma hh # endif #pragma hh #endif #include <three.idl> ...

   Here, `one.idl` is packaged into `first.dll` and `two.idl` is in `second.dll`. When compiling `first.dll`, the `COMPILING_FIRST_DLL` define is set, meaning definitions from `one.idl` (and any other files in that DLL) are not imported. Any other module that includes the stub header for `one.idl` does not define `COMPILING_FIRST_DLL`, and thus imports the necessary symbols from the DLL.

   Rather than explicitly listing all the pre-processor code, it can be cleaner to use a C++ header file for each DLL. See the COS services IDL files in `idl/COS` for an example.