Blog, UTC

Calling into Windows DLLs under Linux, MacOS or BDS

Pleiszenburg.de releases open source project 'zugbruecke'

Build Status: master / releaseDocumentation Status: master / releaseProject License: LGPLv2Project Development StatusAvailable on PyPi - the Python Package IndexAvailable on PyPi - the Python Package Index
zugbruecke is an experimental Python module (currently in development status 3/alpha). It allows to call routines in Windows DLLs from Python code running on Unices / Unix-like systems such as Linux, MacOS or BSD. zugbruecke is designed as a drop-in replacement for Python's standard library's ctypes module. zugbruecke is built on top of Wine. A stand-alone Windows Python interpreter launched in the background is used to execute the called DLL routines. Communication between the Unix-side and the Windows/Wine-side is based on Python's build-in multiprocessing connection capability. zugbruecke has (limited) support for pointers and struct types. zugbruecke comes with extensive logging features allowing to debug problems associated with both itself and with Wine. zugbruecke is written using Python 3 syntax and primarily targets the CPython implementation of Python.

In: ctypes, Python Module, Python, Python 3, Software Development, Wine, Linux, Unix, Interoperability
 

Background

The need for calling routines in DLLs from Linux/Unix programs dates back well over a decade, as far as evidence still exists. It is reflected in numerous forum posts and questions on mailing lists. The following two questions on StackOverflow alone have been viewed about 20,000 times each in their 8 years of existence, while they have never received an actually satisfying answers:

It is a quite common, frequently occurring problem that some relevant function / routine is only offered by a closed / proprietary DLL for which there is no equivalent in the Unix world. Furthermore, it is not a rare issue either that certain old or highly complex code bases can not be ported offhand without significant efforts. At this point, the usual advise is to write an application based on "winelib", which is a library belonging to the Wine project. This approach allows to re-use and compile code-bases written for Windows almost unmodified under Unix. The resulting binaries can be linked against Windows libraries as well as against Unix libraries. This way, a bridge between both worlds can be constructed. In reality, the described process is anything but trivial and does not necessarily lead to the desired result directly. If you are for instance dealing with a single, individual DLL, which you want to use under Unix, you must develop a fitting "winelib" application and consider ways and means of how you can make it communicate with "actual" Unix applications. Going down this path involves a considerable (learning) effort, while a many of the necessary steps are anywhere from little documented to undocumented.

Repeatedly confronted with similar issues over the years, I have developed (or have had to develop) a number individual, specialized solutions. However, it had been my wish from the start to have a generic solution for calling routines in DLLs from Unix programs at my disposal - without having to deal with implementing a winelib application and inter-process communication. Because I usually write code, which is, at its core, controlled by Python scripts, implementing an API compatible to "ctypes" happened to be the logic consequence - unifying all of my previous developments. By releasing "zugbruecke", I am hereby offering the result of this labor as an open source project under the GNU Lesser General Public License v2.1.

About the project

zugbruecke is designed as a drop-in replacement for ctypes, while ctypes is in fact used internally. The following code, running under Linux, MacOS or BSD calls the function "pow" from Microsoft's MSVCRT.dll:

1 from zugbruecke import cdll, c_double
2 dll_pow = cdll.msvcrt.pow
3 dll_pow.argtypes = (c_double, c_double)
4 dll_pow.restype = c_double
5 print('Expect "1024.0": "%.1f".' % dll_pow(2.0, 10.0))
Example 1: Calling a routine from a DLL with zugbruecke and ctypes-Syntax under Unix.

The above is realized by a second Python interpreter for Windows, which is automatically started on top of Wine. It runs the actual calls into DLLs. For handling pointers, zugbruecke offers a special "memsync" protocol. Similar to "argtypes" and "restype", it can be used to define which memory sections based on a function's parameters need to be kept in sync between the Unix side and the Wine side. "memsync" is designed in a way that it will be ignored by ctypes if the code is ever directly executed on Windows. This allows to develop platform independent code with relative ease. The inter-process communication between the Unix side and the Wine side is based on "multiprocessing connections" from Python's standard library.

Use cases

The project's primary objective is the ability to call functions without errors next to being able to easily maintain and extend the code. Speed and security are considered less important at the moment. zugbruecke has been developed for calling into complex numerical computations in DLLs or for reading exotic or outdated file formats quickly and uncomplicated if there is a corresponding (old) DLL available.

zugbruecke should not be used if security matters. This includes authentication, decrypting and encrypting sensitive data. Furthermore, it is not suited for calling a somewhat smaller routine many times in a row, which implies a significant speed penalty due to the required overhead. At the time of writing, a single call of a routine in a DLL on recent hardware costs a bit less than 0.2 µs.

Further reading
Alternative approaches

Next to "winelib" applications, there are only few noteworthy options of how one can call into DLLs under Unix systems. Those shall be covered here.

For a little more than the past decade, mainly for supporting Windows drivers, two projects have made a name for themselves: DriverLoader of Linuxant and NDISwrapper by Pontus Fuchs, Giridhar Pemmasani and others. While the first, proprietary project has been declared obsolete and dead by Linuxant, the second project, published as open source under LGPL, still exists although it apparently has not seen any significant maintenance for about 4 years. Both projects are remarkable because they essentially prove that one can even call into Windows DLLs very close to the operating system under Unix systems in a useful manner.

Building on the last thought, the Google developer and security analyst Tavis Ormandy recently received some attention for publishing tools on GitHub (licensed under the GNU General Public License v2.0). Derived from NDISwrapper, they allow to execute Windows DLLs almost directly on Linux without complex intermediate layers. His development was motivated by his intention to fuzz Windows anti-virus software looking for vulnerabilities. Well, he then just fund one he described as "Crazy Bad" (technically: wormable) in Micosoft's Malware Protection Engine. Some comments on GitHub suggest that his project, "loadlibrary", can also be used for developing Linux software, which needs to access Windows DLLs. However, Ormandy clearly states that his project does not aim at creating a complete implementation of the Windows API similar to the Wine project and that it only makes sense for very rudimentary DLLs with few system calls.

Sebastian M. Ernst