Skip to content

Commit cbba715

Browse files
committed
Docs: Update the LUAZIP guide for vfs.dlopen
Since this makes it much easier to use shared libraries, many details have been added. A full example mimicking the current integration tests should help illustrate the core ideas.
1 parent 67e30cd commit cbba715

File tree

1 file changed

+53
-7
lines changed

1 file changed

+53
-7
lines changed

docs/how-to-guides/standalone-executables.md

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,59 @@ In the above example, Evo will load both files from the VFS, if they exist, or u
4848
1. Whenever a module exists in the VFS and on disk, the VFS takes priority (for security and performance reasons)
4949
1. All `.` (dots) in the path names are internally replaced by `/` to cleanly map to the VFS paths
5050

51-
This change isn't disruptive; all Evo does is add a custom [searcher](https://www.lua.org/manual/5.2/manual.html#pdf-package.searchers) that looks into the VFS first when it dectects it is running a standalone executable.
51+
This change isn't disruptive; all Evo does is add a custom [searcher](https://www.lua.org/manual/5.2/manual.html#pdf-package.searchers) that looks into the VFS first when it dectects it is running a standalone executable. You can still add your own searchers to load different versions, or remove them.
5252

53-
There is one limitation that does exist: You can't directly load native libraries from the VFS.
53+
The path resolution follows all the standard rules. It can be configured with [package.cpath](https://www.lua.org/manual/5.1/manual.html#pdf-package.path) and `LUA_PATH`.
5454

55-
If you want to use `require` (or `ffi.load`) to load a C module, you'll need to provide the DLL/SO files alongside the standalone executable. Alternatively, you can build the DLL/SO files into the app, but then you'll have to extract them from the ZIP archive.
55+
### Lua C-API Modules
5656

57-
In the future, helpers for this [will likely be added](https://github.com/evo-lua/evo-runtime/issues/488), but it's somewhat difficult to predict what exactly is needed here. For now the runtime only provides the most basic support.
57+
Because this mechanism makes use of Lua's [`require`](https://www.lua.org/manual/5.1/manual.html#pdf-require) system, you can only load [Lua C modules](https://www.lua.org/manual/5.1/manual.html#3) with it. In order to do so, you should bundle your application with a compatible version of the C module and use it like you would any other module available on the user's system. The `vfs.searcher` will discover it and let Lua handle the rest.
5858

59-
For the time being, you can distribute C modules separetely (on disk) or extract everything to a temporary directory before loading the app.
59+
The path resolution follows all the standard rules. It can be configured with [package.cpath](https://www.lua.org/manual/5.1/manual.html#pdf-package.cpath) and `LUA_CPATH`.
60+
61+
:::caution
62+
Although many Lua C modules are available and can be used with `require`, this has a significant impact on performance and often incurs additional maintenance costs. Using the C API is of course supported, but not the recommended approach to loading native libraries in this runtime. If you do want to use them, you must make sure they're built for LuaJIT, which is based on Lua 5.1 and not compatible with more recent C API versions.
63+
:::
64+
65+
There's another way to get access to C code that you want to ship with your application, described below.
66+
67+
### Shared Libraries and the FFI
68+
69+
Evo allows you to use shared libraries (`.dll`, `.so`, `.dylib` files) using LuaJIT's [foreign function interface](https://luajit.org/ext_ffi_api.html). This introduces potentially unsafe code paths, but has significant performance benefits. It's also a lot easier to create FFI bindings; no glue code is needed beyond the definitions for types and function signatures (called "cdefs").
70+
71+
The `vfs` library supports loading these kinds of modules even from within self-contained app bundles:
72+
73+
```lua title=vfs-dlopen-example.lua
74+
local ffi = require("ffi")
75+
local uv = require("uv")
76+
local vfs = require("vfs")
77+
78+
-- When run from a LUAZIP app, the cache is already populated with the current app itself
79+
-- Otherwise, you can use vfs.decode to load the app bundle here - but it'll be much slower
80+
local zipApp = vfs.cachedAppBundles[uv.exepath()]
81+
82+
-- For brevity's sake, export only one function. This could easily cover the entire library interface
83+
local cdefs = [[
84+
const char* zlibVersion(void);
85+
]]
86+
87+
ffi.cdef(cdefs)
88+
89+
-- Loading may fail for various reasons, so that vfs.dlopen returns a failure tuple (nil, errorMessage)
90+
-- Given the right paths, this will work when running the script from disk and also when bundled later
91+
local sharedLibraryPath = "zlib.so" -- ... or .dll, .dylib (platform-specific)
92+
local zlibShared = vfs.dlopen(zipApp, sharedLibraryPath) or ffi.load(sharedLibraryPath)
93+
94+
-- If loading did succeed, all of the previously defined functions can now be used from Lua
95+
local versionString = ffi.string(zlibShared.zlibVersion())
96+
printf("Successfully loaded zlib version %s from the LUAZIP bundle!", versionString)
97+
```
98+
99+
There is no path resolution or magic search prefixes to configure. The `vfs.dlopen` searcher simply browses the executable's archive for a file with the given path, which is relative to the root directory. It then extracts the shared object file and uses `ffi.load`, which will invoke the operating system's facilities for loading shared libraries.
100+
101+
You can use `vfs.dlname` to get some portability, which is used by default when no file extension was detected.
102+
103+
To see what's inside the archive, use the `miniz` or `vfs` libraries. The system is designed to be transparent.
60104

61105
## Native Look and Feel
62106

@@ -84,9 +128,11 @@ For commercial use cases, however, you may want to consider moving business-crit
84128

85129
Although Evo comes with quite a few "batteries" that you don't need to manually distribute, the builtin APIs don't cover every use case.
86130

87-
Complex programs often require the help of advanced libraries, which are usually bundled alongside the app (as DLL or shared object files). While LuaJIT can easily access these, you will still need to include them. If creating a standalone executable, you'd then have to extract all the needed libraries at runtime before you can dynamically load them. This is what other interpreted languages do, as well.
131+
Complex programs often require the help of advanced libraries, which are usually bundled alongside the app (as DLL or shared object files). While LuaJIT can easily access these, you will still need to include them. Scripts inside a standalone executable still need to load libraries at runtime. This is what other interpreted languages do, as well.
132+
133+
Unfortunately, shipping binaries to different Linux systems may be troublesome. But at least users there won't necessarily expect it. You can call `ffi.load` with just the library name and rely on system libraries installed by the user, which works on any modern distribution with a built-in package manager (such as `apt` or `pacman`).
88134

89-
Unfortunately, shipping binaries to different Linux systems may be troublesome. But at least users there won't necessarily expect it.
135+
Both the libraries and the runtime executable included in a self-contained application bundle must be compatible with the architecture of the system that's ultimately meant to run them. Cross-compilation is not supported.
90136

91137
### Licensing Issues
92138

0 commit comments

Comments
 (0)