DLL import location for libokFrontPanel.dylib

I’m having a problem similar to what the user reported in this old forum post (Mac OSX Leopard). Specifically, your Front-Panel DLL is looking in some temporary build directory since that’s apparently where it was located during the build or linking step. The problem is that in modern build flows [such as Bazel], that path is in some sandbox, so it is no longer valid once we have built a binary that links agains your provided DLL. Specifically:

ImportError: dlopen(/var/folders/73/sv_fkcf52qqbr83y5nxbpmhh0000gn/lengthy_redacted_file_path/opalkelly/xem6001/pyok.dylib, 2): Library not loaded: libokFrontPanel.dylib
Referenced from: /var/folders/73/sv_fkcf52qqbr83y5nxbpmhh0000gn/lengthy_redacted_file_path/opalkelly/xem6001/pyok.dylib

Paul – can you please confirm? You have determined that there is not an issue with our Python support.

However, you have offered up a solution to help people use our existing, working Python support to work with Cython?

It should be noted that we do not officially support Cython.

Yes, I believe I have it working in Cython, which should be a bit faster/more modern than the SWIG wrapper you distribute. I’m able to use your C++ API to instantiate a legacy cFrontPanel object, find a board that is plugged in (XEM6001) and program a bit file. It seems to be returning all successful status flags, so I think it is working correctly, but I haven’t yet verified in the lab. One thing I found was that the cFrontPanel object needs to be heap allocated in Cython, not stack allocated.

Wait, actually the issue is not resolved, I think I just copied the DLL into /usr/local/lib while debugging, and thought I had fixed it properly in the build flow. It is unusual that DLL loading fails at runtime only for your library…are you doing something unusual like perhaps searching for libokFrontPanel.dylib in that library itself? Would it be possible to distribute a “.a” file so we can just statically link and avoid all these cross-platform dlopen issues?

Please reach out to support@opalkelly.com to discuss options for your custom support needs. Python is officially supported and should be working fine, but Cython is not an official support target.

This actually has nothing to do with Cython, sorry I implied that with my debugging above. I actually can’t even import the DLL in pure C++ unless it is in a system path like /usr/local/lib just calling your C++ DLL with my C++. This leads me to ask the question “what is going on in that DLL?” because it’s unusual that this wouldn’t work in the simplest Bazel compilation example. Sorry I led us down the Cython discussion above, but that appears to be unrelated to the issue. The problem is something to do with how you load your DLL at runtime and how that file is located when it is included in a Bazel build. Here’s the simplest example that will compile (may have a typo or two), but will fail at runtime due to “dyld: Library not loaded: libokFrontPanel.dylib … image not found”. Can you share details on how you actually load the DLL? It’s possible this is a Bazel issue but I think that’s unlikely since it was an issue in 2017 and was apparently fixed (https://github.com/bazelbuild/bazel/issues/3450).

// file: third_party/opalkelly/BUILD
cc_library(
    name = "okfrontpanel",
    srcs = ["libokFrontPanel.dylib"],  # or ".so" if on linux
    hdrs = ["okFrontPanel.h"],
    copts = [
        "-DokNO_MANAGER",
        # "-DokNO_DELAY_LOAD_FRONTPANEL",  # necessary?
    ],
)

// file: client/BUILD
cc_library(
    name = "spi",
    srcs = ["Spi.cpp"],
    hdrs = ["Spi.h"],
    deps = ["//third_party/opalkelly:okfrontpanel"],
)

cc_binary(
    name = "main",
    srcs = ["main.cpp"],
    deps = [":spi"],
)

// file: client/Spi.h
#include "third_party/opalkelly/okFrontPanel.h"
class Spi : {
 public:
  Spi();
 private:
  OpalKellyLegacy::okCFrontPanel ok_;
}

// file:: client/Spi.cpp
#include "client/Spi.h"
Spi::Spi {
    int device_count = ok_.GetDeviceCount();
}

int main() {
  Spi x;
  return 0;
}

Maybe I have to dig into some more Bazel options, like cc_import (https://docs.bazel.build/versions/master/be/c-cpp.html#cc_import).

Can you tell us which of our samples you’re having trouble with? As far as we know, the C++ samples (DESTester and RAMTester) compile just fine and work with the dylib in the local folder.

It’s probably best to start with our samples and get things working and not involve third party software such as Cython and Bazel that could be confusing the issues.

We already have simple examples working and now we’re trying to use your library in a Bazel build flow. As I said, Cython is not actually the problem since the simplest C++ example can’t load the DLL either. I’ll note that cc_import doesn’t work either:

cc_import(
    name = "okfrontpanel",
    hdrs = ["okFrontPanel.h"],
    shared_library = "libokFrontPanel.dylib",
)

I guess I’ll try to dig into the Bazel build artifacts and see why it isn’t getting included in some importable path. It produces some soft links so I think the location it is trying load from is something like this:

bazel-bin/client/spi.runfiles/workspace/_solib_darwin_x86_64/_U_S_Sthird_Uparty_Sopalkelly_Cokfrontpanel___Uthird_Uparty_Sopalkelly/libokFrontPanel.dylib

That is actually a soft link to a sandbox in /private/var/tmp/…
You mention “in the local path” but no real build system would locate DLLs in the same path as the binary build targets, and that includes Bazel. Maybe you could shed some light on whether the DLL itself is doing some sort of path analysis/decision-making for loading?

It is maybe some complicated problem involving osx loaders and where/how bazel writes the paths to the DLL in the compiled binary. Here is some background information:

otool -l returns 32 DLL paths to load relative to @loader_path, including an LC_PATH cmd

@loader_path/../../_solib_darwin_x86_64/_U_S_Sthird_Uparty_Sopalkelly_Cokfrontpanel___Uthird_Uparty_Sopalkelly

The path itself appears to be valid:
bazel-bin/_solib_darwin_x86_64/_U_S_Sthird_Uparty_Sopalkelly_Cokfrontpanel___Uthird_Uparty_Sopalkelly/libokFrontPanel.dylib

But it looks like there is no LC_LOAD_DYLIB command for the Front Panel DLL because the loader is supposed to search this path for the libokFrontPanel.dylib file and find it. Notably: all the other DLLs do not use LC_PATH but instead just directly use an LC_LOAD_DYLIB command that points to the exact .so file.

This leads me to believe that somewhere looking for libokFrontPanel.dylib, the LC_PATH specified in the binary is not actually searched.