summary refs log tree commit diff stats
path: root/lib/pure/dynlib.nim
blob: f31ae94ddbda64172932172035c6c444e1ae6cb0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#
#
#            Nim's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements the ability to access symbols from shared
## libraries. On POSIX this uses the ``dlsym`` mechanism, on
## Windows ``LoadLibrary``.
##
## Examples
## ========
##
## Loading a simple C function
## ---------------------------
##
## The following example demonstrates loading a function called 'greet'
## from a library that is determined at runtime based upon a language choice.
## If the library fails to load or the function 'greet' is not found,
## it quits with a failure error code.
##
## .. code-block::nim
##
##   import dynlib
##
##   type
##     greetFunction = proc(): cstring {.gcsafe, stdcall.}
##
##   let lang = stdin.readLine()
##
##   let lib = case lang
##   of "french":
##     loadLib("french.dll")
##   else:
##     loadLib("english.dll")
##
##   if lib == nil:
##     echo "Error loading library"
##     quit(QuitFailure)
##
##   let greet = cast[greetFunction](lib.symAddr("greet"))
##
##   if greet == nil:
##     echo "Error loading 'greet' function from library"
##     quit(QuitFailure)
##
##   let greeting = greet()
##
##   echo greeting
##
##   unloadLib(lib)
##

import strutils

type
  LibHandle* = pointer ## a handle to a dynamically loaded library

proc loadLib*(path: string, globalSymbols = false): LibHandle {.gcsafe.}
  ## loads a library from `path`. Returns nil if the library could not
  ## be loaded.

proc loadLib*(): LibHandle {.gcsafe.}
  ## gets the handle from the current executable. Returns nil if the
  ## library could not be loaded.

proc unloadLib*(lib: LibHandle) {.gcsafe.}
  ## unloads the library `lib`

proc raiseInvalidLibrary*(name: cstring) {.noinline, noreturn.} =
  ## raises an `EInvalidLibrary` exception.
  raise newException(LibraryError, "could not find symbol: " & $name)

proc symAddr*(lib: LibHandle, name: cstring): pointer {.gcsafe.}
  ## retrieves the address of a procedure/variable from `lib`. Returns nil
  ## if the symbol could not be found.

proc checkedSymAddr*(lib: LibHandle, name: cstring): pointer =
  ## retrieves the address of a procedure/variable from `lib`. Raises
  ## `EInvalidLibrary` if the symbol could not be found.
  result = symAddr(lib, name)
  if result == nil: raiseInvalidLibrary(name)

proc libCandidates*(s: string, dest: var seq[string]) =
  ## given a library name pattern `s` write possible library names to `dest`.
  var le = strutils.find(s, '(')
  var ri = strutils.find(s, ')', le+1)
  if le >= 0 and ri > le:
    var prefix = substr(s, 0, le - 1)
    var suffix = substr(s, ri + 1)
    for middle in split(substr(s, le + 1, ri - 1), '|'):
      libCandidates(prefix & middle & suffix, dest)
  else:
    add(dest, s)

proc loadLibPattern*(pattern: string, globalSymbols = false): LibHandle =
  ## loads a library with name matching `pattern`, similar to what `dlimport`
  ## pragma does. Returns nil if the library could not be loaded.
  ## Warning: this proc uses the GC and so cannot be used to load the GC.
  var candidates = newSeq[string]()
  libCandidates(pattern, candidates)
  for c in candidates:
    result = loadLib(c, globalSymbols)
    if not result.isNil: break

when defined(posix) and not defined(nintendoswitch):
  #
  # =========================================================================
  # This is an implementation based on the dlfcn interface.
  # The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,
  # NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least
  # as an emulation layer on top of native functions.
  # =========================================================================
  #
  import posix

  proc loadLib(path: string, globalSymbols = false): LibHandle =
    let flags =
      if globalSymbols: RTLD_NOW or RTLD_GLOBAL
      else: RTLD_NOW

    dlopen(path, flags)

  proc loadLib(): LibHandle = dlopen(nil, RTLD_NOW)
  proc unloadLib(lib: LibHandle) = discard dlclose(lib)
  proc symAddr(lib: LibHandle, name: cstring): pointer = dlsym(lib, name)

elif defined(nintendoswitch):
  #
  # =========================================================================
  # Nintendo switch DevkitPro sdk does not have these. Raise an error if called.
  # =========================================================================
  #

  proc dlclose(lib: LibHandle) =
    raise newException(OSError, "dlclose not implemented on Nintendo Switch!")
  proc dlopen(path: cstring, mode: int): LibHandle =
    raise newException(OSError, "dlopen not implemented on Nintendo Switch!")
  proc dlsym(lib: LibHandle, name: cstring): pointer =
    raise newException(OSError, "dlsym not implemented on Nintendo Switch!")
  proc loadLib(path: string, global_symbols = false): LibHandle =
    raise newException(OSError, "loadLib not implemented on Nintendo Switch!")
  proc loadLib(): LibHandle =
    raise newException(OSError, "loadLib not implemented on Nintendo Switch!")
  proc unloadLib(lib: LibHandle) =
    raise newException(OSError, "unloadLib not implemented on Nintendo Switch!")
  proc symAddr(lib: LibHandle, name: cstring): pointer =
    raise newException(OSError, "symAddr not implemented on Nintendo Switch!")

elif defined(windows) or defined(dos):
  #
  # =======================================================================
  # Native Windows Implementation
  # =======================================================================
  #
  type
    HMODULE {.importc: "HMODULE".} = pointer
    FARPROC {.importc: "FARPROC".} = pointer

  proc FreeLibrary(lib: HMODULE) {.importc, header: "<windows.h>", stdcall.}
  proc winLoadLibrary(path: cstring): HMODULE {.
      importc: "LoadLibraryA", header: "<windows.h>", stdcall.}
  proc getProcAddress(lib: HMODULE, name: cstring): FARPROC {.
      importc: "GetProcAddress", header: "<windows.h>", stdcall.}

  proc loadLib(path: string, globalSymbols = false): LibHandle =
    result = cast[LibHandle](winLoadLibrary(path))
  proc loadLib(): LibHandle =
    result = cast[LibHandle](winLoadLibrary(nil))
  proc unloadLib(lib: LibHandle) = FreeLibrary(cast[HMODULE](lib))

  proc symAddr(lib: LibHandle, name: cstring): pointer =
    result = cast[pointer](getProcAddress(cast[HMODULE](lib), name))

else:
  {.error: "no implementation for dynlib".}