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-userso 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 libgpibThe 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.cAnd we can now replace the provided commdrv.dll with our own!