Working around the static libstdc++ restriction

I’ve been spending a bit of time, on and off, over the last few months thinking about one of the most annoying bugs in wxHaskell, and how to fix it: that wxHaskell is now unusable from within GHCi.

There are actually two separate bugs: the first, which prevents things from working at all, is that on Windows, there is no DLL version of libstdc++; the second is that you cannot restart a wxWidgets session in GHCi after the application has terminated.

The first bug is a consequence of the fact that, on Windows, GHC uses the MinGW compiler suite to do C/C++ compilation and all linking (including with Haskell objects). The version of MinGW included in GHC does not support dynamic linking of libstdc++.

When wxHaskell was fully cabalized, one of the changes was to replace a complex and fragile build system for the C++ coe in wxHaskell with a much simple cabal-based build. The consequence of this is that the C++ code and wxcore Haskell 0bject code reside in the same library, which means that wxcore needs to be linked against libstdc++.

For compiled binaries, this is not so much of a problem: the static libstdc++ is linked with the other code and all works well. However, in the GHCi case, it is fatal: GHCi doesn’t know how to statically link non-Haskell object code, although it can load DLLs. When you try to load load wxcore, GHCi complains that it cannot load stdc++ (Loading package wxcore-0.12.1.6 … <interactive>: stdc++: The specified module could not be found. can’t load .so/.DLL for stdc++ (addDLL: could not load DLL))

The second problem: inability to restart a GUI, is a consequence of wxWidgets using static destructors. These are only executed when the application fully terminates, which is not the case in a GHCi.

It turns out that a good solution to both of these problems is to repackage the C++ part of wxHaskell as a DLL. The reasons are:

  • The C++ code in the DLL needs to be linked with libstdc++, but this can be done at DLL link time. If libstdc++ is statically linked with the DLL, wxcore no longer needs to depend on linking to libstdc++ (only wxc.dll).
  • If the DLL is dynamically unloaded, the C++ static destructors will be executed (as far as I can tell, this should be true for Linux .so shared libraries as well as Windows DLLs). This means that so long as the wxc shared library is loaded at application start-up and unloaded at application termination, we should be able to correctly restart a GUI.
  • GHCi can load DLLs, so wxHaskell should become usable on GHCi again (since the libstdc++ requirement would have gone away).

The problem, and it has occupied quite a bit of my rather limited spare time, is that this requires quite a bit of re-architecture.

Changes to compile wxc as a DLL

This is probably the simplest part, since wxc was compiled as a DLL in an older version of the build system. The downside is that I would like to use cabal to do the building, since asking anyone to have prerequisites much beyond Haskell Platform greatly reduces the attractiveness of a library.

Changes to use dynamically loaded libraries.

Most usage of shared libraries is pseudo-static, that’s to say that the addresses of functions exported from the shared library are fixed-up when the application is loaded by the linker-loader. In my case, I want to do things fully dynamically. The outline code is fairly straightforward (caveat: this is outline code – I haven’t run it!).

On Windows, you need something like:

HINSTANCE dllHandle = LoadLibrary("wxc.dll");
...
if (dllHandle != NULL) {
  FnPtrType fnPtr = (FnPtrType) GetProcAddress(dllHandle, "functionName");
  if (fnPtr != NULL) {
    return fnPtr(params...);
  }
}
FreeLibrary(dllHandle);

Code in Unix-land is very similar indeed:

void* lib_handle = dlopen("path/to/libwxc.so", RTLD_LAZY);
...
if (lab_handle != NULL) {
  FnPtrType fnPtr = dlsym(lab_handle, "functionName");
  if ((error = dlerror()) == NULL) {
    return fnPtr(params...);
  }
}
...
dlclose(lib_handle);

The problem is that I have about 2,000 functions which need to be exported in this way, and the only sane approach will be to automate things. I also need to find the correct locations to load and unload libraries, and to ensure that all function pointers are invalidated when I unload the DLL.

I now have most of the work done on changes to the build system, and my ideas thought through for the dynamically loaded libraries. In the next few entries (which will hopefully be more closely spaced than of late), we’ll look at these in some more detail.

One aside: I mentioned doing some work to enable Swig to be used as a wrapper generator for the wxc wrapper layer. I still want to do this, but it has been put aside in the “too much work right now” pile. Doing things as I am planning is less maintainable in the long run, but stands a fighting chance of being finished sometime this decade, and I want people to use wxHaskell more than I need to have an awesome system for automating the generation of wrappers over C++ code.

Leave a comment