summary refs log tree commit diff stats
path: root/lib/pure/dynlib.nim
blob: 48fd91b8f33c78b7e7777b5b04b266eeb310df45 (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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#
#
#            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.
##
runnableExamples:
  type
    GreetFunction = proc (): cstring {.gcsafe, stdcall.}

  proc loadGreet(lang: string) =
    let lib =
      case lang
      of "french":
        loadLib("french.dll")
      else:
        loadLib("english.dll")
    assert lib != nil, "Error loading library"

    let greet = cast[GreetFunction](lib.symAddr("greet"))
    assert greet != nil, "Error loading 'greet' function from library"

    echo greet()

    unloadLib(lib)


import std/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 a `LibraryError` 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
  ## `LibraryError` 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 the `dynlib`
  ## 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(genode):
  #
  # =========================================================================
  # Not implemented for Genode without POSIX. Raise an error if called.
  # =========================================================================
  #

  template raiseErr(prc: string) =
    raise newException(OSError, prc & " not implemented, compile with POSIX suport")

  proc dlclose(lib: LibHandle) =
    raiseErr(OSError, "dlclose")
  proc dlopen(path: cstring, mode: int): LibHandle =
    raiseErr(OSError, "dlopen")
  proc dlsym(lib: LibHandle, name: cstring): pointer =
    raiseErr(OSError, "dlsym")
  proc loadLib(path: string, global_symbols = false): LibHandle =
    raiseErr(OSError, "loadLib")
  proc loadLib(): LibHandle =
    raiseErr(OSError, "loadLib")
  proc unloadLib(lib: LibHandle) =
    raiseErr(OSError, "unloadLib")
  proc symAddr(lib: LibHandle, name: cstring): pointer =
    raiseErr(OSError, "symAddr")


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".}