Porting Windows GPIB software to Linux (using Winelib)
A lot of proprietary software in the field of electronics unfortunately only supports Windows - Wine is extremely useful in these cases and supports most of the needed Windows APIs, however using hardware generally doesn’t work as Windows-only drivers are used.
I won’t mention the name of the program I’m trying to port, but it uses a library which we will call commdrv.dll
, which is used to communicate with the underlying hardware. Since this is not possible on Linux using Wine, we will instead need to find a way to modify this library so that it instead uses linux-gpib. But even if we replace this library with our own commdrv.dll
(a Windows DLL), how can we make calls to linux-gpib, which is a Linux library? The answer is Winelib!
Unfortunately Winelib is poorly documented, and I had to figure most of this out through trial & error, reading winebuild
source code, and many many wasted hours. (This is really the only reason I’m writing about it here, so hopefully it helps someone).
Plan:
- Create Winelib “wrapper” (Linux ELF) around
linux-gpib-user
so that it can be used from within Windows programs. - Create
commdrv.dll
(Windows DLL) to replace provided communcation library, which will uses functionality provided by our Winelib wrapper.
This should also be very performant!
Building Linux-GPIB
First we need to build linux-gpib-user from source as normal, however configuring with the following arguments to build for 32-bit architectures and compile statically.
./configure --enable-static --build=i686-pc-linux-gnu CFLAGS=-m32 LDFLAGS=-m32
Note! |
The reason I am compiling for i686 here is because the program is 32 bits and will therefore only work with 32 bit libraries. This is also why I am using `-m32` and `i686-w64-mingw32-gcc` later. |
We now have libgpib.a
which we can use.
Writing a Spec file
Next, we need to write a Spec file, which provides the interface so that our Linux library can act as a DLL that can be called from Windows programs. ‘@’ is used for automatic ordinal allocation (we don’t particularly care),the stdcall calling convention is specified, as well as the required arguments.
@ stdcall ibdev (long long long long long long)
@ stdcall ibask (long long ptr)
@ stdcall ibtmo (long long)
@ stdcall ibpad (long long)
@ stdcall ibwrt (long ptr str)
@ stdcall ibrsp (long str)
@ stdcall ibrd (long ptr long)
@ stdcall ibclr (long)
@ stdcall ibonl (long)
@ stdcall ThreadIbsta ()
...
Compiling GPIB wrapper
We now want to link our Spec file with gpib.a:
winegcc -m32 -g -shared libgpib.a libgpib.spec -o libgpib
The spec file is internally converted to an object file, which provides functions in a way such that Windows libraries can call them - these functions in turn call the underlying linux functions, which are linked with it (libgpib.a
). This then produces libgpib.dll.so
, and although this is an ELF shared object it can be used from Windows programs (running in Wine).
Using this library from a Windows DLL
We can now reimplement the commdrv.dll
library using linux-gpib function calls. Below is an example of implementing the _CommRead
function call.
#include <windows.h>
#define ok(X) ((X) & 0x8000) != 0x8000 // query ERR in ibsta
int (*ibrd)(int, void*, long) = 0;
int ud = 0; // device descriptor
...
__declspec(dllexport)
DWORD __stdcall _CommRead(DWORD a1, DWORD a2, char* returnstr, int num) {
return ok(ibrd(ud, returnstr, num));
}
...
BOOL APIENTRY DllEntryPoint(HMODULE hModule, DWORD r, LPVOID lpReserved) {
if (r == DLL_PROCESS_ATTACH) {
HINSTANCE hDll = LoadLibrary("libgpib.dll.so");
if (!hDll) {
MessageBox(NULL,"libgpib.dll.so required!", "Failed", MB_OK);
ExitProcess(-1);
}
ibrd = (int (*)(int, void*, long))GetProcAddress(hDll, "ibrd");
if (ibrd == NULL) {
MessageBox(NULL,"Symbol cannot be found!", "Failed", MB_OK);
ExitProcess(-1);
}
...
}
return 1;
}
This can then be compiled with
i686-w64-mingw32-gcc -g -Wall -shared -o commdrv.dll commdrv.c
And we can now replace the provided commdrv.dll
with our own!