As you extend Spatial Modeler with operators or dialogs you may find yourself in “DLL Hell”, where at best your plugin fails to load, or worse, crashes Spatial Modeler. This can happen when your plugin depends on DLLs outside of Spatial Modeler that have the same name as those deployed by Spatial Modeler but are incompatible.
This article examines ways you can resolve any DLL Hell problems you may encounter.
You’ve built your operator or dialog plugin, deployed it correctly for Spatial Modeler to discover, but when you run the Spatial Modeler Editor and drag your operator onto the design canvas the application crashes or shows an error. Debugging confirms that your operator is not loading the supporting DLL(s) that your plugin was built against. Dependency Walker, or using the Global Flags tool to enable DLL Loader Snaps in the debugger can show which supporting DLL isn't loading and why.
Once yolu have identified the supporting DLL that's incompatible with your plugin, you need to either remove the need to load the supporting DLL, resolve the name clash or enable multiple instances of DLLs with the same name in the Spatial Modeler process. There are several techniques from which you can choose. In summary they are:
If the supporting DLL is known to provide the same functionality as its Spatial Modeler counterpart you can usually resolve the incompatibility by compiling and linking against the headers and import libraries provided in the Spatial Modeler SDK.
If you find that the version of, say, an Open Source DLL deployed in Spatial Modeler is missing functionality that your plugin requires and there is a static library available, then you can remove the runtime dependency by statically linking against that library.
If have the source code for the problematic DLL you can rebuild the DLL with a different output name to resolve the name clash.
If you are not able to statically link or rename the problematic DLL you can use Win32 assemblies to enable Spatial Modeler to host multiple instances of a DLL with the same name at the same time, This technique involves adding information that identifies the version of the supporting DLL and adding information about that dependency into your plugin.
A Win32 assembly is simply a DLL that incorporates an XML resource known as an assembly manifest. It is this manifest that contains identity and/or dependency information. When the operating system loads your plugin it first probes for an assembly manifest and if successful looks for information that describes the dependency. If successful it searches for DLLs containing a manifest that has matching identity information. If a match is found then that DLL is loaded, even if a DLL with the same name is already loaded in the process.
Here's an example assuming your plugin is called foo.dll and your supporting DLL is called bar.dll
First create an assembly manifest file for bar.dll that includes the assembly identity. Call it bar.dll.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyIdentity name="bar" type="win32" processorArchitecture="x86" version="188.8.131.52"></assemblyIdentity> <file name="bar.dll"></file> </assembly>
Next create an assembly manifest for your plugin to add the dependency description. Call it foo.dll.manifest
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <dependency> <dependentAssembly> <assemblyIdentity name="bar" type="win32" processorArchitecture="x86" version="184.108.40.206" /> </dependentAssembly> </dependency> </assembly>
Use Microsoft's manifest tool to embed the manifests into your 2 DLLs.
mt -manifest foo.dll.manifest -outputresource:foo.dll;2 mt -manifest bar.dll.manifest -toutputresource:bar.dll;2
The DLLs are now ready for redeployment.
If one or more of your components and the SMSDK depend on a single third-party DLL for which you have only headers and an import library, it is still possible to resolve a naming clash. Doing so will involve:
A DLL’s import library contains the details of all the functions exported by the DLL. Included in these details is the name of the DLL itself, so when you link against a DLL’s import library you embed this dependency information into your plugin. When the operating system loads your component into a process it retrieves this information and therefore knows which supporting DLLs are required by your plugin.
If you want to rename a dependency you must create an import library that references the new DLL name. To do this we extract and parse the function definitions from the original import library and create a new import definition (DEF) file that contains the new DLL name and a list of the functions exported by it. We use the Visual Studio DUMPBIN and LIB utilities in a batch file to do just that. The following sample DOS batch script illustrates how
echo off REM This script creates a renamed copy of a DLL and import library alongside the original files REM usage: rename_dll path_to_original_DLL path_to_original_import_library renamed_dll_name set ORIGINAL_DLL=%1 set ORIGINAL_LIB=%2 set NEWDLLNAME=%3 if "%3"=="" goto usage for /f %%i in ("%ORIGINAL_DLL%") do @set DLL_PATH=%%~dpi for /f %%i in ("%ORIGINAL_LIB%") do @set IMPORTLIB_PATH=%%~dpi for /f %%i in ("%NEWDLLNAME%") do @set NEWNAME=%%~ni @set NEW_DLL=%DLL_PATH%%NEWNAME%.dll @set NEW_IMPORTLIB=%IMPORTLIB_PATH%%NEWNAME%.lib @set DEF_PATH=%IMPORTLIB_PATH%%NEWNAME%.def @set ARCH=x86 echo Getting machine architecture dumpbin /headers %ORIGINAL_DLL% > %IMPORTLIB_PATH%headers.txt for /f "delims=() tokens=1,2" %%i in (%IMPORTLIB_PATH%headers.txt) do if %%j == x64 @set ARCH=x64 if %ARCH%==x64 ( @set ARCHOPT=/MACHINE:X64 ) ELSE ( @set ARCHOPT=/MACHINE:X86 ) echo Extracting exports from %ORIGINAL_LIB% dumpbin /exports %ORIGINAL_LIB% > %IMPORTLIB_PATH%exports.txt if not %ERRORLEVEL%==0 ( echo Failed to extract exports goto error_exit ) @echo LIBRARY %NEWNAME%.dll > %DEF_PATH% @echo EXPORTS >> %DEF_PATH% for /f "tokens=4 skip=19" %%i in (%IMPORTLIB_PATH%exports.txt) do @echo %%i >> %DEF_PATH% echo Creating new import library lib /nologo /def:%DEF_PATH% %ARCHOPT% /out:%NEW_IMPORTLIB% if not %ERRORLEVEL%==0 ( echo Failed to create import library goto error_exit ) REM Delete the temporary output del /f %DEF_PATH% del /f %IMPORTLIB_PATH%exports.txt echo Copying DLL copy %ORIGINAL_DLL% %NEW_DLL% if not %ERRORLEVEL%==0 ( echo Failed to create new DLL goto error_exit ) echo Renamed library creation succeeded goto :EOF :error_exit echo Renamed library creation failed :usage echo usage: rename_dll path_to_original_DLL path_to_original_import_library renamed_dll_name
Note that this approach only works where the renamed DLL isn’t itself dependent on other DLLs that are contributing to DLL Hell. This is because the solution outlined here relies on you being able to recompile and link any components that depend on the renamed DLL. For more complex dependences using Win32 Assemblies is a more suitable solution.