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:

  1. Create Winelib “wrapper” (Linux ELF) around linux-gpib-user so that it can be used from within Windows programs.
  2. 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.

file-outline gpib.dll.spec
@ 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.

file-outline commdrv.c [snippet]
#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!