C Interface -- Library name must be a constant expression?

I’m calling a C function:

LIBTTV = "path/to/library.so"
ccall( (:TTVFast, LIBTTV), Cvoid, ... )

This gives an error:

ERROR: LoadError: TypeError: in top-level scope, in ccall:
first argument not a pointer or valid constant expression,
expected Ptr, got Tuple{Symbol,String}

If I replace LIBTTV with the actual string:

ccall( (:TTVFast, "path/to/library.so"), Cvoid, ... )

The call works correctly.

Now, this is a little weird. Why does the path to the library need to be a constant? Needless to say, hard-coding a path is bad form. I want to use Libdl to find the correct path to the library. I can’t imagine that this is intended behaviour. Is there something that I’m doing wrong? How can I put the path to the library in a variable?

Thanks for the help.

This is entirely desired behavior. You should look for the library and hard code it at build time. For one time script, just use a const global variable for the name since there isn’t a distinction between build time and runtime there.

If you must look for the library at runtime, you need to switch to dlopen and dlsym explicitly.

What exactly do you mean by “hard code it at build time”? This is a script. There is no “build time”.

Also, a function cannot make a global const variable. So apparently the only option is to de-modularize the code. It’s annoying that the desired behaviour appears to be “broken”.

Well, as I said, for a one time script, the build time and the run time are the same.

For one, that’s not true, but yes, you need to determine the name before you use the function.

There’s nothing non modular about it.

That’s because (edited:) your interpretation of the desired behavior or your definition of broken is wrong. What you are thinking is likely the C dlopen behavior. But ccall is not a function, it’s a syntax exactly the same as writing normal function calls in C. What you want does not work for the same reason const char *name = "sin"; double f(double) asm(name); f(1.0); doesn’t work as you need a compile time constant there as the symbol name (and since Julia allows you to explicitly specify the lib name, that as well).

What you want corresponds to C dlopen which will work exactly the same in Julia with basically the same function names. You do not get address memorization automatically, since the lookup logic can be arbitrary code and it’s impossible for the compiler to keep track of, again, same as c. If you have some specific pattern, you can create a memorized version of dlsym easily, I believe pycall has one implementation. If this is what you mean by modularize then you should just do that.

Note that I use C as example but it could really be any language with a similar interface.

2 Likes

The better way to do this is ccall( (:TTVFast, "library.so"), Cvoid, ... ) and then make sure "library.so" is in your LD_LIBRARY_PATH. This will make your script work on different systems too where the path might be different and each can have its own LD_LIBRARY_PATH.

If its really important that its specifiable via an argument, you could use a generated function, which effectively “moves it to build-time” like @yuyichao suggests, I think something like the following will work:

call_TTVFast(LIBTTV::String) = call_TTVFast(Val(Symbol(LIBTTV)))

@generated function call_TTVFast(::Val{LIBTTV}) where {LIBTTV}
    quote 
        ccall( (:TTVFast, $(String(LIBTTV))), Cvoid, ...)
    end
end

call_TTVFast("/path/to/library.so")

Don’t call the compiler for the sake of calling the compiler. This will give you a performance worse than doing a Dict{String,Ptr{Cvoid}} lookup as memorization.

Why doesn’t it work for me? Am I doing something wrong?:

julia> function foo()
           global const x = 3
       end
ERROR: syntax: `global const` declaration not allowed
inside function around REPL[1]:2

Thanks for the clarification. I did think that ccall was a function.

Use eval. It indeed does not allow you to do what you want in the same function since you still

However, you don’t need to put all those code in global scope. Your (“build time”) initialization code can be a function. Purely from a code organization point of view.

I had the same confusion initially so I’m not surprized…

Yeah, ccall probably should use macro syntax but it predates macros.

2 Likes

Here is the documentation on Libdl, for dynamic linking. Libdl.dlopen the library, and Libdl.dlsym to get function pointers that work with ccall.

julia> using Libdl

julia> libc  = dlopen("/usr/lib/libc.so.6")
Ptr{Nothing} @0x00007f45f0a783f0

julia> clock_pointer = dlsym(libc, :clock)
Ptr{Nothing} @0x00007f45f0396f80

julia> noargccall(function_pointer) = ccall(function_pointer, Int32, ())
noargccall (generic function with 1 method)

julia> noargccall(clock_pointer)
5177952

julia> noargccall(clock_pointer)
5328805