As far as I can tell, there's no automatic way to look into the current exe for luaopen_* functions when requiring a module. The default C package loader looks inside foo.dll for luaopen_foo, but if I'm writing an .exe, I can't just implement an external linkage function luaopen_foo and expect that loader to find it. It either has to be in foo.dll on the cpath, or I have to manually add it.
[...]
I'm suggesting that before Lua 5.5 launches, the default C package loader look in the current .exe for luaopen_foo when requiring "foo".
You can actually write this functionality in Lua:
-- get path to executable first
local app
for i = 0, -math.huge,-1 do
if not arg[i] then break end
app = arg[i]
end
-- add a searcher based on package.loadlib
table.insert(package.searchers, 1, function(modname)
local symbol = "luaopen_"..modname:gsub("%.", "_")
local f,err = package.loadlib(app, symbol)
return f or string.format("failed loading `%s` from executable '%s': %s", symbol, app, err:gsub("\n$", ""))
end)
-- Load for example the module luaopen_builtin_mod
require "builtin.mod".foo()
This however only works for some platforms, as dynamically loading the executable runs into problems on some other platforms. I tested this for Windows (under MinGW, didn't try msvc), Ubuntu and Alpine, and while Windows and Alpine worked fine, Ubuntu complained about dynamically loading the executable. I suspect the difference between Alpine and Ubuntu is that Alpine uses musl, while Ubuntu uses glibc. See this StackOverflow post for more info on why you seemingly can't dlopen executables.
If you plan to go cross platform at some point, I'd recommend to
do what Scott says:
Keep a list of your C modules around (of your luaopen_<bla>
entrypoints) and load the function pointers directly into the
preload table, for example.
You
can actually write this functionality in Lua:
-- get path to executable first
local app
for i = 0, -math.huge,-1 do
if not arg[i] then break end
app = arg[i]
end
-- add a searcher based on package.loadlib
table.insert(package.searchers, 1, function(modname)
local symbol = "luaopen_"..modname:gsub("%.", "_")
local f,err = package.loadlib(app, symbol)
return f or string.format("failed loading `%s` from executable '%s': %s", symbol, app, err:gsub("\n$", ""))
end)
-- Load for example the module luaopen_builtin_mod
require "builtin.mod".foo()
Little Addendum: For my example to work, the executable must be
compiled with -rdynamic on Linux and -Wl,--export-all-symbols
on Windows, otherwise package.loadlib won't find the
symbol, even if nm reports it correctly.