@@ -854,18 +854,135 @@ it must be handled in other ways.
854854
855855In some cases, the exact name or path of the needed library is not known in
856856advance and must be computed at run time. To handle such cases, the library
857- component specification can be a value such as ` Libdl.LazyLibrary ` . For
858- example, in ` @ccall blas.dgemm() ` , there can be a global defined as `const blas
859- = LazyLibrary("libblas")` . The runtime will call ` dlsym(: dgemm , dlopen(blas))`
860- when the ` @ccall ` itself is executed. The ` Libdl.dlopen ` function can be
861- overloaded for custom types to provide alternate behaviors. However, it is
862- assumed that the library location does not change once it is determined, so the
863- result of the call can be cached and reused. Therefore, the number of times the
864- expression executes is unspecified, and returning different values for multiple
865- calls results in unspecified behavior.
866-
867- If even more flexibility is needed, it is possible
868- to use computed values as function names by staging through [ ` eval ` ] ( @ref ) as follows:
857+ component specification can be a value such as ` Libdl.LazyLibrary ` . The runtime
858+ will call ` Libdl.dlopen ` on that object when first used by a ` ccall ` .
859+
860+ ### [ Using LazyLibrary for Lazy Loading] (@id man-lazylibrary)
861+
862+ [ ` Libdl.LazyLibrary ` ] ( @ref ) provides a thread-safe mechanism for deferring library loading
863+ until first use. This is the recommended approach for library initialization in modern Julia code.
864+
865+ A ` LazyLibrary ` represents a library that opens itself (and its dependencies) automatically
866+ on first use in a ` ccall() ` , ` @ccall ` , ` dlopen() ` , ` dlsym() ` , ` dlpath() ` , or ` cglobal() ` .
867+ The library is loaded exactly once in a thread-safe manner, and subsequent calls reuse the
868+ loaded library handle.
869+
870+ #### Basic Usage
871+
872+ ``` julia
873+ using Libdl
874+
875+ # Define a LazyLibrary as a const for optimal performance
876+ const libz = LazyLibrary (" libz" )
877+
878+ # Use directly in @ccall - library loads automatically on first call
879+ @ccall libz. deflate (strm:: Ptr{Cvoid} , flush:: Cint ):: Cint
880+
881+ # Also works with ccall
882+ ccall ((:inflate , libz), Cint, (Ptr{Cvoid}, Cint), strm, flush)
883+ ```
884+
885+ #### Platform-Specific Libraries
886+
887+ For code that needs to work across different platforms:
888+
889+ ``` julia
890+ const mylib = LazyLibrary (
891+ if Sys. iswindows ()
892+ " mylib.dll"
893+ elseif Sys. isapple ()
894+ " libmylib.dylib"
895+ else
896+ " libmylib.so"
897+ end
898+ )
899+ ```
900+
901+ #### Libraries with Dependencies
902+
903+ When a library depends on other libraries, specify the dependencies to ensure
904+ they load in the correct order:
905+
906+ ``` julia
907+ const libfoo = LazyLibrary (" libfoo" )
908+ const libbar = LazyLibrary (" libbar" ; dependencies= [libfoo])
909+
910+ # When libbar is first used, libfoo is loaded first automatically
911+ @ccall libbar. bar_function (x:: Cint ):: Cint
912+ ```
913+
914+ #### Lazy Path Construction
915+
916+ For libraries whose paths are determined at runtime, use ` LazyLibraryPath ` :
917+
918+ ``` julia
919+ # Path is constructed when library is first accessed
920+ const mylib = LazyLibrary (LazyLibraryPath (artifact_dir, " lib" , " libmylib.so" ))
921+ ```
922+
923+ #### Initialization Callbacks
924+
925+ If a library requires initialization after loading:
926+
927+ ``` julia
928+ const mylib = LazyLibrary (" libmylib" ;
929+ on_load_callback = () -> @ccall mylib. initialize ():: Cvoid
930+ )
931+ ```
932+
933+ !!! warning
934+ The ` on_load_callback ` should be minimal and must not call ` wait() ` on any tasks.
935+ It is called exactly once by the thread that loads the library.
936+
937+ #### Conversion from ` __init__() ` Pattern
938+
939+ Before ` LazyLibrary ` , library paths were often computed in ` __init__() ` functions.
940+ This pattern can be replaced with ` LazyLibrary ` for better performance and thread safety.
941+
942+ Old pattern using ` __init__() ` :
943+
944+ ``` julia
945+ # Old: Library path computed in __init__()
946+ libmylib_path = " "
947+
948+ function __init__ (
949+ # Loads library on startup, whether it is used or not
950+ global libmylib_path = find_library ([" libmylib" ])
951+ end
952+
953+ function myfunc (x)
954+ ccall ((:cfunc , libmylib_path), Cint, (Cint,), x)
955+ end
956+ ```
957+
958+ New pattern using `LazyLibrary`:
959+
960+ ``` julia
961+ # New: Library as const, no __init__() needed
962+ const libmylib = LazyLibrary (" libmylib" )
963+
964+ function myfunc (x)
965+ # Library loads automatically just before calling `cfunc`
966+ @ccall libmylib. cfunc (x:: Cint ):: Cint
967+ end
968+ ```
969+
970+ For more details, see the [`Libdl.LazyLibrary`](@ref) documentation.
971+
972+ ### Overloading `dlopen` for Custom Types
973+
974+ The runtime will call `dlsym(:function, dlopen(library)::Ptr{Cvoid})` when a `@ccall` is executed.
975+ The `Libdl.dlopen` function can be overloaded for custom types to provide alternate behaviors.
976+ However, it is assumed that the library location and handle does not change
977+ once it is determined, so the result of the call may be cached and reused.
978+ Therefore, the number of times the `dlopen` expression executes is unspecified,
979+ and returning different values for multiple calls will results in unspecified
980+ (but valid) behavior.
981+
982+ ### Computed Function Names
983+
984+ If even more flexibility is needed, it is possible to use computed values as
985+ function names by staging through [`eval`](@ref) as follows:
869986
870987``` julia
871988@eval @ccall " lib" .$ (string (" a" , " b" ))():: Cint
@@ -876,38 +993,37 @@ expression, which is then evaluated. Keep in mind that `eval` only operates at t
876993so within this expression local variables will not be available (unless their values are substituted
877994with `$`). For this reason, `eval` is typically only used to form top-level definitions, for example
878995when wrapping libraries that contain many similar functions.
879- A similar example can be constructed for [ ` @cfunction ` ] ( @ref ) .
880-
881- However, doing this will also be very slow and leak memory, so you should usually avoid this and instead keep
882- reading.
883- The next section discusses how to use indirect calls to efficiently achieve a similar effect.
884996
885- ## Indirect Calls
997+ ### Indirect Calls
886998
887- The first argument to ` @ccall ` can also be an expression evaluated at run time. In this
888- case, the expression must evaluate to a ` Ptr ` , which will be used as the address of the native
889- function to call. This behavior occurs when the first ` @ccall ` argument contains references
890- to non-constants, such as local variables, function arguments, or non-constant globals.
999+ The first argument to `@ccall` can also be an expression to be evaluated at run
1000+ time, each time it is used. In this case, the expression must evaluate to a
1001+ `Ptr`, which will be used as the address of the native function to call. This
1002+ behavior occurs when the first `@ccall` argument is marked with `$` and when
1003+ the first `ccall` argument is not a simple constant literal or expression in
1004+ `()`. The argument can be any expression and can use local variables and
1005+ arguments and can return a different value every time.
8911006
892- For example, you might look up the function via ` dlsym ` ,
893- then cache it in a shared reference for that session. For example:
1007+ For example, you might implement a macro similar to `cglobal` that looks up the
1008+ function via `dlsym`, then caches the pointer in a shared reference (which is
1009+ auto reset to C_NULL during precompile saving).
1010+ For example:
8941011
8951012``` julia
8961013macro dlsym (lib, func)
897- z = Ref {Ptr{Cvoid}} (C_NULL )
1014+ z = Ref (C_NULL )
8981015 quote
899- let zlocal = $ z[]
900- if zlocal == C_NULL
901- zlocal = dlsym ($ (esc (lib)):: Ptr{Cvoid} , $ (esc (func))):: Ptr{Cvoid}
902- $ z[] = zlocal
903- end
904- zlocal
1016+ local zlocal = $ z[]
1017+ if zlocal == C_NULL
1018+ zlocal = dlsym ($ (esc (lib)):: Ptr{Cvoid} , $ (esc (func))):: Ptr{Cvoid}
1019+ $ z[] = zlocal
9051020 end
1021+ zlocal
9061022 end
9071023end
9081024
909- mylibvar = Libdl . dlopen (" mylib" )
910- @ccall $ (@dlsym (mylibvar, " myfunc" ))():: Cvoid
1025+ const mylibvar = LazyLibrary (" mylib" )
1026+ @ccall $ (@dlsym (dlopen ( mylibvar) , " myfunc" ))():: Cvoid
9111027```
9121028
9131029## Closure cfunctions
0 commit comments