├── dlls ├── config.nims ├── exception.nim ├── require_sqlite.nim └── testdll.nim ├── examples ├── config.nims ├── sqlite │ ├── config.nims │ ├── sqlite3.nim │ └── db_sqlite.nim ├── ssl │ ├── libeay32.dll │ ├── libeay64.dll │ ├── ssleay32.dll │ └── ssleay64.dll ├── embedded_ssl_test_run.cmd ├── ssl_test.nim ├── embedded_sqlite.nim ├── embedded_ssl_test.nim ├── usage2.nim └── usage1.nim ├── tests ├── config.nims ├── test_exception_dll.nim ├── test_rtlib.nim ├── test_hook.nim └── test_basic.nim ├── changelog.md ├── license.txt ├── memlib.nimble ├── readme.md ├── memlib ├── private │ └── sharedseq.nim └── rtlib.nim ├── docs └── nimdoc.out.css └── memlib.nim /dlls/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/..") 2 | -------------------------------------------------------------------------------- /examples/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/..") 2 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/..") 2 | -------------------------------------------------------------------------------- /examples/sqlite/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../..") 2 | -------------------------------------------------------------------------------- /examples/ssl/libeay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khchen/memlib/HEAD/examples/ssl/libeay32.dll -------------------------------------------------------------------------------- /examples/ssl/libeay64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khchen/memlib/HEAD/examples/ssl/libeay64.dll -------------------------------------------------------------------------------- /examples/ssl/ssleay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khchen/memlib/HEAD/examples/ssl/ssleay32.dll -------------------------------------------------------------------------------- /examples/ssl/ssleay64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khchen/memlib/HEAD/examples/ssl/ssleay64.dll -------------------------------------------------------------------------------- /examples/embedded_ssl_test_run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Reset the PATH so that no dependencies will be found. 3 | set backup_path=%path% 4 | set path= 5 | embedded_ssl_test.exe 6 | set path=%backup_path% 7 | set backup_path= 8 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | Version 1.3.0 2 | ------------- 3 | * Suppress warning on last devel compiler (1.7.x). 4 | * Fix hook not works on some version of Windows. 5 | * Rewrite embedded_ssl example. 6 | 7 | Version 1.2.0 8 | ------------- 9 | * Fix cstring issue to work with winim 3.7.0. 10 | * Allows varargs pragma (#2). 11 | * Modify tests and examples to test varargs. 12 | 13 | Version 1.1.0 14 | ------------- 15 | * Add memlib/rtlib submodule. 16 | * Fix typo. 17 | 18 | Version 1.0.0 19 | ------------- 20 | * Initial release 21 | -------------------------------------------------------------------------------- /tests/test_exception_dll.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import memlib 9 | 10 | when defined(cpu64): 11 | const DllPath = "../dlls/exception64.dll" 12 | else: 13 | const DllPath = "../dlls/exception32.dll" 14 | 15 | const DllData = staticReadDll DllPath 16 | 17 | discard checkedLoadLib(DllData) 18 | -------------------------------------------------------------------------------- /examples/ssl_test.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | # This file has some codes that need SSL related DLL to run. 9 | # Compile this file to ssl_test.exe and use embedded_ssl_test.nim to test it. 10 | # 11 | # nim c -d:ssl ssl_test 12 | # 13 | 14 | import httpclient 15 | 16 | var client = newHttpClient() 17 | echo client.getContent("https://google.com")[0..127] 18 | -------------------------------------------------------------------------------- /tests/test_rtlib.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import memlib/rtlib 9 | 10 | type IStream = object 11 | 12 | proc SHCreateMemStream(pInit: pointer, cbInit: cint): ptr IStream 13 | {.rtlib: "shlwapi", stdcall, importc: 12.} 14 | 15 | var str = "abcde" 16 | let pStream = SHCreateMemStream(addr str[0], cint str.len) 17 | assert pStream != nil 18 | 19 | proc printf(formatstr: cstring) {.rtlib: "msvcrt", importc: "printf", varargs.} 20 | 21 | printf("This works %s", "as expected") 22 | -------------------------------------------------------------------------------- /dlls/exception.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import winim/lean 9 | 10 | proc NimMain() {.cdecl, importc.} 11 | 12 | proc test() = 13 | try: 14 | echo "try to raise an exception..." 15 | raise newException(Exception, "") 16 | except: 17 | echo "exception catched" 18 | finally: 19 | echo "finally executed" 20 | 21 | proc DllMain*(hinst: HINSTANCE, reason: DWORD, reserved: LPVOID): BOOL {.stdcall, exportc, dynlib.} = 22 | if reason == DLL_PROCESS_ATTACH: 23 | NimMain() 24 | test() 25 | return TRUE 26 | -------------------------------------------------------------------------------- /dlls/require_sqlite.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import winim/lean, memlib 9 | import strformat except `&` 10 | 11 | when defined(cpu64): 12 | const SqliteDllName = "sqlite3_64" 13 | else: 14 | const SqliteDllName = "sqlite3_32" 15 | 16 | proc NimMain() {.cdecl, importc.} 17 | 18 | withPragma { dynlib: SqliteDllName, cdecl, importc }: 19 | proc sqlite3_libversion(): cstring 20 | 21 | withPragma { exportc, dynlib, stdcall }: 22 | proc DllMain*(hinst: HINSTANCE, reason: DWORD, reserved: LPVOID): BOOL = 23 | if reason == DLL_PROCESS_ATTACH: 24 | NimMain() 25 | echo fmt"SQLITE3 v{$sqlite3_libversion()} loaded" 26 | return TRUE 27 | -------------------------------------------------------------------------------- /tests/test_hook.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import winim/lean 9 | import memlib, terminal 10 | 11 | when defined(cpu64): 12 | const 13 | DllPath = "../dlls/require_sqlite64.dll" 14 | SqliteDllName = "sqlite3_64" 15 | 16 | else: 17 | const 18 | DllPath = "../dlls/require_sqlite32.dll" 19 | SqliteDllName = "sqlite3_32" 20 | 21 | const 22 | DllData = staticReadDll DllPath 23 | SqliteData = staticReadDll SqliteDllName 24 | 25 | SetEnvironmentVariable("path", nil) 26 | 27 | echo "Hook the Windows API [Y/N]?" 28 | while true: 29 | case getch() 30 | of 'y', 'Y': 31 | checkedLoadLib(SqliteData).hook(SqliteDllName) 32 | break 33 | of 'n', 'N': 34 | break 35 | else: discard 36 | 37 | echo "Try to load \"require_sqlite32.dll\" ..." 38 | discard checkedLoadLib(DllData) 39 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2021-2022 Kai-Hung Chen, Ward 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /examples/embedded_sqlite.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import sqlite/db_sqlite, math 9 | 10 | let db = open(":memory:", "", "", "") 11 | 12 | db.exec(sql"DROP TABLE IF EXISTS my_table") 13 | db.exec(sql"""CREATE TABLE my_table ( 14 | id INTEGER PRIMARY KEY, 15 | name VARCHAR(50) NOT NULL, 16 | i INT(11), 17 | f DECIMAL(18, 10) 18 | )""") 19 | 20 | db.exec(sql"BEGIN") 21 | for i in 1..1000: 22 | db.exec(sql"INSERT INTO my_table (name, i, f) VALUES (?, ?, ?)", 23 | "Item#" & $i, i, sqrt(i.float)) 24 | db.exec(sql"COMMIT") 25 | 26 | for x in db.fastRows(sql"SELECT * FROM my_table"): 27 | echo x 28 | 29 | let id = db.tryInsertId(sql"""INSERT INTO my_table (name, i, f) 30 | VALUES (?, ?, ?)""", 31 | "Item#1001", 1001, sqrt(1001.0)) 32 | echo "Inserted item: ", db.getValue(sql"SELECT name FROM my_table WHERE id=?", id) 33 | 34 | db.close() 35 | -------------------------------------------------------------------------------- /dlls/testdll.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import winim/lean, memlib 9 | import strformat except `&` 10 | 11 | proc NimMain() {.cdecl, importc.} 12 | 13 | withPragma { exportc, dynlib }: 14 | 15 | var buffer: string 16 | 17 | proc test_cdecl*(a, b, c: cstring): cstring {.cdecl.} = 18 | buffer = fmt"cdecl {a} {b} {c}" 19 | return cstring buffer 20 | 21 | proc test_stdcall*(a, b, c: cstring): cstring {.stdcall.} = 22 | buffer = fmt"stdcall {a} {b} {c}" 23 | return cstring buffer 24 | 25 | proc test_fastcall*(a, b, c: cstring): cstring {.fastcall.} = 26 | buffer = fmt"fastcall {a} {b} {c}" 27 | return cstring buffer 28 | 29 | proc test_varargs(i: cint): cint {.cdecl, varargs.} = 30 | {.emit: """ 31 | int sum = i; 32 | va_list argp; 33 | va_start(argp, i); 34 | 35 | while(1) { 36 | int arg_value = va_arg(argp, int); 37 | sum += arg_value; 38 | if (arg_value == 0) 39 | break; 40 | } 41 | va_end(argp); 42 | 43 | return sum; 44 | """.} 45 | 46 | proc DllMain*(hinst: HINSTANCE, reason: DWORD, reserved: LPVOID): BOOL {.stdcall.} = 47 | if reason == DLL_PROCESS_ATTACH: NimMain() 48 | return TRUE 49 | -------------------------------------------------------------------------------- /examples/embedded_ssl_test.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | # This example run "ssl_test.exe" with embedded OpenSSL DLLs. 9 | # Using legacy dlls because there are some unresolved issues to load 10 | # DLLs in last version. 11 | 12 | # Nim SSL library (openssl.nim) use dynlib pragma to load the OpenSSL DLLs. 13 | # All user code is executed after that, so there is no way to change the 14 | # behavior of dynlib by user code. 15 | 16 | # Here provide another way: embedding both dlls and the target exe file. 17 | # Compile this file to embedded_ssl_test.exe and use embedded_ssl_test_run.cmd 18 | # to test it to ensure the DLLs is noted loaded from PATH. 19 | 20 | import memlib 21 | 22 | const 23 | ExeFile = staticReadDll("ssl_test.exe") 24 | 25 | when defined(cpu64): 26 | const 27 | (DLLSSLName, DLLSSLData) = staticReadDllWithName "ssl/ssleay64.dll" 28 | (DLLUtilName, DLLUtilData) = staticReadDllWithName "ssl/libeay64.dll" 29 | else: 30 | const 31 | (DLLSSLName, DLLSSLData) = staticReadDllWithName "ssl/ssleay32.dll" 32 | (DLLUtilName, DLLUtilData) = staticReadDllWithName "ssl/libeay32.dll" 33 | 34 | proc load() = 35 | var utilMod = checkedLoadLib(DLLUtilData) 36 | utilMod.hook(DLLUtilName) 37 | echo "[", DLLUtilName, " hooked]" 38 | 39 | var sslMod = checkedLoadLib(DLLSSLData) 40 | sslMod.hook(DLLSSLName) 41 | echo "[", DLLSSLName, " hooked]" 42 | 43 | load() 44 | loadLib(ExeFile).run() 45 | -------------------------------------------------------------------------------- /memlib.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "1.3.0" 4 | author = "Ward" 5 | description = "Memlib - Load Windows DLL from memory" 6 | license = "MIT" 7 | skipDirs = @["tests", "examples", "docs"] 8 | 9 | # Dependencies 10 | 11 | requires "nim >= 1.2.0", "winim >= 3.6.0, minhook >= 1.0.0" 12 | 13 | task dll, "Build Dlls": 14 | withDir "dlls": 15 | exec "nim c -d:danger --opt:size --nomain --app:lib --cpu:amd64 -o:testdll64.dll testdll.nim" 16 | exec "nim c -d:danger --opt:size --nomain --app:lib --cpu:i386 --passL:-static-libgcc --passL:-Wl,--kill-at -o:testdll32.dll testdll.nim" 17 | exec "nim c -d:danger --opt:size --nomain --app:lib --cpu:amd64 -o:require_sqlite64.dll require_sqlite.nim" 18 | exec "nim c -d:danger --opt:size --nomain --app:lib --cpu:i386 --passL:-static-libgcc --passL:-Wl,--kill-at -o:require_sqlite32.dll require_sqlite.nim" 19 | exec "nim cpp -d:danger --opt:size --nomain --app:lib --cpu:amd64 --exceptions:cpp --passL:-static-libgcc --passL:-static-libstdc++ -o:exception64.dll exception.nim" 20 | exec "nim cpp -d:danger --opt:size --nomain --app:lib --cpu:i386 --exceptions:cpp --passL:-static-libgcc --passL:-static-libstdc++ -o:exception32.dll exception.nim" 21 | 22 | task example, "Build Examples": 23 | withDir "examples": 24 | exec "nim c usage1.nim" 25 | exec "nim c usage2.nim" 26 | exec "nim c embedded_sqlite.nim" 27 | exec "nim c -d:ssl -d:nimDisableCertificateValidation ssl_test.nim" 28 | exec "nim c embedded_ssl_test.nim" 29 | 30 | task test, "Run Tests": 31 | withDir "tests": 32 | exec "nim r test_basic" 33 | exec "nim r test_exception_dll" 34 | exec "nim r test_hook" 35 | exec "nim r test_rtlib" 36 | 37 | task clean, "Delete all EXE and DLL files": 38 | exec "cmd /c IF EXIST examples\\*.exe del examples\\*.exe" 39 | exec "cmd /c IF EXIST dlls\\*.dll del dlls\\*.dll" 40 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Memlib 2 | This module is designed to be a drop-in replacement for `dynlib pragma` and `dynlib module` in Windows. The main part of this module is a pure nim implementation of the famous MemoryModule library. [MemoryModule](https://github.com/fancycode/MemoryModule) is a C library that can load a DLL completely from memory without storing on the disk first. So that the we can embed all DLLs into the main EXE file. 3 | 4 | ## Features 5 | * Nim implementation of MemoryModule with C++ exceptions and SEH support. 6 | * Dynlib module replacement to load DLL from memory. 7 | * Dynlib pragma replacement to load DLL at runtime. 8 | * Compile-time DLL finder for easy embedding. 9 | * Get address of DLL functions by name or by ordinal. 10 | * Hook the system API (LoadLibrary and GetProcAddress) to use a memory module. 11 | 12 | ## Examples 13 | ```nim 14 | import memlib 15 | 16 | # Embed DLL and load it from memory. 17 | block: 18 | const dll = staticReadDll("sqlite3_64.dll") 19 | proc libversion(): cstring {.cdecl, memlib: dll, importc: "sqlite3_libversion".} 20 | echo libversion() 21 | 22 | # Load DLL at runtime. 23 | block: 24 | proc libversion(): cstring {.cdecl, memlib: "sqlite3_64.dll", importc: "sqlite3_libversion".} 25 | echo libversion() 26 | ``` 27 | 28 | Please also check the examples directory. There are some codes to demonstrate how to modify nim/lib/wrappers so that these DLLs (sqlite and ssl, etc.) can be embedded. 29 | 30 | ## Rtlib 31 | I found the ability to load DLL by memlib at runtime is very handy. So I add a submodule called `rtlib` that only do this job. This is just a subset of memlib, but using rtlib instead of memlib will reduce the output .exe size. 32 | 33 | For example, the document of [SHCreateMemStream](https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream) says: "*Prior to Windows Vista, this function was not included in the public Shlwapi.h file, nor was it exported by name from Shlwapi.dll. To use it on earlier systems, you must call it directly from the Shlwapi.dll file as ordinal 12.*" We can use rtlib to call this API in really easy way. 34 | 35 | ```nim 36 | import memlib/rtlib 37 | proc SHCreateMemStream(pInit: pointer, cbInit: cint): ptr IStream 38 | {.rtlib: "shlwapi", stdcall, importc: 12.} 39 | 40 | var str = "abcde" 41 | let pStream = SHCreateMemStream(addr str[0], cint str.len) 42 | assert pStream != nil 43 | ``` 44 | 45 | ## Docs 46 | * https://khchen.github.io/memlib 47 | 48 | ## License 49 | Read license.txt for more details. 50 | 51 | Copyright (c) 2021-2022 Kai-Hung Chen, Ward. All rights reserved. 52 | -------------------------------------------------------------------------------- /examples/usage2.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import memlib, dynlib, winim/lean 9 | 10 | # Advanced Usage 11 | 12 | # Windows DLL can index a function by ordinal. This is aslo supported by memlib. 13 | block: 14 | type 15 | libversionProc = proc(): cstring {.cdecl.} 16 | 17 | when defined(cpu64): 18 | const 19 | SqliteName = "sqlite3_64.dll" 20 | SqliteDll = staticReadDll(SqliteName) 21 | Ordinal = 0x79 22 | else: 23 | const 24 | SqliteName = "sqlite3_32.dll" 25 | SqliteDll = staticReadDll(SqliteName) 26 | Ordinal = 0x78 27 | 28 | let 29 | lib = loadlib(SqliteDll) 30 | libversion1 = cast[libversionProc](lib.symAddr(Ordinal)) 31 | 32 | echo libversion1(), " (loadlib/symAddr in memlib module + Ordinal)" 33 | 34 | proc libversion2(): cstring {.cdecl, memlib: SqliteDll, importc: Ordinal.} 35 | echo libversion2(), " (memlib macro + embeded DLL + Ordinal)" 36 | 37 | proc libversion3(): cstring {.cdecl, memlib: SqliteName, importc: Ordinal.} 38 | echo libversion3(), " (memlib macro + runtime DLL + Ordinal)" 39 | 40 | # proc libversion4(): cstring {.cdecl, dynlib: SqliteName, importc: Ordinal.} 41 | # Error: string literal expected 42 | 43 | # Sometimes we use push and pop pragmas for dynlib. 44 | block: 45 | when defined(cpu64): 46 | const SqliteDll = "sqlite3_64.dll" 47 | else: 48 | const SqliteDll = "sqlite3_32.dll" 49 | 50 | {.push cdecl, dynlib: SqliteDll, importc: "sqlite3_$1".} 51 | proc libversion(): cstring 52 | {.pop.} 53 | 54 | echo libversion(), " (push pragma + dynlib pragma)" 55 | 56 | # For memlib, you can use `withPragma` macro instead. 57 | block: 58 | when defined(cpu64): 59 | const SqliteDll = staticReadDll("sqlite3_64.dll") 60 | else: 61 | const SqliteDll = staticReadDll("sqlite3_32.dll") 62 | 63 | withPragma { cdecl, memlib: SqliteDll, importc: "sqlite3_$1" }: 64 | proc libversion(): cstring 65 | 66 | echo libversion(), " (withPragma macro + memlib macro as pragma)" 67 | 68 | # Pragma pragma can also be used with dynlib. 69 | block: 70 | when defined(cpu64): 71 | const SqliteDll = "sqlite3_64.dll" 72 | else: 73 | const SqliteDll = "sqlite3_32.dll" 74 | 75 | {.pragma: mylib1, cdecl, dynlib: SqliteDll, importc: "sqlite3_$1".} 76 | proc libversion(): cstring {.mylib1.} 77 | 78 | echo libversion(), " (pragma pragma + dynlib pragma)" 79 | 80 | # For memlib, you can use `buildPragma` macro instead. 81 | block: 82 | when defined(cpu64): 83 | const SqliteDll = staticReadDll("sqlite3_64.dll") 84 | else: 85 | const SqliteDll = staticReadDll("sqlite3_32.dll") 86 | 87 | buildPragma { cdecl, memlib: SqliteDll, importc: "sqlite3_$1" }: mylib2 88 | proc libversion(): cstring {.mylib2.} 89 | 90 | echo libversion(), " (buildPragma macro + memlib macro as pragma)" 91 | 92 | # Memlib also privodes the ability to hook the Windows API. This means, you can 93 | # let traditional LoadLibrary/GetProcAddress API to work with an embeded DLL. 94 | # This is the solution if you want embed a chain of DLL (A needs B, B needs C, etc). 95 | block: 96 | when defined(cpu64): 97 | const SqliteDll = staticReadDll("sqlite3_64.dll") 98 | else: 99 | const SqliteDll = staticReadDll("sqlite3_32.dll") 100 | 101 | let lib = loadlib(SqliteDll) 102 | lib.hook("arbitrary_name") 103 | 104 | type 105 | libversionProc = proc(): cstring {.cdecl.} 106 | 107 | let 108 | hModule = LoadLibrary("arbitrary_name") 109 | libversion = cast[libversionProc](GetProcAddress(hModule ,"sqlite3_libversion")) 110 | 111 | echo libversion(), " (LoadLibrary/GetProcAddress + hooked name)" 112 | 113 | # Try to use bulit-in dynlib module, and of course it calls the hooked Windows API 114 | block: 115 | type 116 | libversionProc = proc(): cstring {.cdecl.} 117 | 118 | let 119 | lib = loadLib("arbitrary_name") 120 | libversion = cast[libversionProc](lib.symAddr("sqlite3_libversion")) 121 | 122 | echo libversion(), " (bulit-in dynlib module + hooked name)" 123 | 124 | # Memlib also provides drop-in replacements of following system API: FindResource, 125 | # SizeofResource, LoadResource, LoadString. 126 | block: 127 | when defined(cpu64): 128 | const SqliteDll = staticReadDll("sqlite3_64.dll") 129 | else: 130 | const SqliteDll = staticReadDll("sqlite3_32.dll") 131 | 132 | let 133 | lib = loadlib(SqliteDll) 134 | resc = findResource(lib, MAKEINTRESOURCE(1), RT_VERSION) 135 | adr = cast[LPVOID](loadResource(lib, resc)) 136 | 137 | var 138 | buffer: LPVOID 139 | size: UINT 140 | 141 | if VerQueryValue(adr, r"\StringFileInfo\040904b0\FileVersion", &buffer, &size): 142 | echo $cast[LPCTSTR](buffer), " (find resource in MemoryModule)" 143 | -------------------------------------------------------------------------------- /examples/usage1.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import memlib, dynlib 9 | 10 | # Basic Usage 11 | 12 | # Try to use built-in dynlib moudle at first (aka. loading DLL at runtime). 13 | block: 14 | type 15 | libversionProc = proc(): cstring {.cdecl.} 16 | 17 | when defined(cpu64): 18 | const SqliteDll = "sqlite3_64.dll" 19 | else: 20 | const SqliteDll = "sqlite3_32.dll" 21 | 22 | let 23 | lib = loadLib(SqliteDll) 24 | libversion = cast[libversionProc](lib.symAddr("sqlite3_libversion")) 25 | 26 | echo libversion(), " (loadlib/symAddr in bulit-in dynlib module)" 27 | 28 | # Memlib can do the same thing as dynlib, but embed the DLL into the output EXE. 29 | block: 30 | type 31 | libversionProc = proc(): cstring {.cdecl.} 32 | 33 | when defined(cpu64): 34 | const SqliteDll = staticReadDll("sqlite3_64.dll") 35 | else: 36 | const SqliteDll = staticReadDll("sqlite3_32.dll") 37 | 38 | assert(SqliteDll.type is DllContent) 39 | 40 | let 41 | lib = loadlib(SqliteDll) 42 | libversion = cast[libversionProc](lib.symAddr("sqlite3_libversion")) 43 | 44 | echo libversion(), " (loadlib/symAddr in memlib module)" 45 | 46 | # A checked version procs will raise exceptions on fail. (otherwise, you may 47 | # enconter "Illegal storage access" message.) 48 | block: 49 | type 50 | libversionProc = proc(): cstring {.cdecl.} 51 | 52 | when defined(cpu64): 53 | const SqliteDll = staticReadDll("sqlite3_64.dll") 54 | else: 55 | const SqliteDll = staticReadDll("sqlite3_32.dll") 56 | 57 | try: 58 | let 59 | lib = checkedLoadlib(SqliteDll) 60 | libversion = cast[libversionProc](lib.checkedSymAddr("func_not_exists")) 61 | 62 | echo libversion(), " (checkedLoadlib/checkedSymAddr in memlib module)" 63 | 64 | except LibraryError: 65 | echo "error (checkedLoadlib/checkedSymAddr in memlib module)" 66 | 67 | # Here demonstrate another way to load DLL by built-in `dynlib` pragma. 68 | block: 69 | when defined(cpu64): 70 | const SqliteDll = "sqlite3_64.dll" 71 | else: 72 | const SqliteDll = "sqlite3_32.dll" 73 | 74 | proc libversion(): cstring {.cdecl, dynlib: SqliteDll, importc: "sqlite3_libversion".} 75 | echo libversion(), " (dynlib pragma)" 76 | 77 | # Again, Memlib can do it but embedding the DLL, via `memlib` macro. 78 | block: 79 | when defined(cpu64): 80 | const SqliteDll = staticReadDll("sqlite3_64.dll") 81 | else: 82 | const SqliteDll = staticReadDll("sqlite3_32.dll") 83 | 84 | proc libversion(): cstring {.cdecl, memlib: SqliteDll, importc: "sqlite3_libversion".} 85 | echo libversion(), " (memlib macro as pragma + DllContent)" 86 | 87 | # The memlib macro also accepts a preloaded library handle. 88 | block: 89 | when defined(cpu64): 90 | const SqliteDll = staticReadDll("sqlite3_64.dll") 91 | else: 92 | const SqliteDll = staticReadDll("sqlite3_32.dll") 93 | 94 | let lib = loadlib(SqliteDll) 95 | proc libversion(): cstring {.cdecl, memlib: lib, importc: "sqlite3_libversion".} 96 | echo libversion(), " (memlib macro as pragma + MemoryModule)" 97 | 98 | # Maybe you already have noticed, `staticReadDll` does a lot of magic to find a 99 | # DLL and load it at compile-time. The returned value is in DllContent (distinct 100 | # string) format. Of course you can prepare your own DLL binary data by yourself. 101 | # For example, read from file. 102 | block: 103 | when defined(cpu64): 104 | const SqlitePath = r"c:\nim\bin\sqlite3_64.dll" 105 | else: 106 | const SqlitePath = r"c:\nim\bin\sqlite3_32.dll" 107 | 108 | try: 109 | let 110 | data = readfile(SqlitePath) # must provide the full path 111 | lib = checkedLoadLib(cast[seq[byte]](data)) # or checkedLoadLib(DllContent data) 112 | 113 | proc libversion(): cstring {.cdecl, memlib: lib, importc: "sqlite3_libversion".} 114 | echo libversion(), " (memlib macro as pragma + MemoryModule)" 115 | 116 | except: 117 | echo "error (memlib macro as pragma + MemoryModule)" 118 | 119 | # If you provide a string to `memlib` macro. It works, too. However, the DLL 120 | # will NOT be embeded. This is just a EASIER way to load DLL at runtime (compare 121 | # to dynlib module). 122 | block: 123 | when defined(cpu64): 124 | const SqliteDll = "sqlite3_64.dll" 125 | else: 126 | const SqliteDll = "sqlite3_32.dll" 127 | 128 | # assert(SqliteDll.type is string) 129 | proc libversion(): cstring {.cdecl, memlib: SqliteDll, importc: "sqlite3_libversion".} 130 | echo libversion(), " (memlib macro as pragma + const string)" 131 | 132 | # What's different to `dynlib` pragma? Don't forget it load DLL at runtime. 133 | # So you can use not only const string but also runtime string . 134 | block: 135 | when defined(cpu64): 136 | let SqliteDll = "sqlite3_64.dll" 137 | else: 138 | let SqliteDll = "sqlite3_32.dll" 139 | 140 | proc libversion(): cstring {.cdecl, memlib: SqliteDll, importc: "sqlite3_libversion".} 141 | echo libversion(), " (memlib macro as pragma + runtime string)" 142 | 143 | # If the DLL is not exists, you can catch an exception instead of quitting (needs 144 | # checkedMemlib macro). 145 | block: 146 | try: 147 | proc libversion(): cstring {.cdecl, checkedMemlib: "DllNotExists", importc: "sqlite3_libversion".} 148 | echo libversion(), " (checkedMemlib macro as pragma)" 149 | 150 | except LibraryError: 151 | echo "error (checkedMemlib macro as pragma)" 152 | -------------------------------------------------------------------------------- /memlib/private/sharedseq.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import locks, algorithm 9 | export algorithm 10 | 11 | when not declared(reallocShared0): 12 | import locks 13 | var heapLock: Lock 14 | heapLock.initLock() 15 | 16 | proc reallocShared0(p: pointer, oldSize, newSize: Natural): pointer = 17 | acquire(heapLock) 18 | result = reallocShared(p, newSize) 19 | if newSize > oldSize: 20 | zeroMem(cast[pointer](cast[uint](result) + uint(oldSize)), newSize - oldSize) 21 | release(heapLock) 22 | 23 | type 24 | SharedSeqObj[T] = object 25 | buffer: ptr UncheckedArray[T] 26 | lock: Lock 27 | size: int 28 | cap: int 29 | 30 | SharedSeq*[T] = ptr SharedSeqObj[T] 31 | 32 | proc resize(old: int): int {.inline.} = 33 | if old <= 0: result = 4 34 | elif old < 65536: result = old * 2 35 | else: result = old * 3 div 2 # for large arrays * 3/2 is better 36 | 37 | proc ok*[T](s: SharedSeq[T]): bool {.inline.} = 38 | result = s != nil and s.buffer != nil 39 | 40 | template withLock[T](s: SharedSeq[T], body: untyped) = 41 | assert s.ok 42 | acquire(s.lock) 43 | try: 44 | body 45 | finally: 46 | release(s.lock) 47 | 48 | proc newSharedSeq*[T](size: Natural = 0): SharedSeq[T] = 49 | result = createShared(SharedSeqObj[T]) 50 | 51 | let cap = resize(size) 52 | result.buffer = cast[ptr UncheckedArray[T]](createShared(T, cap)) 53 | assert result.ok 54 | 55 | result.lock.initLock() 56 | result.size = size 57 | result.cap = cap 58 | 59 | proc newSharedSeqOfCap*[T](cap: Natural): SharedSeq[T] = 60 | result.buffer = cast[ptr UncheckedArray[T]](createShared(T, cap)) 61 | assert result.ok 62 | 63 | result.lock.initLock() 64 | result.size = 0 65 | result.cap = cap 66 | 67 | proc newSharedSeq*[T](data: openarray[T]): SharedSeq[T] = 68 | result = newSharedSeq[T](data.len) 69 | for i in 0 ..< data.len: 70 | result.buffer[i] = data[i] 71 | 72 | proc dealloc*[T](s: SharedSeq[T]) = 73 | if s != nil: 74 | if s.buffer != nil: 75 | deallocShared(s.buffer) 76 | deallocShared(s) 77 | 78 | proc ensure[T](s: SharedSeq[T], cap: int) = 79 | if s.cap < cap: 80 | let newCap = max(resize(s.cap), cap) 81 | s.buffer = cast[ptr UncheckedArray[T]](reallocShared0(s.buffer, sizeof(T) * s.cap, sizeof(T) * newCap)) 82 | s.cap = newCap 83 | assert s.ok 84 | 85 | proc setLen*[T](s: SharedSeq[T], size: int) = 86 | withLock(s): 87 | s.ensure(size) 88 | s.size = size 89 | 90 | proc add*[T](s: SharedSeq[T], data: T) = 91 | withLock(s): 92 | s.ensure(s.size + 1) 93 | s.buffer[s.size] = data 94 | s.size.inc 95 | 96 | proc add*[T](s: SharedSeq[T], data: openarray[T]) = 97 | withLock(s): 98 | s.ensure(s.size + data.len) 99 | for i in 0 ..< data.len: 100 | s.buffer[s.size] = data[i] 101 | s.size.inc 102 | 103 | proc add*[T](s: SharedSeq[T], t: SharedSeq[T]) = 104 | withLock(s): withLock(t): 105 | s.ensure(s.size + t.size) 106 | for i in 0 ..< t.size: 107 | s.buffer[s.size] = t.buffer[i] 108 | s.size.inc 109 | 110 | proc pop*[T](s: SharedSeq[T]): T = 111 | withLock(s): 112 | assert s.size >= 1 113 | s.size.dec 114 | result = s.buffer[s.size] 115 | 116 | proc del*[T](s: SharedSeq[T], index: Natural) = 117 | withLock(s): 118 | assert s.size >= 1 and index < s.size 119 | s.size.dec 120 | s.buffer[index] = s.buffer[s.size] 121 | 122 | proc delete*[T](s: SharedSeq[T], index: Natural) = 123 | withLock(s): 124 | assert s.size >= 1 and index < s.size 125 | s.size.dec 126 | for i in index.int .. (s.size - 1): 127 | s.buffer[i] = s.buffer[i + 1] 128 | 129 | proc len*[T](s: SharedSeq[T]): int {.inline.} = 130 | withLock(s): 131 | result = s.size 132 | 133 | proc high*[T](s: SharedSeq[T]): int {.inline.} = 134 | withLock(s): 135 | result = s.size - 1 136 | 137 | proc `[]`*[T](s: SharedSeq[T], i: Natural): T {.inline.} = 138 | withLock(s): 139 | assert i < s.size 140 | result = s.buffer[i] 141 | 142 | proc `[]=`*[T](s: SharedSeq[T], i: Natural, x: T) {.inline.} = 143 | withLock(s): 144 | assert i < s.size 145 | s.buffer[i] = x 146 | 147 | proc `@`*[T](s: SharedSeq[T]): seq[T] = 148 | withLock(s): 149 | result.setLen(s.size) 150 | for i in 0 ..< s.size: 151 | result[i] = s.buffer[i] 152 | 153 | iterator items*[T](s: SharedSeq[T]): T = 154 | withLock(s): 155 | for i in 0 ..< s.size: 156 | yield s[i] 157 | 158 | iterator mitems*[T](s: SharedSeq[T]): var T = 159 | withLock(s): 160 | for i in 0 ..< s.size: 161 | yield cast[var T](addr s.buffer[i]) 162 | 163 | iterator pairs*[T](s: SharedSeq[T]): (int, T) = 164 | withLock(s): 165 | for i in 0 ..< s.size: 166 | yield (i, s[i]) 167 | 168 | iterator mpairs*[T](s: SharedSeq[T]): (int, var T) = 169 | withLock(s): 170 | for i in 0 ..< s.size: 171 | yield (i, cast[var T](addr s.buffer[i])) 172 | 173 | proc sort*[T](s: SharedSeq[T], order = SortOrder.Ascending) = 174 | withLock(s): 175 | sort(toOpenArray(s.buffer, 0, s.size - 1), order) 176 | 177 | proc sort*[T](s: SharedSeq[T], cmp: proc (x, y: T): int, order = SortOrder.Ascending) = 178 | withLock(s): 179 | sort(toOpenArray(s.buffer, 0, s.size - 1), cmp, order) 180 | 181 | proc binarySearch*[T, K](s: SharedSeq[T], key: K, cmp: proc (x: T; y: K): int): int = 182 | withLock(s): 183 | result = binarySearch(toOpenArray(s.buffer, 0, s.size - 1), key, cmp) 184 | 185 | proc binarySearch*[T](s: SharedSeq[T], key: T): int = 186 | withLock(s): 187 | result = binarySearch(toOpenArray(s.buffer, 0, s.size - 1), key) 188 | 189 | proc `$`*[T](s: SharedSeq[T]): string = 190 | withLock(s): 191 | result.add "@@[" 192 | for i in 0 ..< s.size: 193 | result.add $s.buffer[i] 194 | if i < s.size - 1: result.add ", " 195 | 196 | result.add "]" 197 | 198 | when isMainModule: 199 | import random 200 | 201 | var s = newSharedSeq[int]() 202 | for i in 1..100: 203 | s.add rand(1000) 204 | 205 | s.sort 206 | echo s 207 | echo s.binarySearch(996) 208 | -------------------------------------------------------------------------------- /memlib/rtlib.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import macros, locks, strutils 9 | import winim/lean 10 | import private/sharedseq 11 | 12 | var 13 | rtLibs: SharedSeq[HMODULE] 14 | gLock: Lock 15 | 16 | proc globalInit() = 17 | gLock.initLock() 18 | rtLibs = newSharedSeq[HMODULE]() 19 | 20 | once: 21 | globalInit() 22 | 23 | proc `[]`[T](x: T, U: typedesc): U = 24 | ## syntax sugar for cast 25 | cast[U](x) 26 | 27 | proc `{}`[T](x: T, U: typedesc): U = 28 | ## syntax sugar for zero extends cast 29 | when sizeof(x) == 1: x[uint8][U] 30 | elif sizeof(x) == 2: x[uint16][U] 31 | elif sizeof(x) == 4: x[uint32][U] 32 | elif sizeof(x) == 8: x[uint64][U] 33 | else: {.fatal.} 34 | 35 | proc symErrorMessage(sym: LPCSTR): string {.raises: [].} = 36 | let msg = if HIWORD(sym{uint}) == 0: "ordinal " & $(sym{uint}) else: "symbol " & $sym 37 | result = "Could not find " & msg 38 | 39 | template rtlibLookup(callPtr: ptr pointer, name: string, sym: LPCSTR, errorLib, errorSym: untyped) = 40 | var handle = LoadLibrary(name) 41 | if handle == 0: 42 | errorLib 43 | 44 | else: 45 | withLock(gLock): 46 | var found = false 47 | for i in 0 ..< rtLibs.len: 48 | if rtLibs[i] == handle: 49 | found = true 50 | break 51 | 52 | if found: 53 | FreeLibrary(handle) # decrease reference count only 54 | 55 | else: 56 | rtLibs.add handle 57 | 58 | callPtr[] = GetProcAddress(handle, sym) 59 | if callPtr[] == nil: 60 | errorSym 61 | 62 | proc checkedRtlibLookup*(callPtr: ptr pointer, name: string, sym: LPCSTR) {.raises: [LibraryError].} = 63 | ## A helper used by `rtlib` macro. 64 | rtlibLookup(callPtr, name, sym) do: 65 | raise newException(LibraryError, "Could not load " & name) 66 | do: 67 | raise newException(LibraryError, symErrorMessage(sym)) 68 | 69 | proc rtlibLookup*(callPtr: ptr pointer, name: string, sym: LPCSTR) {.raises: [].} = 70 | ## A helper used by `rtlib` macro. 71 | rtlibLookup(callPtr, name, sym) do: 72 | return 73 | do: 74 | return 75 | 76 | proc rewritePragma(def: NimNode, hasRaises: bool): (NimNode, NimNode) = 77 | var 78 | sym = def.name.toStrLit 79 | newPragma = newTree(nnkPragma) 80 | typPragma = newTree(nnkPragma) 81 | procty = newTree(nnkProcTy, def.params, typPragma) 82 | 83 | # Procs imported from Dll implies gcsafe and raises: [] 84 | typPragma.add ident("gcsafe") 85 | newPragma.add ident("gcsafe") 86 | 87 | typPragma.add newColonExpr(ident("raises"), newNimNode(nnkBracket)) 88 | typPragma.add newColonExpr(ident("tags"), newNimNode(nnkBracket)) 89 | if not hasRaises: 90 | newPragma.add newColonExpr(ident("raises"), newNimNode(nnkBracket)) 91 | newPragma.add newColonExpr(ident("tags"), newNimNode(nnkBracket)) 92 | 93 | for node in def.pragma: 94 | # ignore single importc 95 | if node.kind == nnkIdent and $node == "importc": 96 | continue 97 | 98 | # ignore importc: symbol, but copy the symbol 99 | if node.kind == nnkExprColonExpr and $node[0] == "importc": 100 | sym = node[1] 101 | if sym.kind == nnkStrLit: 102 | sym.strVal = sym.strVal.multiReplace(("$$", "$"), ("$1", $def[0])) 103 | continue 104 | 105 | # only procDef accept discardable 106 | if node.kind == nnkIdent and $node == "discardable": 107 | newPragma.add node 108 | continue 109 | 110 | newPragma.add node 111 | typPragma.add node 112 | 113 | def.pragma = newPragma 114 | return (sym, procty) 115 | 116 | proc addParams(def: NimNode) = 117 | for node in def.params: 118 | if node.kind == nnkIdentDefs: 119 | for i in 0 ..< node.len - 2: 120 | assert def.body[^1].kind == nnkCall 121 | def.body[^1].add node[i] 122 | 123 | proc isVarargs(def: NimNode): bool = 124 | for i in def.pragma: 125 | if i.eqIdent("varargs"): return true 126 | return false 127 | 128 | proc compose(dll, def: NimNode, hasRaises: bool): NimNode = 129 | var 130 | (sym, procty) = def.rewritePragma(hasRaises) 131 | rtLookup = ident(if `hasRaises`: "checkedRtlibLookup" else: "rtlibLookup") 132 | isVarargs = def.isVarargs() 133 | name = if isVarargs: ident(def.name.strVal) else: ident("call") 134 | 135 | var code = quote do: 136 | var 137 | `name` {.global.}: `procty` 138 | sym: LPCSTR 139 | 140 | when `sym` is string: 141 | sym = LPCSTR `sym` 142 | elif `sym` is SomeInteger: 143 | sym = `sym`.int[LPCSTR] 144 | else: 145 | {.fatal: "importc only allows string or integer".} 146 | 147 | if `name`.isNil: 148 | {.gcsafe.}: 149 | when `dll` is string: # Load dll at runtime 150 | `rtlookup`(`name`.addr[ptr pointer], `dll`, sym) 151 | 152 | else: 153 | {.fatal: "rtlib only accepts string".} 154 | 155 | `name`() 156 | 157 | if isVarargs: 158 | code.del(code.len - 1) # remove last call 159 | result = code 160 | 161 | else: 162 | def.body = code 163 | def.addParams() # add params to last call 164 | result = def 165 | 166 | macro checkedRtlib*(dll, def: untyped): untyped = 167 | ## `dynlib` pragma replacement to load DLL at runtime. 168 | ## Raise `LibraryError` if error occurred. 169 | ## See `rtlib` for details. 170 | def.expectKind(nnkProcDef) 171 | result = compose(dll, def, hasRaises = true) 172 | 173 | macro rtlib*(dll, def: untyped): untyped = 174 | ## `dynlib` pragma replacement to load DLL at runtime. 175 | ## Accepts a `string` only. 176 | ## ============ ============================================================ 177 | ## Parameter Meaning 178 | ## ============ ============================================================ 179 | ## string Loads the DLL at runtime by system API. 180 | ## ============ ============================================================ 181 | 182 | runnableExamples: 183 | proc libversion(): cstring {.cdecl, rtlib: "sqlite3_64.dll", importc: "sqlite3_libversion".} 184 | echo libversion() 185 | 186 | def.expectKind(nnkProcDef) 187 | result = compose(dll, def, hasRaises = false) 188 | -------------------------------------------------------------------------------- /tests/test_basic.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | import unittest, memlib 9 | import winim/lean 10 | 11 | when defined(cpu64): 12 | const DllPath = "../dlls/testdll64.dll" 13 | else: 14 | const DllPath = "../dlls/testdll32.dll" 15 | 16 | const DllData = staticReadDll DllPath 17 | 18 | suite "Test Suites for Memlib": 19 | 20 | test "Dynlib": 21 | proc test1(a, b, c: cstring): cstring {.dynlib: DllPath, cdecl, importc: "test_cdecl".} 22 | proc test2(a, b, c: cstring): cstring {.dynlib: DllPath, fastcall, importc: "test_fastcall".} 23 | proc test3(a, b, c: cstring): cstring {.dynlib: DllPath, stdcall, importc: "test_stdcall".} 24 | proc test_varargs(i: cint): cint {.dynlib: DllPath, cdecl, varargs, importc.} 25 | 26 | check: 27 | test1("a", "b", "c") == "cdecl a b c" 28 | test2("a", "b", "c") == "fastcall a b c" 29 | test3("a", "b", "c") == "stdcall a b c" 30 | test_varargs(1, 2, 3, 4, 0) == 10 31 | 32 | test "Memlib with string (Loading a DLL at runtime)": 33 | proc test1(a, b, c: cstring): cstring {.memlib: DllPath, cdecl, importc: "test_cdecl".} 34 | proc test2(a, b, c: cstring): cstring {.memlib: DllPath, fastcall, importc: "test_fastcall".} 35 | proc test3(a, b, c: cstring): cstring {.memlib: DllPath, stdcall, importc: "test_stdcall".} 36 | proc test4(a, b, c: cstring): cstring {.memlib: DllPath, cdecl, importc: 3.} 37 | proc test5(a, b, c: cstring): cstring {.memlib: DllPath, fastcall, importc: 4.} 38 | proc test6(a, b, c: cstring): cstring {.memlib: DllPath, stdcall, importc: 5.} 39 | proc test_varargs(i: cint): cint {.memlib: DllPath, cdecl, varargs, importc.} 40 | 41 | check: 42 | test1("a", "b", "c") == "cdecl a b c" 43 | test2("a", "b", "c") == "fastcall a b c" 44 | test3("a", "b", "c") == "stdcall a b c" 45 | test4("a", "b", "c") == "cdecl a b c" 46 | test5("a", "b", "c") == "fastcall a b c" 47 | test6("a", "b", "c") == "stdcall a b c" 48 | test_varargs(1, 2, 3, 4, 0) == 10 49 | 50 | test "Memlib with DllContent (Loading a DLL from memory)": 51 | proc test1(a, b, c: cstring): cstring {.memlib: DllData, cdecl, importc: "test_cdecl".} 52 | proc test2(a, b, c: cstring): cstring {.memlib: DllData, fastcall, importc: "test_fastcall".} 53 | proc test3(a, b, c: cstring): cstring {.memlib: DllData, stdcall, importc: "test_stdcall".} 54 | proc test4(a, b, c: cstring): cstring {.memlib: DllData, cdecl, importc: 3.} 55 | proc test5(a, b, c: cstring): cstring {.memlib: DllData, fastcall, importc: 4.} 56 | proc test6(a, b, c: cstring): cstring {.memlib: DllData, stdcall, importc: 5.} 57 | proc test_varargs(i: cint): cint {.memlib: DllData, cdecl, varargs, importc.} 58 | 59 | check: 60 | test1("a", "b", "c") == "cdecl a b c" 61 | test2("a", "b", "c") == "fastcall a b c" 62 | test3("a", "b", "c") == "stdcall a b c" 63 | test4("a", "b", "c") == "cdecl a b c" 64 | test5("a", "b", "c") == "fastcall a b c" 65 | test6("a", "b", "c") == "stdcall a b c" 66 | test_varargs(1, 2, 3, 4, 0) == 10 67 | 68 | test "Memlib with preloaded MemoryModule": 69 | var lib = checkedLoadLib(DllData) 70 | proc test1(a, b, c: cstring): cstring {.memlib: lib, cdecl, importc: "test_cdecl".} 71 | proc test2(a, b, c: cstring): cstring {.memlib: lib, fastcall, importc: "test_fastcall".} 72 | proc test3(a, b, c: cstring): cstring {.memlib: lib, stdcall, importc: "test_stdcall".} 73 | proc test4(a, b, c: cstring): cstring {.memlib: lib, cdecl, importc: 3.} 74 | proc test5(a, b, c: cstring): cstring {.memlib: lib, fastcall, importc: 4.} 75 | proc test6(a, b, c: cstring): cstring {.memlib: lib, stdcall, importc: 5.} 76 | proc test_varargs(i: cint): cint {.memlib: lib, cdecl, varargs, importc.} 77 | 78 | check: 79 | test1("a", "b", "c") == "cdecl a b c" 80 | test2("a", "b", "c") == "fastcall a b c" 81 | test3("a", "b", "c") == "stdcall a b c" 82 | test4("a", "b", "c") == "cdecl a b c" 83 | test5("a", "b", "c") == "fastcall a b c" 84 | test6("a", "b", "c") == "stdcall a b c" 85 | test_varargs(1, 2, 3, 4, 0) == 10 86 | 87 | test "Hook the Windows API to use preloaded MemoryModule": 88 | const Name = "this_dll_not_exist" 89 | var lib = checkedLoadLib(DllData) 90 | lib.hook(Name) 91 | 92 | check(LoadLibrary(Name) == cast[HMODULE](lib)) 93 | check(GetProcAddress(LoadLibrary(Name), "test_cdecl") == lib.symAddr("test_cdecl")) 94 | check(GetProcAddress(LoadLibrary(Name), "test_fastcall") == lib.symAddr("test_fastcall")) 95 | check(GetProcAddress(LoadLibrary(Name), "test_stdcall") == lib.symAddr("test_stdcall")) 96 | check(GetProcAddress(LoadLibrary(Name), cast[LPCSTR](3)) == lib.symAddr(3)) 97 | check(GetProcAddress(LoadLibrary(Name), cast[LPCSTR](4)) == lib.symAddr(4)) 98 | check(GetProcAddress(LoadLibrary(Name), cast[LPCSTR](5)) == lib.symAddr(5)) 99 | check(GetProcAddress(LoadLibrary(Name), "test_varargs") == lib.symAddr("test_varargs")) 100 | 101 | proc test1(a, b, c: cstring): cstring {.memlib: Name, cdecl, importc: "test_cdecl".} 102 | proc test2(a, b, c: cstring): cstring {.memlib: Name, fastcall, importc: "test_fastcall".} 103 | proc test3(a, b, c: cstring): cstring {.memlib: Name, stdcall, importc: "test_stdcall".} 104 | proc test4(a, b, c: cstring): cstring {.memlib: Name, cdecl, importc: 3.} 105 | proc test5(a, b, c: cstring): cstring {.memlib: Name, fastcall, importc: 4.} 106 | proc test6(a, b, c: cstring): cstring {.memlib: Name, stdcall, importc: 5.} 107 | proc test_varargs(i: cint): cint {.memlib: Name, cdecl, varargs, importc.} 108 | 109 | check: 110 | test1("a", "b", "c") == "cdecl a b c" 111 | test2("a", "b", "c") == "fastcall a b c" 112 | test3("a", "b", "c") == "stdcall a b c" 113 | test4("a", "b", "c") == "cdecl a b c" 114 | test5("a", "b", "c") == "fastcall a b c" 115 | test6("a", "b", "c") == "stdcall a b c" 116 | test_varargs(1, 2, 3, 4, 0) == 10 117 | 118 | test "Read and Load a DLL at runtime": 119 | var lib = checkedLoadLib(DllContent readfile(DllPath)) 120 | proc test1(a, b, c: cstring): cstring {.memlib: lib, cdecl, importc: "test_cdecl".} 121 | proc test2(a, b, c: cstring): cstring {.memlib: lib, fastcall, importc: "test_fastcall".} 122 | proc test3(a, b, c: cstring): cstring {.memlib: lib, stdcall, importc: "test_stdcall".} 123 | proc test4(a, b, c: cstring): cstring {.memlib: lib, cdecl, importc: 3.} 124 | proc test5(a, b, c: cstring): cstring {.memlib: lib, fastcall, importc: 4.} 125 | proc test6(a, b, c: cstring): cstring {.memlib: lib, stdcall, importc: 5.} 126 | proc test_varargs(i: cint): cint {.memlib: lib, cdecl, varargs, importc.} 127 | 128 | check: 129 | test1("a", "b", "c") == "cdecl a b c" 130 | test2("a", "b", "c") == "fastcall a b c" 131 | test3("a", "b", "c") == "stdcall a b c" 132 | test4("a", "b", "c") == "cdecl a b c" 133 | test5("a", "b", "c") == "fastcall a b c" 134 | test6("a", "b", "c") == "stdcall a b c" 135 | test_varargs(1, 2, 3, 4, 0) == 10 136 | 137 | test "Memlib error handling": 138 | const NotDll = staticReadDll(currentSourcePath()) 139 | var 140 | invalidLib = loadLib(NotDll) 141 | vaildLib = loadLib(DllData) 142 | 143 | check: 144 | vaildLib != nil 145 | vaildLib.symAddr("test_cdecl") != nil 146 | 147 | invalidLib == nil 148 | invalidLib.symAddr("test_cdecl") == nil 149 | vaildLib.symAddr("test_cdecl_not_exist") == nil 150 | 151 | expect LibraryError: 152 | discard checkedLoadLib(NotDll) 153 | 154 | expect LibraryError: 155 | discard invalidLib.checkedSymAddr("test_cdecl") 156 | 157 | expect LibraryError: 158 | discard vaildLib.checkedSymAddr("test_cdecl_not_exist") 159 | 160 | expect LibraryError: 161 | proc test1(a, b, c: cstring): cstring {.checkedMemlib: invalidLib, cdecl, importc: "test_cdecl".} 162 | discard test1("a", "b", "c") 163 | 164 | expect LibraryError: 165 | proc test1(a, b, c: cstring): cstring {.checkedMemlib: vaildLib, cdecl, importc: "test_cdecl_not_exist".} 166 | discard test1("a", "b", "c") 167 | 168 | expect LibraryError: 169 | proc test1(a, b, c: cstring): cstring {.checkedMemlib: vaildLib, cdecl, importc: 999.} 170 | discard test1("a", "b", "c") 171 | 172 | test "WithPragma macro (Push pragma replacement)": 173 | withPragma { dynlib: DllPath }: 174 | block: 175 | proc test1(a, b, c: cstring): cstring {.cdecl, importc: "test_cdecl".} 176 | proc test2(a, b, c: cstring): cstring {.fastcall, importc: "test_fastcall".} 177 | proc test3(a, b, c: cstring): cstring {.stdcall, importc: "test_stdcall".} 178 | proc test_varargs(i: cint): cint {.cdecl, varargs, importc.} 179 | 180 | check: 181 | test1("a", "b", "c") == "cdecl a b c" 182 | test2("a", "b", "c") == "fastcall a b c" 183 | test3("a", "b", "c") == "stdcall a b c" 184 | test_varargs(1, 2, 3, 4, 0) == 10 185 | 186 | withPragma { memlib: DllPath }: 187 | block: 188 | proc test1(a, b, c: cstring): cstring {.cdecl, importc: "test_cdecl".} 189 | proc test2(a, b, c: cstring): cstring {.fastcall, importc: "test_fastcall".} 190 | proc test3(a, b, c: cstring): cstring {.stdcall, importc: "test_stdcall".} 191 | proc test_varargs(i: cint): cint {.cdecl, varargs, importc.} 192 | 193 | check: 194 | test1("a", "b", "c") == "cdecl a b c" 195 | test2("a", "b", "c") == "fastcall a b c" 196 | test3("a", "b", "c") == "stdcall a b c" 197 | test_varargs(1, 2, 3, 4, 0) == 10 198 | 199 | withPragma { memlib: DllData }: 200 | block: 201 | proc test1(a, b, c: cstring): cstring {.cdecl, importc: "test_cdecl".} 202 | proc test2(a, b, c: cstring): cstring {.fastcall, importc: "test_fastcall".} 203 | proc test3(a, b, c: cstring): cstring {.stdcall, importc: "test_stdcall".} 204 | proc test_varargs(i: cint): cint {.cdecl, varargs, importc.} 205 | 206 | check: 207 | test1("a", "b", "c") == "cdecl a b c" 208 | test2("a", "b", "c") == "fastcall a b c" 209 | test3("a", "b", "c") == "stdcall a b c" 210 | test_varargs(1, 2, 3, 4, 0) == 10 211 | 212 | test "BuildPragma macro (Pragma pragma replacement)": 213 | block: 214 | buildPragma { dynlib: DllPath }: mylib 215 | proc test1(a, b, c: cstring): cstring {.mylib, cdecl, importc: "test_cdecl".} 216 | proc test2(a, b, c: cstring): cstring {.mylib, fastcall, importc: "test_fastcall".} 217 | proc test3(a, b, c: cstring): cstring {.mylib, stdcall, importc: "test_stdcall".} 218 | proc test_varargs(i: cint): cint {.mylib, cdecl, varargs, importc.} 219 | 220 | check: 221 | test1("a", "b", "c") == "cdecl a b c" 222 | test2("a", "b", "c") == "fastcall a b c" 223 | test3("a", "b", "c") == "stdcall a b c" 224 | test_varargs(1, 2, 3, 4, 0) == 10 225 | 226 | block: 227 | buildPragma { memlib: DllPath }: mylib 228 | proc test1(a, b, c: cstring): cstring {.mylib, cdecl, importc: "test_cdecl".} 229 | proc test2(a, b, c: cstring): cstring {.mylib, fastcall, importc: "test_fastcall".} 230 | proc test3(a, b, c: cstring): cstring {.mylib, stdcall, importc: "test_stdcall".} 231 | proc test_varargs(i: cint): cint {.mylib, cdecl, varargs, importc.} 232 | 233 | check: 234 | test1("a", "b", "c") == "cdecl a b c" 235 | test2("a", "b", "c") == "fastcall a b c" 236 | test3("a", "b", "c") == "stdcall a b c" 237 | test_varargs(1, 2, 3, 4, 0) == 10 238 | 239 | block: 240 | buildPragma { memlib: DllData }: mylib 241 | proc test1(a, b, c: cstring): cstring {.mylib, cdecl, importc: "test_cdecl".} 242 | proc test2(a, b, c: cstring): cstring {.mylib, fastcall, importc: "test_fastcall".} 243 | proc test3(a, b, c: cstring): cstring {.mylib, stdcall, importc: "test_stdcall".} 244 | proc test_varargs(i: cint): cint {.mylib, cdecl, varargs, importc.} 245 | 246 | check: 247 | test1("a", "b", "c") == "cdecl a b c" 248 | test2("a", "b", "c") == "fastcall a b c" 249 | test3("a", "b", "c") == "stdcall a b c" 250 | test_varargs(1, 2, 3, 4, 0) == 10 251 | -------------------------------------------------------------------------------- /examples/sqlite/sqlite3.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2012 Andreas Rumpf 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | when defined(nimHasStyleChecks): 11 | {.push styleChecks: off.} 12 | 13 | #[ 14 | when defined(windows): 15 | when defined(nimOldDlls): 16 | const Lib = "sqlite3.dll" 17 | elif defined(cpu64): 18 | const Lib = "sqlite3_64.dll" 19 | else: 20 | const Lib = "sqlite3_32.dll" 21 | elif defined(macosx): 22 | const 23 | Lib = "libsqlite3(|.0).dylib" 24 | else: 25 | const 26 | Lib = "libsqlite3.so(|.0)" 27 | 28 | when defined(staticSqlite): 29 | {.pragma: mylib.} 30 | {.compile("sqlite3.c", "-O3").} 31 | else: 32 | {.pragma: mylib, dynlib: Lib.} 33 | ]# 34 | 35 | #==================================================================== 36 | # Modify to use memlib 37 | #==================================================================== 38 | 39 | import memlib 40 | 41 | when defined(cpu64): 42 | const Lib = staticReadDll "sqlite3_64" 43 | else: 44 | const Lib = staticReadDll "sqlite3_32" 45 | 46 | let lib = checkedLoadLib(Lib) 47 | buildPragma { memlib: lib }: mylib 48 | 49 | #==================================================================== 50 | 51 | const 52 | SQLITE_INTEGER* = 1 53 | SQLITE_FLOAT* = 2 54 | SQLITE_BLOB* = 4 55 | SQLITE_NULL* = 5 56 | SQLITE_TEXT* = 3 57 | SQLITE_UTF8* = 1 58 | SQLITE_UTF16LE* = 2 59 | SQLITE_UTF16BE* = 3 # Use native byte order 60 | SQLITE_UTF16* = 4 # sqlite3_create_function only 61 | SQLITE_ANY* = 5 #sqlite_exec return values 62 | SQLITE_OK* = 0 63 | SQLITE_ERROR* = 1 # SQL error or missing database 64 | SQLITE_INTERNAL* = 2 # An internal logic error in SQLite 65 | SQLITE_PERM* = 3 # Access permission denied 66 | SQLITE_ABORT* = 4 # Callback routine requested an abort 67 | SQLITE_BUSY* = 5 # The database file is locked 68 | SQLITE_LOCKED* = 6 # A table in the database is locked 69 | SQLITE_NOMEM* = 7 # A malloc() failed 70 | SQLITE_READONLY* = 8 # Attempt to write a readonly database 71 | SQLITE_INTERRUPT* = 9 # Operation terminated by sqlite3_interrupt() 72 | SQLITE_IOERR* = 10 # Some kind of disk I/O error occurred 73 | SQLITE_CORRUPT* = 11 # The database disk image is malformed 74 | SQLITE_NOTFOUND* = 12 # (Internal Only) Table or record not found 75 | SQLITE_FULL* = 13 # Insertion failed because database is full 76 | SQLITE_CANTOPEN* = 14 # Unable to open the database file 77 | SQLITE_PROTOCOL* = 15 # Database lock protocol error 78 | SQLITE_EMPTY* = 16 # Database is empty 79 | SQLITE_SCHEMA* = 17 # The database schema changed 80 | SQLITE_TOOBIG* = 18 # Too much data for one row of a table 81 | SQLITE_CONSTRAINT* = 19 # Abort due to constraint violation 82 | SQLITE_MISMATCH* = 20 # Data type mismatch 83 | SQLITE_MISUSE* = 21 # Library used incorrectly 84 | SQLITE_NOLFS* = 22 # Uses OS features not supported on host 85 | SQLITE_AUTH* = 23 # Authorization denied 86 | SQLITE_FORMAT* = 24 # Auxiliary database format error 87 | SQLITE_RANGE* = 25 # 2nd parameter to sqlite3_bind out of range 88 | SQLITE_NOTADB* = 26 # File opened that is not a database file 89 | SQLITE_ROW* = 100 # sqlite3_step() has another row ready 90 | SQLITE_DONE* = 101 # sqlite3_step() has finished executing 91 | SQLITE_COPY* = 0 92 | SQLITE_CREATE_INDEX* = 1 93 | SQLITE_CREATE_TABLE* = 2 94 | SQLITE_CREATE_TEMP_INDEX* = 3 95 | SQLITE_CREATE_TEMP_TABLE* = 4 96 | SQLITE_CREATE_TEMP_TRIGGER* = 5 97 | SQLITE_CREATE_TEMP_VIEW* = 6 98 | SQLITE_CREATE_TRIGGER* = 7 99 | SQLITE_CREATE_VIEW* = 8 100 | SQLITE_DELETE* = 9 101 | SQLITE_DROP_INDEX* = 10 102 | SQLITE_DROP_TABLE* = 11 103 | SQLITE_DROP_TEMP_INDEX* = 12 104 | SQLITE_DROP_TEMP_TABLE* = 13 105 | SQLITE_DROP_TEMP_TRIGGER* = 14 106 | SQLITE_DROP_TEMP_VIEW* = 15 107 | SQLITE_DROP_TRIGGER* = 16 108 | SQLITE_DROP_VIEW* = 17 109 | SQLITE_INSERT* = 18 110 | SQLITE_PRAGMA* = 19 111 | SQLITE_READ* = 20 112 | SQLITE_SELECT* = 21 113 | SQLITE_TRANSACTION* = 22 114 | SQLITE_UPDATE* = 23 115 | SQLITE_ATTACH* = 24 116 | SQLITE_DETACH* = 25 117 | SQLITE_ALTER_TABLE* = 26 118 | SQLITE_REINDEX* = 27 119 | SQLITE_DENY* = 1 120 | SQLITE_IGNORE* = 2 # Original from sqlite3.h: 121 | #define SQLITE_STATIC ((void(*)(void *))0) 122 | #define SQLITE_TRANSIENT ((void(*)(void *))-1) 123 | SQLITE_DETERMINISTIC* = 0x800 124 | 125 | type 126 | Sqlite3 {.pure, final.} = object 127 | PSqlite3* = ptr Sqlite3 128 | PPSqlite3* = ptr PSqlite3 129 | Sqlite3_Backup {.pure, final.} = object 130 | PSqlite3_Backup* = ptr Sqlite3_Backup 131 | PPSqlite3_Backup* = ptr PSqlite3_Backup 132 | Context{.pure, final.} = object 133 | Pcontext* = ptr Context 134 | TStmt{.pure, final.} = object 135 | PStmt* = ptr TStmt 136 | Value{.pure, final.} = object 137 | PValue* = ptr Value 138 | PValueArg* = array[0..127, PValue] 139 | 140 | Callback* = proc (para1: pointer, para2: int32, para3, 141 | para4: cstringArray): int32{.cdecl.} 142 | Tbind_destructor_func* = proc (para1: pointer){.cdecl, locks: 0, tags: [], gcsafe.} 143 | Create_function_step_func* = proc (para1: Pcontext, para2: int32, 144 | para3: PValueArg){.cdecl.} 145 | Create_function_func_func* = proc (para1: Pcontext, para2: int32, 146 | para3: PValueArg){.cdecl.} 147 | Create_function_final_func* = proc (para1: Pcontext){.cdecl.} 148 | Result_func* = proc (para1: pointer){.cdecl.} 149 | Create_collation_func* = proc (para1: pointer, para2: int32, para3: pointer, 150 | para4: int32, para5: pointer): int32{.cdecl.} 151 | Collation_needed_func* = proc (para1: pointer, para2: PSqlite3, eTextRep: int32, 152 | para4: cstring){.cdecl.} 153 | 154 | const 155 | SQLITE_STATIC* = nil 156 | SQLITE_TRANSIENT* = cast[Tbind_destructor_func](-1) 157 | 158 | proc close*(para1: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_close".} 159 | proc exec*(para1: PSqlite3, sql: cstring, para3: Callback, para4: pointer, 160 | errmsg: var cstring): int32{.cdecl, mylib, 161 | importc: "sqlite3_exec".} 162 | proc last_insert_rowid*(para1: PSqlite3): int64{.cdecl, mylib, 163 | importc: "sqlite3_last_insert_rowid".} 164 | proc changes*(para1: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_changes".} 165 | proc total_changes*(para1: PSqlite3): int32{.cdecl, mylib, 166 | importc: "sqlite3_total_changes".} 167 | proc interrupt*(para1: PSqlite3){.cdecl, mylib, importc: "sqlite3_interrupt".} 168 | proc complete*(sql: cstring): int32{.cdecl, mylib, 169 | importc: "sqlite3_complete".} 170 | proc complete16*(sql: pointer): int32{.cdecl, mylib, 171 | importc: "sqlite3_complete16".} 172 | proc busy_handler*(para1: PSqlite3, 173 | para2: proc (para1: pointer, para2: int32): int32{.cdecl.}, 174 | para3: pointer): int32{.cdecl, mylib, 175 | importc: "sqlite3_busy_handler".} 176 | proc busy_timeout*(para1: PSqlite3, ms: int32): int32{.cdecl, mylib, 177 | importc: "sqlite3_busy_timeout".} 178 | proc get_table*(para1: PSqlite3, sql: cstring, resultp: var cstringArray, 179 | nrow, ncolumn: var cint, errmsg: ptr cstring): int32{.cdecl, 180 | mylib, importc: "sqlite3_get_table".} 181 | proc free_table*(result: cstringArray){.cdecl, mylib, 182 | importc: "sqlite3_free_table".} 183 | # Todo: see how translate sqlite3_mprintf, sqlite3_vmprintf, sqlite3_snprintf 184 | # function sqlite3_mprintf(_para1:Pchar; args:array of const):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_mprintf'; 185 | proc mprintf*(para1: cstring): cstring{.cdecl, varargs, mylib, 186 | importc: "sqlite3_mprintf".} 187 | #function sqlite3_vmprintf(_para1:Pchar; _para2:va_list):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_vmprintf'; 188 | proc free*(z: cstring){.cdecl, mylib, importc: "sqlite3_free".} 189 | #function sqlite3_snprintf(_para1:longint; _para2:Pchar; _para3:Pchar; args:array of const):Pchar;cdecl; external Sqlite3Lib name 'sqlite3_snprintf'; 190 | proc snprintf*(para1: int32, para2: cstring, para3: cstring): cstring{.cdecl, 191 | mylib, varargs, importc: "sqlite3_snprintf".} 192 | proc set_authorizer*(para1: PSqlite3, xAuth: proc (para1: pointer, para2: int32, 193 | para3: cstring, para4: cstring, para5: cstring, para6: cstring): int32{. 194 | cdecl.}, pUserData: pointer): int32{.cdecl, mylib, 195 | importc: "sqlite3_set_authorizer".} 196 | proc trace*(para1: PSqlite3, xTrace: proc (para1: pointer, para2: cstring){.cdecl.}, 197 | para3: pointer): pointer{.cdecl, mylib, 198 | importc: "sqlite3_trace".} 199 | proc progress_handler*(para1: PSqlite3, para2: int32, 200 | para3: proc (para1: pointer): int32{.cdecl.}, 201 | para4: pointer){.cdecl, mylib, 202 | importc: "sqlite3_progress_handler".} 203 | proc commit_hook*(para1: PSqlite3, para2: proc (para1: pointer): int32{.cdecl.}, 204 | para3: pointer): pointer{.cdecl, mylib, 205 | importc: "sqlite3_commit_hook".} 206 | proc open*(filename: cstring, ppDb: var PSqlite3): int32{.cdecl, mylib, 207 | importc: "sqlite3_open".} 208 | proc open16*(filename: pointer, ppDb: var PSqlite3): int32{.cdecl, mylib, 209 | importc: "sqlite3_open16".} 210 | proc errcode*(db: PSqlite3): int32{.cdecl, mylib, importc: "sqlite3_errcode".} 211 | proc errmsg*(para1: PSqlite3): cstring{.cdecl, mylib, importc: "sqlite3_errmsg".} 212 | proc errmsg16*(para1: PSqlite3): pointer{.cdecl, mylib, 213 | importc: "sqlite3_errmsg16".} 214 | proc prepare*(db: PSqlite3, zSql: cstring, nBytes: int32, ppStmt: var PStmt, 215 | pzTail: ptr cstring): int32{.cdecl, mylib, 216 | importc: "sqlite3_prepare".} 217 | 218 | proc prepare_v2*(db: PSqlite3, zSql: cstring, nByte: cint, ppStmt: var PStmt, 219 | pzTail: ptr cstring): cint {. 220 | importc: "sqlite3_prepare_v2", cdecl, mylib.} 221 | 222 | proc prepare16*(db: PSqlite3, zSql: pointer, nBytes: int32, ppStmt: var PStmt, 223 | pzTail: var pointer): int32{.cdecl, mylib, 224 | importc: "sqlite3_prepare16".} 225 | proc bind_blob*(para1: PStmt, para2: int32, para3: pointer, n: int32, 226 | para5: Tbind_destructor_func): int32{.cdecl, mylib, 227 | importc: "sqlite3_bind_blob".} 228 | proc bind_double*(para1: PStmt, para2: int32, para3: float64): int32{.cdecl, 229 | mylib, importc: "sqlite3_bind_double".} 230 | proc bind_int*(para1: PStmt, para2: int32, para3: int32): int32{.cdecl, 231 | mylib, importc: "sqlite3_bind_int".} 232 | proc bind_int64*(para1: PStmt, para2: int32, para3: int64): int32{.cdecl, 233 | mylib, importc: "sqlite3_bind_int64".} 234 | proc bind_null*(para1: PStmt, para2: int32): int32{.cdecl, mylib, 235 | importc: "sqlite3_bind_null".} 236 | proc bind_text*(para1: PStmt, para2: int32, para3: cstring, n: int32, 237 | para5: Tbind_destructor_func): int32{.cdecl, mylib, 238 | importc: "sqlite3_bind_text".} 239 | proc bind_text16*(para1: PStmt, para2: int32, para3: pointer, para4: int32, 240 | para5: Tbind_destructor_func): int32{.cdecl, mylib, 241 | importc: "sqlite3_bind_text16".} 242 | #function sqlite3_bind_value(_para1:Psqlite3_stmt; _para2:longint; _para3:Psqlite3_value):longint;cdecl; external Sqlite3Lib name 'sqlite3_bind_value'; 243 | #These overloaded functions were introduced to allow the use of SQLITE_STATIC and SQLITE_TRANSIENT 244 | #It's the c world man ;-) 245 | proc bind_blob*(para1: PStmt, para2: int32, para3: pointer, n: int32, 246 | para5: int32): int32{.cdecl, mylib, 247 | importc: "sqlite3_bind_blob".} 248 | proc bind_text*(para1: PStmt, para2: int32, para3: cstring, n: int32, 249 | para5: int32): int32{.cdecl, mylib, 250 | importc: "sqlite3_bind_text".} 251 | proc bind_text16*(para1: PStmt, para2: int32, para3: pointer, para4: int32, 252 | para5: int32): int32{.cdecl, mylib, 253 | importc: "sqlite3_bind_text16".} 254 | proc bind_parameter_count*(para1: PStmt): int32{.cdecl, mylib, 255 | importc: "sqlite3_bind_parameter_count".} 256 | proc bind_parameter_name*(para1: PStmt, para2: int32): cstring{.cdecl, 257 | mylib, importc: "sqlite3_bind_parameter_name".} 258 | proc bind_parameter_index*(para1: PStmt, zName: cstring): int32{.cdecl, 259 | mylib, importc: "sqlite3_bind_parameter_index".} 260 | proc clear_bindings*(para1: PStmt): int32 {.cdecl, 261 | mylib, importc: "sqlite3_clear_bindings".} 262 | proc column_count*(PStmt: PStmt): int32{.cdecl, mylib, 263 | importc: "sqlite3_column_count".} 264 | proc column_name*(para1: PStmt, para2: int32): cstring{.cdecl, mylib, 265 | importc: "sqlite3_column_name".} 266 | proc column_table_name*(para1: PStmt; para2: int32): cstring{.cdecl, mylib, 267 | importc: "sqlite3_column_table_name".} 268 | proc column_name16*(para1: PStmt, para2: int32): pointer{.cdecl, mylib, 269 | importc: "sqlite3_column_name16".} 270 | proc column_decltype*(para1: PStmt, i: int32): cstring{.cdecl, mylib, 271 | importc: "sqlite3_column_decltype".} 272 | proc column_decltype16*(para1: PStmt, para2: int32): pointer{.cdecl, 273 | mylib, importc: "sqlite3_column_decltype16".} 274 | proc step*(para1: PStmt): int32{.cdecl, mylib, importc: "sqlite3_step".} 275 | proc data_count*(PStmt: PStmt): int32{.cdecl, mylib, 276 | importc: "sqlite3_data_count".} 277 | proc column_blob*(para1: PStmt, iCol: int32): pointer{.cdecl, mylib, 278 | importc: "sqlite3_column_blob".} 279 | proc column_bytes*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 280 | importc: "sqlite3_column_bytes".} 281 | proc column_bytes16*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 282 | importc: "sqlite3_column_bytes16".} 283 | proc column_double*(para1: PStmt, iCol: int32): float64{.cdecl, mylib, 284 | importc: "sqlite3_column_double".} 285 | proc column_int*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 286 | importc: "sqlite3_column_int".} 287 | proc column_int64*(para1: PStmt, iCol: int32): int64{.cdecl, mylib, 288 | importc: "sqlite3_column_int64".} 289 | proc column_text*(para1: PStmt, iCol: int32): cstring{.cdecl, mylib, 290 | importc: "sqlite3_column_text".} 291 | proc column_text16*(para1: PStmt, iCol: int32): pointer{.cdecl, mylib, 292 | importc: "sqlite3_column_text16".} 293 | proc column_type*(para1: PStmt, iCol: int32): int32{.cdecl, mylib, 294 | importc: "sqlite3_column_type".} 295 | proc finalize*(PStmt: PStmt): int32{.cdecl, mylib, 296 | importc: "sqlite3_finalize".} 297 | proc reset*(PStmt: PStmt): int32{.cdecl, mylib, importc: "sqlite3_reset".} 298 | proc create_function*(para1: PSqlite3, zFunctionName: cstring, nArg: int32, 299 | eTextRep: int32, para5: pointer, 300 | xFunc: Create_function_func_func, 301 | xStep: Create_function_step_func, 302 | xFinal: Create_function_final_func): int32{.cdecl, 303 | mylib, importc: "sqlite3_create_function".} 304 | proc create_function16*(para1: PSqlite3, zFunctionName: pointer, nArg: int32, 305 | eTextRep: int32, para5: pointer, 306 | xFunc: Create_function_func_func, 307 | xStep: Create_function_step_func, 308 | xFinal: Create_function_final_func): int32{.cdecl, 309 | mylib, importc: "sqlite3_create_function16".} 310 | proc aggregate_count*(para1: Pcontext): int32{.cdecl, mylib, 311 | importc: "sqlite3_aggregate_count".} 312 | proc value_blob*(para1: PValue): pointer{.cdecl, mylib, 313 | importc: "sqlite3_value_blob".} 314 | proc value_bytes*(para1: PValue): int32{.cdecl, mylib, 315 | importc: "sqlite3_value_bytes".} 316 | proc value_bytes16*(para1: PValue): int32{.cdecl, mylib, 317 | importc: "sqlite3_value_bytes16".} 318 | proc value_double*(para1: PValue): float64{.cdecl, mylib, 319 | importc: "sqlite3_value_double".} 320 | proc value_int*(para1: PValue): int32{.cdecl, mylib, 321 | importc: "sqlite3_value_int".} 322 | proc value_int64*(para1: PValue): int64{.cdecl, mylib, 323 | importc: "sqlite3_value_int64".} 324 | proc value_text*(para1: PValue): cstring{.cdecl, mylib, 325 | importc: "sqlite3_value_text".} 326 | proc value_text16*(para1: PValue): pointer{.cdecl, mylib, 327 | importc: "sqlite3_value_text16".} 328 | proc value_text16le*(para1: PValue): pointer{.cdecl, mylib, 329 | importc: "sqlite3_value_text16le".} 330 | proc value_text16be*(para1: PValue): pointer{.cdecl, mylib, 331 | importc: "sqlite3_value_text16be".} 332 | proc value_type*(para1: PValue): int32{.cdecl, mylib, 333 | importc: "sqlite3_value_type".} 334 | proc aggregate_context*(para1: Pcontext, nBytes: int32): pointer{.cdecl, 335 | mylib, importc: "sqlite3_aggregate_context".} 336 | proc user_data*(para1: Pcontext): pointer{.cdecl, mylib, 337 | importc: "sqlite3_user_data".} 338 | proc get_auxdata*(para1: Pcontext, para2: int32): pointer{.cdecl, mylib, 339 | importc: "sqlite3_get_auxdata".} 340 | proc set_auxdata*(para1: Pcontext, para2: int32, para3: pointer, 341 | para4: proc (para1: pointer){.cdecl.}){.cdecl, mylib, 342 | importc: "sqlite3_set_auxdata".} 343 | proc result_blob*(para1: Pcontext, para2: pointer, para3: int32, 344 | para4: Result_func){.cdecl, mylib, 345 | importc: "sqlite3_result_blob".} 346 | proc result_double*(para1: Pcontext, para2: float64){.cdecl, mylib, 347 | importc: "sqlite3_result_double".} 348 | proc result_error*(para1: Pcontext, para2: cstring, para3: int32){.cdecl, 349 | mylib, importc: "sqlite3_result_error".} 350 | proc result_error16*(para1: Pcontext, para2: pointer, para3: int32){.cdecl, 351 | mylib, importc: "sqlite3_result_error16".} 352 | proc result_int*(para1: Pcontext, para2: int32){.cdecl, mylib, 353 | importc: "sqlite3_result_int".} 354 | proc result_int64*(para1: Pcontext, para2: int64){.cdecl, mylib, 355 | importc: "sqlite3_result_int64".} 356 | proc result_null*(para1: Pcontext){.cdecl, mylib, 357 | importc: "sqlite3_result_null".} 358 | proc result_text*(para1: Pcontext, para2: cstring, para3: int32, 359 | para4: Result_func){.cdecl, mylib, 360 | importc: "sqlite3_result_text".} 361 | proc result_text16*(para1: Pcontext, para2: pointer, para3: int32, 362 | para4: Result_func){.cdecl, mylib, 363 | importc: "sqlite3_result_text16".} 364 | proc result_text16le*(para1: Pcontext, para2: pointer, para3: int32, 365 | para4: Result_func){.cdecl, mylib, 366 | importc: "sqlite3_result_text16le".} 367 | proc result_text16be*(para1: Pcontext, para2: pointer, para3: int32, 368 | para4: Result_func){.cdecl, mylib, 369 | importc: "sqlite3_result_text16be".} 370 | proc result_value*(para1: Pcontext, para2: PValue){.cdecl, mylib, 371 | importc: "sqlite3_result_value".} 372 | proc create_collation*(para1: PSqlite3, zName: cstring, eTextRep: int32, 373 | para4: pointer, xCompare: Create_collation_func): int32{. 374 | cdecl, mylib, importc: "sqlite3_create_collation".} 375 | proc create_collation16*(para1: PSqlite3, zName: cstring, eTextRep: int32, 376 | para4: pointer, xCompare: Create_collation_func): int32{. 377 | cdecl, mylib, importc: "sqlite3_create_collation16".} 378 | proc collation_needed*(para1: PSqlite3, para2: pointer, para3: Collation_needed_func): int32{. 379 | cdecl, mylib, importc: "sqlite3_collation_needed".} 380 | proc collation_needed16*(para1: PSqlite3, para2: pointer, para3: Collation_needed_func): int32{. 381 | cdecl, mylib, importc: "sqlite3_collation_needed16".} 382 | proc libversion*(): cstring{.cdecl, mylib, importc: "sqlite3_libversion".} 383 | #Alias for allowing better code portability (win32 is not working with external variables) 384 | proc version*(): cstring{.cdecl, mylib, importc: "sqlite3_libversion".} 385 | # Not published functions 386 | proc libversion_number*(): int32{.cdecl, mylib, 387 | importc: "sqlite3_libversion_number".} 388 | 389 | proc backup_init*(pDest: PSqlite3, zDestName: cstring, pSource: PSqlite3, zSourceName: cstring): PSqlite3_Backup {. 390 | cdecl, mylib, importc: "sqlite3_backup_init".} 391 | 392 | proc backup_step*(pBackup: PSqlite3_Backup, nPage: int32): int32 {.cdecl, mylib, importc: "sqlite3_backup_step".} 393 | 394 | proc backup_finish*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_finish".} 395 | 396 | proc backup_pagecount*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_pagecount".} 397 | 398 | proc backup_remaining*(pBackup: PSqlite3_Backup): int32 {.cdecl, mylib, importc: "sqlite3_backup_remaining".} 399 | 400 | proc sqlite3_sleep*(t: int64): int64 {.cdecl, mylib, importc: "sqlite3_sleep".} 401 | 402 | #function sqlite3_key(db:Psqlite3; pKey:pointer; nKey:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_key'; 403 | #function sqlite3_rekey(db:Psqlite3; pKey:pointer; nKey:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_rekey'; 404 | #function sqlite3_sleep(_para1:longint):longint;cdecl; external Sqlite3Lib name 'sqlite3_sleep'; 405 | #function sqlite3_expired(_para1:Psqlite3_stmt):longint;cdecl; external Sqlite3Lib name 'sqlite3_expired'; 406 | #function sqlite3_global_recover:longint;cdecl; external Sqlite3Lib name 'sqlite3_global_recover'; 407 | # implementation 408 | 409 | when defined(nimHasStyleChecks): 410 | {.pop.} -------------------------------------------------------------------------------- /examples/sqlite/db_sqlite.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2015 Andreas Rumpf 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## A higher level `SQLite`:idx: database wrapper. This interface 11 | ## is implemented for other databases too. 12 | ## 13 | ## Basic usage 14 | ## =========== 15 | ## 16 | ## The basic flow of using this module is: 17 | ## 18 | ## 1. Open database connection 19 | ## 2. Execute SQL query 20 | ## 3. Close database connection 21 | ## 22 | ## Parameter substitution 23 | ## ---------------------- 24 | ## 25 | ## All `db_*` modules support the same form of parameter substitution. 26 | ## That is, using the `?` (question mark) to signify the place where a 27 | ## value should be placed. For example: 28 | ## 29 | ## .. code-block:: Nim 30 | ## 31 | ## sql"INSERT INTO my_table (colA, colB, colC) VALUES (?, ?, ?)" 32 | ## 33 | ## Opening a connection to a database 34 | ## ---------------------------------- 35 | ## 36 | ## .. code-block:: Nim 37 | ## 38 | ## import std/db_sqlite 39 | ## 40 | ## # user, password, database name can be empty. 41 | ## # These params are not used on db_sqlite module. 42 | ## let db = open("mytest.db", "", "", "") 43 | ## db.close() 44 | ## 45 | ## Creating a table 46 | ## ---------------- 47 | ## 48 | ## .. code-block:: Nim 49 | ## 50 | ## db.exec(sql"DROP TABLE IF EXISTS my_table") 51 | ## db.exec(sql"""CREATE TABLE my_table ( 52 | ## id INTEGER, 53 | ## name VARCHAR(50) NOT NULL 54 | ## )""") 55 | ## 56 | ## Inserting data 57 | ## -------------- 58 | ## 59 | ## .. code-block:: Nim 60 | ## 61 | ## db.exec(sql"INSERT INTO my_table (id, name) VALUES (0, ?)", 62 | ## "Jack") 63 | ## 64 | ## Larger example 65 | ## -------------- 66 | ## 67 | ## .. code-block:: nim 68 | ## 69 | ## import std/[db_sqlite, math] 70 | ## 71 | ## let db = open("mytest.db", "", "", "") 72 | ## 73 | ## db.exec(sql"DROP TABLE IF EXISTS my_table") 74 | ## db.exec(sql"""CREATE TABLE my_table ( 75 | ## id INTEGER PRIMARY KEY, 76 | ## name VARCHAR(50) NOT NULL, 77 | ## i INT(11), 78 | ## f DECIMAL(18, 10) 79 | ## )""") 80 | ## 81 | ## db.exec(sql"BEGIN") 82 | ## for i in 1..1000: 83 | ## db.exec(sql"INSERT INTO my_table (name, i, f) VALUES (?, ?, ?)", 84 | ## "Item#" & $i, i, sqrt(i.float)) 85 | ## db.exec(sql"COMMIT") 86 | ## 87 | ## for x in db.fastRows(sql"SELECT * FROM my_table"): 88 | ## echo x 89 | ## 90 | ## let id = db.tryInsertId(sql"""INSERT INTO my_table (name, i, f) 91 | ## VALUES (?, ?, ?)""", 92 | ## "Item#1001", 1001, sqrt(1001.0)) 93 | ## echo "Inserted item: ", db.getValue(sql"SELECT name FROM my_table WHERE id=?", id) 94 | ## 95 | ## db.close() 96 | ## 97 | ## Storing binary data example 98 | ##---------------------------- 99 | ## 100 | ## .. code-block:: nim 101 | ## 102 | ## import std/random 103 | ## 104 | ## ## Generate random float datas 105 | ## var orig = newSeq[float64](150) 106 | ## randomize() 107 | ## for x in orig.mitems: 108 | ## x = rand(1.0)/10.0 109 | ## 110 | ## let db = open("mysqlite.db", "", "", "") 111 | ## block: ## Create database 112 | ## ## Binary datas needs to be of type BLOB in SQLite 113 | ## let createTableStr = sql"""CREATE TABLE test( 114 | ## id INTEGER NOT NULL PRIMARY KEY, 115 | ## data BLOB 116 | ## ) 117 | ## """ 118 | ## db.exec(createTableStr) 119 | ## 120 | ## block: ## Insert data 121 | ## var id = 1 122 | ## ## Data needs to be converted to seq[byte] to be interpreted as binary by bindParams 123 | ## var dbuf = newSeq[byte](orig.len*sizeof(float64)) 124 | ## copyMem(unsafeAddr(dbuf[0]), unsafeAddr(orig[0]), dbuf.len) 125 | ## 126 | ## ## Use prepared statement to insert binary data into database 127 | ## var insertStmt = db.prepare("INSERT INTO test (id, data) VALUES (?, ?)") 128 | ## insertStmt.bindParams(id, dbuf) 129 | ## let bres = db.tryExec(insertStmt) 130 | ## ## Check insert 131 | ## doAssert(bres) 132 | ## # Destroy statement 133 | ## finalize(insertStmt) 134 | ## 135 | ## block: ## Use getValue to select data 136 | ## var dataTest = db.getValue(sql"SELECT data FROM test WHERE id = ?", 1) 137 | ## ## Calculate sequence size from buffer size 138 | ## let seqSize = int(dataTest.len*sizeof(byte)/sizeof(float64)) 139 | ## ## Copy binary string data in dataTest into a seq 140 | ## var res: seq[float64] = newSeq[float64](seqSize) 141 | ## copyMem(unsafeAddr(res[0]), addr(dataTest[0]), dataTest.len) 142 | ## 143 | ## ## Check datas obtained is identical 144 | ## doAssert res == orig 145 | ## 146 | ## db.close() 147 | ## 148 | ## 149 | ## Note 150 | ## ==== 151 | ## This module does not implement any ORM features such as mapping the types from the schema. 152 | ## Instead, a `seq[string]` is returned for each row. 153 | ## 154 | ## The reasoning is as follows: 155 | ## 1. it's close to what many DBs offer natively (`char**`:c:) 156 | ## 2. it hides the number of types that the DB supports 157 | ## (int? int64? decimal up to 10 places? geo coords?) 158 | ## 3. it's convenient when all you do is to forward the data to somewhere else (echo, log, put the data into a new query) 159 | ## 160 | ## See also 161 | ## ======== 162 | ## 163 | ## * `db_odbc module `_ for ODBC database wrapper 164 | ## * `db_mysql module `_ for MySQL database wrapper 165 | ## * `db_postgres module `_ for PostgreSQL database wrapper 166 | 167 | {.experimental: "codeReordering".} 168 | 169 | import sqlite3, macros 170 | 171 | import db_common 172 | export db_common 173 | 174 | import std/private/[since, dbutils] 175 | 176 | type 177 | DbConn* = PSqlite3 ## Encapsulates a database connection. 178 | Row* = seq[string] ## A row of a dataset. `NULL` database values will be 179 | ## converted to an empty string. 180 | InstantRow* = PStmt ## A handle that can be used to get a row's column 181 | ## text on demand. 182 | SqlPrepared* = distinct PStmt ## a identifier for the prepared queries 183 | 184 | proc dbError*(db: DbConn) {.noreturn.} = 185 | ## Raises a `DbError` exception. 186 | ## 187 | ## **Examples:** 188 | ## 189 | ## .. code-block:: Nim 190 | ## 191 | ## let db = open("mytest.db", "", "", "") 192 | ## if not db.tryExec(sql"SELECT * FROM not_exist_table"): 193 | ## dbError(db) 194 | ## db.close() 195 | var e: ref DbError 196 | new(e) 197 | e.msg = $sqlite3.errmsg(db) 198 | raise e 199 | 200 | proc dbQuote*(s: string): string = 201 | ## Escapes the `'` (single quote) char to `''`. 202 | ## Because single quote is used for defining `VARCHAR` in SQL. 203 | runnableExamples: 204 | doAssert dbQuote("'") == "''''" 205 | doAssert dbQuote("A Foobar's pen.") == "'A Foobar''s pen.'" 206 | 207 | result = "'" 208 | for c in items(s): 209 | if c == '\'': add(result, "''") 210 | else: add(result, c) 211 | add(result, '\'') 212 | 213 | proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string = 214 | dbFormatImpl(formatstr, dbQuote, args) 215 | 216 | proc prepare*(db: DbConn; q: string): SqlPrepared {.since: (1, 3).} = 217 | ## Creates a new `SqlPrepared` statement. 218 | if prepare_v2(db, q, q.len.cint,result.PStmt, nil) != SQLITE_OK: 219 | discard finalize(result.PStmt) 220 | dbError(db) 221 | 222 | proc tryExec*(db: DbConn, query: SqlQuery, 223 | args: varargs[string, `$`]): bool = 224 | ## Tries to execute the query and returns `true` if successful, `false` otherwise. 225 | ## 226 | ## **Examples:** 227 | ## 228 | ## .. code-block:: Nim 229 | ## 230 | ## let db = open("mytest.db", "", "", "") 231 | ## if not db.tryExec(sql"SELECT * FROM my_table"): 232 | ## dbError(db) 233 | ## db.close() 234 | assert(not db.isNil, "Database not connected.") 235 | var q = dbFormat(query, args) 236 | var stmt: sqlite3.PStmt 237 | if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK: 238 | let x = step(stmt) 239 | if x in {SQLITE_DONE, SQLITE_ROW}: 240 | result = finalize(stmt) == SQLITE_OK 241 | else: 242 | discard finalize(stmt) 243 | result = false 244 | 245 | proc tryExec*(db: DbConn, stmtName: SqlPrepared): bool = 246 | let x = step(stmtName.PStmt) 247 | if x in {SQLITE_DONE, SQLITE_ROW}: 248 | result = true 249 | else: 250 | discard finalize(stmtName.PStmt) 251 | result = false 252 | 253 | proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) = 254 | ## Executes the query and raises a `DbError` exception if not successful. 255 | ## 256 | ## **Examples:** 257 | ## 258 | ## .. code-block:: Nim 259 | ## 260 | ## let db = open("mytest.db", "", "", "") 261 | ## try: 262 | ## db.exec(sql"INSERT INTO my_table (id, name) VALUES (?, ?)", 263 | ## 1, "item#1") 264 | ## except: 265 | ## stderr.writeLine(getCurrentExceptionMsg()) 266 | ## finally: 267 | ## db.close() 268 | if not tryExec(db, query, args): dbError(db) 269 | 270 | proc newRow(L: int): Row = 271 | newSeq(result, L) 272 | for i in 0..L-1: result[i] = "" 273 | 274 | proc setupQuery(db: DbConn, query: SqlQuery, 275 | args: varargs[string]): PStmt = 276 | assert(not db.isNil, "Database not connected.") 277 | var q = dbFormat(query, args) 278 | if prepare_v2(db, q, q.len.cint, result, nil) != SQLITE_OK: dbError(db) 279 | 280 | proc setupQuery(db: DbConn, stmtName: SqlPrepared): SqlPrepared {.since: (1, 3).} = 281 | assert(not db.isNil, "Database not connected.") 282 | result = stmtName 283 | 284 | proc setRow(stmt: PStmt, r: var Row, cols: cint) = 285 | for col in 0'i32..cols-1: 286 | let cb = column_bytes(stmt, col) 287 | setLen(r[col], cb) # set capacity 288 | if column_type(stmt, col) == SQLITE_BLOB: 289 | copyMem(addr(r[col][0]), column_blob(stmt, col), cb) 290 | else: 291 | setLen(r[col], 0) 292 | let x = column_text(stmt, col) 293 | if not isNil(x): add(r[col], x) 294 | 295 | iterator fastRows*(db: DbConn, query: SqlQuery, 296 | args: varargs[string, `$`]): Row = 297 | ## Executes the query and iterates over the result dataset. 298 | ## 299 | ## This is very fast, but potentially dangerous. Use this iterator only 300 | ## if you require **ALL** the rows. 301 | ## 302 | ## **Note:** Breaking the `fastRows()` iterator during a loop will cause the 303 | ## next database query to raise a `DbError` exception `unable to close due 304 | ## to ...`. 305 | ## 306 | ## **Examples:** 307 | ## 308 | ## .. code-block:: Nim 309 | ## 310 | ## let db = open("mytest.db", "", "", "") 311 | ## 312 | ## # Records of my_table: 313 | ## # | id | name | 314 | ## # |----|----------| 315 | ## # | 1 | item#1 | 316 | ## # | 2 | item#2 | 317 | ## 318 | ## for row in db.fastRows(sql"SELECT id, name FROM my_table"): 319 | ## echo row 320 | ## 321 | ## # Output: 322 | ## # @["1", "item#1"] 323 | ## # @["2", "item#2"] 324 | ## 325 | ## db.close() 326 | var stmt = setupQuery(db, query, args) 327 | var L = (column_count(stmt)) 328 | var result = newRow(L) 329 | try: 330 | while step(stmt) == SQLITE_ROW: 331 | setRow(stmt, result, L) 332 | yield result 333 | finally: 334 | if finalize(stmt) != SQLITE_OK: dbError(db) 335 | 336 | iterator fastRows*(db: DbConn, stmtName: SqlPrepared): Row {.since: (1, 3).} = 337 | discard setupQuery(db, stmtName) 338 | var L = (column_count(stmtName.PStmt)) 339 | var result = newRow(L) 340 | try: 341 | while step(stmtName.PStmt) == SQLITE_ROW: 342 | setRow(stmtName.PStmt, result, L) 343 | yield result 344 | except: 345 | dbError(db) 346 | 347 | iterator instantRows*(db: DbConn, query: SqlQuery, 348 | args: varargs[string, `$`]): InstantRow = 349 | ## Similar to `fastRows iterator <#fastRows.i,DbConn,SqlQuery,varargs[string,]>`_ 350 | ## but returns a handle that can be used to get column text 351 | ## on demand using `[]`. Returned handle is valid only within the iterator body. 352 | ## 353 | ## **Examples:** 354 | ## 355 | ## .. code-block:: Nim 356 | ## 357 | ## let db = open("mytest.db", "", "", "") 358 | ## 359 | ## # Records of my_table: 360 | ## # | id | name | 361 | ## # |----|----------| 362 | ## # | 1 | item#1 | 363 | ## # | 2 | item#2 | 364 | ## 365 | ## for row in db.instantRows(sql"SELECT * FROM my_table"): 366 | ## echo "id:" & row[0] 367 | ## echo "name:" & row[1] 368 | ## echo "length:" & $len(row) 369 | ## 370 | ## # Output: 371 | ## # id:1 372 | ## # name:item#1 373 | ## # length:2 374 | ## # id:2 375 | ## # name:item#2 376 | ## # length:2 377 | ## 378 | ## db.close() 379 | var stmt = setupQuery(db, query, args) 380 | try: 381 | while step(stmt) == SQLITE_ROW: 382 | yield stmt 383 | finally: 384 | if finalize(stmt) != SQLITE_OK: dbError(db) 385 | 386 | iterator instantRows*(db: DbConn, stmtName: SqlPrepared): InstantRow {.since: (1, 3).} = 387 | var stmt = setupQuery(db, stmtName).PStmt 388 | try: 389 | while step(stmt) == SQLITE_ROW: 390 | yield stmt 391 | except: 392 | dbError(db) 393 | 394 | proc toTypeKind(t: var DbType; x: int32) = 395 | case x 396 | of SQLITE_INTEGER: 397 | t.kind = dbInt 398 | t.size = 8 399 | of SQLITE_FLOAT: 400 | t.kind = dbFloat 401 | t.size = 8 402 | of SQLITE_BLOB: t.kind = dbBlob 403 | of SQLITE_NULL: t.kind = dbNull 404 | of SQLITE_TEXT: t.kind = dbVarchar 405 | else: t.kind = dbUnknown 406 | 407 | proc setColumns(columns: var DbColumns; x: PStmt) = 408 | let L = column_count(x) 409 | setLen(columns, L) 410 | for i in 0'i32 ..< L: 411 | columns[i].name = $column_name(x, i) 412 | columns[i].typ.name = $column_decltype(x, i) 413 | toTypeKind(columns[i].typ, column_type(x, i)) 414 | columns[i].tableName = $column_table_name(x, i) 415 | 416 | iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery, 417 | args: varargs[string, `$`]): InstantRow = 418 | ## Similar to `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_, 419 | ## but sets information about columns to `columns`. 420 | ## 421 | ## **Examples:** 422 | ## 423 | ## .. code-block:: Nim 424 | ## 425 | ## let db = open("mytest.db", "", "", "") 426 | ## 427 | ## # Records of my_table: 428 | ## # | id | name | 429 | ## # |----|----------| 430 | ## # | 1 | item#1 | 431 | ## # | 2 | item#2 | 432 | ## 433 | ## var columns: DbColumns 434 | ## for row in db.instantRows(columns, sql"SELECT * FROM my_table"): 435 | ## discard 436 | ## echo columns[0] 437 | ## 438 | ## # Output: 439 | ## # (name: "id", tableName: "my_table", typ: (kind: dbNull, 440 | ## # notNull: false, name: "INTEGER", size: 0, maxReprLen: 0, precision: 0, 441 | ## # scale: 0, min: 0, max: 0, validValues: @[]), primaryKey: false, 442 | ## # foreignKey: false) 443 | ## 444 | ## db.close() 445 | var stmt = setupQuery(db, query, args) 446 | setColumns(columns, stmt) 447 | try: 448 | while step(stmt) == SQLITE_ROW: 449 | yield stmt 450 | finally: 451 | if finalize(stmt) != SQLITE_OK: dbError(db) 452 | 453 | proc `[]`*(row: InstantRow, col: int32): string {.inline.} = 454 | ## Returns text for given column of the row. 455 | ## 456 | ## See also: 457 | ## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_ 458 | ## example code 459 | $column_text(row, col) 460 | 461 | proc unsafeColumnAt*(row: InstantRow, index: int32): cstring {.inline.} = 462 | ## Returns cstring for given column of the row. 463 | ## 464 | ## See also: 465 | ## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_ 466 | ## example code 467 | column_text(row, index) 468 | 469 | proc len*(row: InstantRow): int32 {.inline.} = 470 | ## Returns number of columns in a row. 471 | ## 472 | ## See also: 473 | ## * `instantRows iterator <#instantRows.i,DbConn,SqlQuery,varargs[string,]>`_ 474 | ## example code 475 | column_count(row) 476 | 477 | proc getRow*(db: DbConn, query: SqlQuery, 478 | args: varargs[string, `$`]): Row = 479 | ## Retrieves a single row. If the query doesn't return any rows, this proc 480 | ## will return a `Row` with empty strings for each column. 481 | ## 482 | ## **Examples:** 483 | ## 484 | ## .. code-block:: Nim 485 | ## 486 | ## let db = open("mytest.db", "", "", "") 487 | ## 488 | ## # Records of my_table: 489 | ## # | id | name | 490 | ## # |----|----------| 491 | ## # | 1 | item#1 | 492 | ## # | 2 | item#2 | 493 | ## 494 | ## doAssert db.getRow(sql"SELECT id, name FROM my_table" 495 | ## ) == Row(@["1", "item#1"]) 496 | ## doAssert db.getRow(sql"SELECT id, name FROM my_table WHERE id = ?", 497 | ## 2) == Row(@["2", "item#2"]) 498 | ## 499 | ## # Returns empty. 500 | ## doAssert db.getRow(sql"INSERT INTO my_table (id, name) VALUES (?, ?)", 501 | ## 3, "item#3") == @[] 502 | ## doAssert db.getRow(sql"DELETE FROM my_table WHERE id = ?", 3) == @[] 503 | ## doAssert db.getRow(sql"UPDATE my_table SET name = 'ITEM#1' WHERE id = ?", 504 | ## 1) == @[] 505 | ## db.close() 506 | var stmt = setupQuery(db, query, args) 507 | var L = (column_count(stmt)) 508 | result = newRow(L) 509 | if step(stmt) == SQLITE_ROW: 510 | setRow(stmt, result, L) 511 | if finalize(stmt) != SQLITE_OK: dbError(db) 512 | 513 | proc getAllRows*(db: DbConn, query: SqlQuery, 514 | args: varargs[string, `$`]): seq[Row] = 515 | ## Executes the query and returns the whole result dataset. 516 | ## 517 | ## **Examples:** 518 | ## 519 | ## .. code-block:: Nim 520 | ## 521 | ## let db = open("mytest.db", "", "", "") 522 | ## 523 | ## # Records of my_table: 524 | ## # | id | name | 525 | ## # |----|----------| 526 | ## # | 1 | item#1 | 527 | ## # | 2 | item#2 | 528 | ## 529 | ## doAssert db.getAllRows(sql"SELECT id, name FROM my_table") == @[Row(@["1", "item#1"]), Row(@["2", "item#2"])] 530 | ## db.close() 531 | result = @[] 532 | for r in fastRows(db, query, args): 533 | result.add(r) 534 | 535 | proc getAllRows*(db: DbConn, stmtName: SqlPrepared): seq[Row] {.since: (1, 3).} = 536 | result = @[] 537 | for r in fastRows(db, stmtName): 538 | result.add(r) 539 | 540 | iterator rows*(db: DbConn, query: SqlQuery, 541 | args: varargs[string, `$`]): Row = 542 | ## Similar to `fastRows iterator <#fastRows.i,DbConn,SqlQuery,varargs[string,]>`_, 543 | ## but slower and safe. 544 | ## 545 | ## **Examples:** 546 | ## 547 | ## .. code-block:: Nim 548 | ## 549 | ## let db = open("mytest.db", "", "", "") 550 | ## 551 | ## # Records of my_table: 552 | ## # | id | name | 553 | ## # |----|----------| 554 | ## # | 1 | item#1 | 555 | ## # | 2 | item#2 | 556 | ## 557 | ## for row in db.rows(sql"SELECT id, name FROM my_table"): 558 | ## echo row 559 | ## 560 | ## ## Output: 561 | ## ## @["1", "item#1"] 562 | ## ## @["2", "item#2"] 563 | ## 564 | ## db.close() 565 | for r in fastRows(db, query, args): yield r 566 | 567 | iterator rows*(db: DbConn, stmtName: SqlPrepared): Row {.since: (1, 3).} = 568 | for r in fastRows(db, stmtName): yield r 569 | 570 | proc getValue*(db: DbConn, query: SqlQuery, 571 | args: varargs[string, `$`]): string = 572 | ## Executes the query and returns the first column of the first row of the 573 | ## result dataset. Returns `""` if the dataset contains no rows or the database 574 | ## value is `NULL`. 575 | ## 576 | ## **Examples:** 577 | ## 578 | ## .. code-block:: Nim 579 | ## 580 | ## let db = open("mytest.db", "", "", "") 581 | ## 582 | ## # Records of my_table: 583 | ## # | id | name | 584 | ## # |----|----------| 585 | ## # | 1 | item#1 | 586 | ## # | 2 | item#2 | 587 | ## 588 | ## doAssert db.getValue(sql"SELECT name FROM my_table WHERE id = ?", 589 | ## 2) == "item#2" 590 | ## doAssert db.getValue(sql"SELECT id, name FROM my_table") == "1" 591 | ## doAssert db.getValue(sql"SELECT name, id FROM my_table") == "item#1" 592 | ## 593 | ## db.close() 594 | var stmt = setupQuery(db, query, args) 595 | if step(stmt) == SQLITE_ROW: 596 | let cb = column_bytes(stmt, 0) 597 | if cb == 0: 598 | result = "" 599 | else: 600 | if column_type(stmt, 0) == SQLITE_BLOB: 601 | result.setLen(cb) 602 | copyMem(addr(result[0]), column_blob(stmt, 0), cb) 603 | else: 604 | result = newStringOfCap(cb) 605 | add(result, column_text(stmt, 0)) 606 | else: 607 | result = "" 608 | if finalize(stmt) != SQLITE_OK: dbError(db) 609 | 610 | proc getValue*(db: DbConn, stmtName: SqlPrepared): string {.since: (1, 3).} = 611 | var stmt = setupQuery(db, stmtName).PStmt 612 | if step(stmt) == SQLITE_ROW: 613 | let cb = column_bytes(stmt, 0) 614 | if cb == 0: 615 | result = "" 616 | else: 617 | if column_type(stmt, 0) == SQLITE_BLOB: 618 | result.setLen(cb) 619 | copyMem(addr(result[0]), column_blob(stmt, 0), cb) 620 | else: 621 | result = newStringOfCap(cb) 622 | add(result, column_text(stmt, 0)) 623 | else: 624 | result = "" 625 | 626 | proc tryInsertID*(db: DbConn, query: SqlQuery, 627 | args: varargs[string, `$`]): int64 = 628 | ## Executes the query (typically "INSERT") and returns the 629 | ## generated ID for the row or -1 in case of an error. 630 | ## 631 | ## **Examples:** 632 | ## 633 | ## .. code-block:: Nim 634 | ## 635 | ## let db = open("mytest.db", "", "", "") 636 | ## db.exec(sql"CREATE TABLE my_table (id INTEGER, name VARCHAR(50) NOT NULL)") 637 | ## 638 | ## doAssert db.tryInsertID(sql"INSERT INTO not_exist_table (id, name) VALUES (?, ?)", 639 | ## 1, "item#1") == -1 640 | ## db.close() 641 | assert(not db.isNil, "Database not connected.") 642 | var q = dbFormat(query, args) 643 | var stmt: sqlite3.PStmt 644 | result = -1 645 | if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK: 646 | if step(stmt) == SQLITE_DONE: 647 | result = last_insert_rowid(db) 648 | if finalize(stmt) != SQLITE_OK: 649 | result = -1 650 | else: 651 | discard finalize(stmt) 652 | 653 | proc insertID*(db: DbConn, query: SqlQuery, 654 | args: varargs[string, `$`]): int64 = 655 | ## Executes the query (typically "INSERT") and returns the 656 | ## generated ID for the row. 657 | ## 658 | ## Raises a `DbError` exception when failed to insert row. 659 | ## For Postgre this adds `RETURNING id` to the query, so it only works 660 | ## if your primary key is named `id`. 661 | ## 662 | ## **Examples:** 663 | ## 664 | ## .. code-block:: Nim 665 | ## 666 | ## let db = open("mytest.db", "", "", "") 667 | ## db.exec(sql"CREATE TABLE my_table (id INTEGER, name VARCHAR(50) NOT NULL)") 668 | ## 669 | ## for i in 0..2: 670 | ## let id = db.insertID(sql"INSERT INTO my_table (id, name) VALUES (?, ?)", i, "item#" & $i) 671 | ## echo "LoopIndex = ", i, ", InsertID = ", id 672 | ## 673 | ## # Output: 674 | ## # LoopIndex = 0, InsertID = 1 675 | ## # LoopIndex = 1, InsertID = 2 676 | ## # LoopIndex = 2, InsertID = 3 677 | ## 678 | ## db.close() 679 | result = tryInsertID(db, query, args) 680 | if result < 0: dbError(db) 681 | 682 | proc tryInsert*(db: DbConn, query: SqlQuery, pkName: string, 683 | args: varargs[string, `$`]): int64 {.since: (1, 3).} = 684 | ## same as tryInsertID 685 | tryInsertID(db, query, args) 686 | 687 | proc insert*(db: DbConn, query: SqlQuery, pkName: string, 688 | args: varargs[string, `$`]): int64 {.since: (1, 3).} = 689 | ## same as insertId 690 | result = tryInsert(db, query,pkName, args) 691 | if result < 0: dbError(db) 692 | 693 | proc execAffectedRows*(db: DbConn, query: SqlQuery, 694 | args: varargs[string, `$`]): int64 = 695 | ## Executes the query (typically "UPDATE") and returns the 696 | ## number of affected rows. 697 | ## 698 | ## **Examples:** 699 | ## 700 | ## .. code-block:: Nim 701 | ## 702 | ## let db = open("mytest.db", "", "", "") 703 | ## 704 | ## # Records of my_table: 705 | ## # | id | name | 706 | ## # |----|----------| 707 | ## # | 1 | item#1 | 708 | ## # | 2 | item#2 | 709 | ## 710 | ## doAssert db.execAffectedRows(sql"UPDATE my_table SET name = 'TEST'") == 2 711 | ## 712 | ## db.close() 713 | exec(db, query, args) 714 | result = changes(db) 715 | 716 | proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared): int64 717 | {.since: (1, 3).} = 718 | exec(db, stmtName) 719 | result = changes(db) 720 | 721 | proc close*(db: DbConn) = 722 | ## Closes the database connection. 723 | ## 724 | ## **Examples:** 725 | ## 726 | ## .. code-block:: Nim 727 | ## 728 | ## let db = open("mytest.db", "", "", "") 729 | ## db.close() 730 | if sqlite3.close(db) != SQLITE_OK: dbError(db) 731 | 732 | proc open*(connection, user, password, database: string): DbConn = 733 | ## Opens a database connection. Raises a `DbError` exception if the connection 734 | ## could not be established. 735 | ## 736 | ## **Note:** Only the `connection` parameter is used for `sqlite`. 737 | ## 738 | ## **Examples:** 739 | ## 740 | ## .. code-block:: Nim 741 | ## 742 | ## try: 743 | ## let db = open("mytest.db", "", "", "") 744 | ## ## do something... 745 | ## ## db.getAllRows(sql"SELECT * FROM my_table") 746 | ## db.close() 747 | ## except: 748 | ## stderr.writeLine(getCurrentExceptionMsg()) 749 | var db: DbConn 750 | if sqlite3.open(connection, db) == SQLITE_OK: 751 | result = db 752 | else: 753 | dbError(db) 754 | 755 | proc setEncoding*(connection: DbConn, encoding: string): bool = 756 | ## Sets the encoding of a database connection, returns `true` for 757 | ## success, `false` for failure. 758 | ## 759 | ## **Note:** The encoding cannot be changed once it's been set. 760 | ## According to SQLite3 documentation, any attempt to change 761 | ## the encoding after the database is created will be silently 762 | ## ignored. 763 | exec(connection, sql"PRAGMA encoding = ?", [encoding]) 764 | result = connection.getValue(sql"PRAGMA encoding") == encoding 765 | 766 | proc finalize*(sqlPrepared:SqlPrepared) {.discardable, since: (1, 3).} = 767 | discard finalize(sqlPrepared.PStmt) 768 | 769 | template dbBindParamError*(paramIdx: int, val: varargs[untyped]) = 770 | ## Raises a `DbError` exception. 771 | var e: ref DbError 772 | new(e) 773 | e.msg = "error binding param in position " & $paramIdx 774 | raise e 775 | 776 | proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int32) {.since: (1, 3).} = 777 | ## Binds a int32 to the specified paramIndex. 778 | if bind_int(ps.PStmt, paramIdx.int32, val) != SQLITE_OK: 779 | dbBindParamError(paramIdx, val) 780 | 781 | proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int64) {.since: (1, 3).} = 782 | ## Binds a int64 to the specified paramIndex. 783 | if bind_int64(ps.PStmt, paramIdx.int32, val) != SQLITE_OK: 784 | dbBindParamError(paramIdx, val) 785 | 786 | proc bindParam*(ps: SqlPrepared, paramIdx: int, val: int) {.since: (1, 3).} = 787 | ## Binds a int to the specified paramIndex. 788 | when sizeof(int) == 8: 789 | bindParam(ps, paramIdx, val.int64) 790 | else: 791 | bindParam(ps, paramIdx, val.int32) 792 | 793 | proc bindParam*(ps: SqlPrepared, paramIdx: int, val: float64) {.since: (1, 3).} = 794 | ## Binds a 64bit float to the specified paramIndex. 795 | if bind_double(ps.PStmt, paramIdx.int32, val) != SQLITE_OK: 796 | dbBindParamError(paramIdx, val) 797 | 798 | proc bindNull*(ps: SqlPrepared, paramIdx: int) {.since: (1, 3).} = 799 | ## Sets the bindparam at the specified paramIndex to null 800 | ## (default behaviour by sqlite). 801 | if bind_null(ps.PStmt, paramIdx.int32) != SQLITE_OK: 802 | dbBindParamError(paramIdx) 803 | 804 | proc bindParam*(ps: SqlPrepared, paramIdx: int, val: string, copy = true) {.since: (1, 3).} = 805 | ## Binds a string to the specified paramIndex. 806 | ## if copy is true then SQLite makes its own private copy of the data immediately 807 | if bind_text(ps.PStmt, paramIdx.int32, val.cstring, val.len.int32, if copy: SQLITE_TRANSIENT else: SQLITE_STATIC) != SQLITE_OK: 808 | dbBindParamError(paramIdx, val) 809 | 810 | proc bindParam*(ps: SqlPrepared, paramIdx: int,val: openArray[byte], copy = true) {.since: (1, 3).} = 811 | ## binds a blob to the specified paramIndex. 812 | ## if copy is true then SQLite makes its own private copy of the data immediately 813 | let len = val.len 814 | if bind_blob(ps.PStmt, paramIdx.int32, val[0].unsafeAddr, len.int32, if copy: SQLITE_TRANSIENT else: SQLITE_STATIC) != SQLITE_OK: 815 | dbBindParamError(paramIdx, val) 816 | 817 | macro bindParams*(ps: SqlPrepared, params: varargs[untyped]): untyped {.since: (1, 3).} = 818 | let bindParam = bindSym("bindParam", brOpen) 819 | let bindNull = bindSym("bindNull") 820 | let preparedStatement = genSym() 821 | result = newStmtList() 822 | # Store `ps` in a temporary variable. This prevents `ps` from being evaluated every call. 823 | result.add newNimNode(nnkLetSection).add(newIdentDefs(preparedStatement, newEmptyNode(), ps)) 824 | for idx, param in params: 825 | if param.kind != nnkNilLit: 826 | result.add newCall(bindParam, preparedStatement, newIntLitNode idx + 1, param) 827 | else: 828 | result.add newCall(bindNull, preparedStatement, newIntLitNode idx + 1) 829 | 830 | macro untypedLen(args: varargs[untyped]): int = 831 | newLit(args.len) 832 | 833 | template exec*(db: DbConn, stmtName: SqlPrepared, 834 | args: varargs[typed]): untyped = 835 | when untypedLen(args) > 0: 836 | if reset(stmtName.PStmt) != SQLITE_OK: 837 | dbError(db) 838 | if clear_bindings(stmtName.PStmt) != SQLITE_OK: 839 | dbError(db) 840 | stmtName.bindParams(args) 841 | if not tryExec(db, stmtName): dbError(db) 842 | 843 | when not defined(testing) and isMainModule: 844 | var db = open(":memory:", "", "", "") 845 | exec(db, sql"create table tbl1(one varchar(10), two smallint)", []) 846 | exec(db, sql"insert into tbl1 values('hello!',10)", []) 847 | exec(db, sql"insert into tbl1 values('goodbye', 20)", []) 848 | var p1 = db.prepare "create table tbl2(one varchar(10), two smallint)" 849 | exec(db, p1) 850 | finalize(p1) 851 | var p2 = db.prepare "insert into tbl2 values('hello!',10)" 852 | exec(db, p2) 853 | finalize(p2) 854 | var p3 = db.prepare "insert into tbl2 values('goodbye', 20)" 855 | exec(db, p3) 856 | finalize(p3) 857 | #db.query("create table tbl1(one varchar(10), two smallint)") 858 | #db.query("insert into tbl1 values('hello!',10)") 859 | #db.query("insert into tbl1 values('goodbye', 20)") 860 | for r in db.rows(sql"select * from tbl1", []): 861 | echo(r[0], r[1]) 862 | for r in db.instantRows(sql"select * from tbl1", []): 863 | echo(r[0], r[1]) 864 | var p4 = db.prepare "select * from tbl2" 865 | for r in db.rows(p4): 866 | echo(r[0], r[1]) 867 | finalize(p4) 868 | var i5 = 0 869 | var p5 = db.prepare "select * from tbl2" 870 | for r in db.instantRows(p5): 871 | inc i5 872 | echo(r[0], r[1]) 873 | assert i5 == 2 874 | finalize(p5) 875 | 876 | for r in db.rows(sql"select * from tbl2", []): 877 | echo(r[0], r[1]) 878 | for r in db.instantRows(sql"select * from tbl2", []): 879 | echo(r[0], r[1]) 880 | var p6 = db.prepare "select * from tbl2 where one = ? " 881 | p6.bindParams("goodbye") 882 | var rowsP3 = 0 883 | for r in db.rows(p6): 884 | rowsP3 = 1 885 | echo(r[0], r[1]) 886 | assert rowsP3 == 1 887 | finalize(p6) 888 | 889 | var p7 = db.prepare "select * from tbl2 where two=?" 890 | p7.bindParams(20'i32) 891 | when sizeof(int) == 4: 892 | p7.bindParams(20) 893 | var rowsP = 0 894 | for r in db.rows(p7): 895 | rowsP = 1 896 | echo(r[0], r[1]) 897 | assert rowsP == 1 898 | finalize(p7) 899 | 900 | exec(db, sql"CREATE TABLE photos(ID INTEGER PRIMARY KEY AUTOINCREMENT, photo BLOB)") 901 | var p8 = db.prepare "INSERT INTO photos (ID,PHOTO) VALUES (?,?)" 902 | var d = "abcdefghijklmnopqrstuvwxyz" 903 | p8.bindParams(1'i32, "abcdefghijklmnopqrstuvwxyz") 904 | exec(db, p8) 905 | finalize(p8) 906 | var p10 = db.prepare "INSERT INTO photos (ID,PHOTO) VALUES (?,?)" 907 | p10.bindParams(2'i32,nil) 908 | exec(db, p10) 909 | exec( db, p10, 3, nil) 910 | finalize(p10) 911 | for r in db.rows(sql"select * from photos where ID = 1", []): 912 | assert r[1].len == d.len 913 | assert r[1] == d 914 | var i6 = 0 915 | for r in db.rows(sql"select * from photos where ID = 3", []): 916 | i6 = 1 917 | assert i6 == 1 918 | var p9 = db.prepare("select * from photos where PHOTO is ?") 919 | p9.bindParams(nil) 920 | var rowsP2 = 0 921 | for r in db.rows(p9): 922 | rowsP2 = 1 923 | echo(r[0], repr r[1]) 924 | assert rowsP2 == 1 925 | finalize(p9) 926 | 927 | db_sqlite.close(db) 928 | -------------------------------------------------------------------------------- /docs/nimdoc.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stylesheet for use with Docutils/rst2html. 3 | 4 | See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to 5 | customize this style sheet. 6 | 7 | Modified from Chad Skeeters' rst2html-style 8 | https://bitbucket.org/cskeeters/rst2html-style/ 9 | 10 | Modified by Boyd Greenfield and narimiran 11 | */ 12 | 13 | :root { 14 | --primary-background: #fff; 15 | --secondary-background: ghostwhite; 16 | --third-background: #e8e8e8; 17 | --border: #dde; 18 | --text: #222; 19 | --anchor: #07b; 20 | --anchor-focus: #607c9f; 21 | --input-focus: #1fa0eb; 22 | --strong: #3c3c3c; 23 | --hint: #9A9A9A; 24 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1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"); 25 | 26 | --keyword: #5e8f60; 27 | --identifier: #222; 28 | --comment: #484a86; 29 | --operator: #155da4; 30 | --punctuation: black; 31 | --other: black; 32 | --escapeSequence: #c4891b; 33 | --number: #252dbe; 34 | --literal: #a4255b; 35 | --raw-data: #a4255b; 36 | } 37 | 38 | [data-theme="dark"] { 39 | --primary-background: #171921; 40 | --secondary-background: #1e202a; 41 | --third-background: #2b2e3b; 42 | --border: #0e1014; 43 | --text: #fff; 44 | --anchor: #8be9fd; 45 | --anchor-focus: #8be9fd; 46 | --input-focus: #8be9fd; 47 | --strong: #bd93f9; 48 | --hint: #7A7C85; 49 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 50 | 51 | --keyword: #ff79c6; 52 | --identifier: #f8f8f2; 53 | --comment: #6272a4; 54 | --operator: #ff79c6; 55 | --punctuation: #f8f8f2; 56 | --other: #f8f8f2; 57 | --escapeSequence: #bd93f9; 58 | --number: #bd93f9; 59 | --literal: #f1fa8c; 60 | --raw-data: #8be9fd; 61 | } 62 | 63 | .theme-switch-wrapper { 64 | display: flex; 65 | align-items: center; 66 | 67 | em { 68 | margin-left: 10px; 69 | font-size: 1rem; 70 | } 71 | } 72 | .theme-switch { 73 | display: inline-block; 74 | height: 22px; 75 | position: relative; 76 | width: 50px; 77 | } 78 | 79 | .theme-switch input { 80 | display: none; 81 | } 82 | 83 | .slider { 84 | background-color: #ccc; 85 | bottom: 0; 86 | cursor: pointer; 87 | left: 0; 88 | position: absolute; 89 | right: 0; 90 | top: 0; 91 | transition: .4s; 92 | } 93 | 94 | .slider:before { 95 | background-color: #fff; 96 | bottom: 4px; 97 | content: ""; 98 | height: 13px; 99 | left: 4px; 100 | position: absolute; 101 | transition: .4s; 102 | width: 13px; 103 | } 104 | 105 | input:checked + .slider { 106 | background-color: #66bb6a; 107 | } 108 | 109 | input:checked + .slider:before { 110 | transform: translateX(26px); 111 | } 112 | 113 | .slider.round { 114 | border-radius: 17px; 115 | } 116 | 117 | .slider.round:before { 118 | border-radius: 50%; 119 | } 120 | 121 | html { 122 | font-size: 100%; 123 | -webkit-text-size-adjust: 100%; 124 | -ms-text-size-adjust: 100%; } 125 | 126 | body { 127 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 128 | font-weight: 400; 129 | font-size: 1.125em; 130 | line-height: 1.5; 131 | color: var(--text); 132 | background-color: var(--primary-background); } 133 | 134 | /* Skeleton grid */ 135 | .container { 136 | position: relative; 137 | width: 100%; 138 | max-width: 1050px; 139 | margin: 0 auto; 140 | padding: 0; 141 | box-sizing: border-box; } 142 | 143 | .column, 144 | .columns { 145 | width: 100%; 146 | float: left; 147 | box-sizing: border-box; 148 | margin-left: 1%; 149 | } 150 | 151 | .column:first-child, 152 | .columns:first-child { 153 | margin-left: 0; } 154 | 155 | .three.columns { 156 | width: 19%; } 157 | 158 | .nine.columns { 159 | width: 80.0%; } 160 | 161 | .twelve.columns { 162 | width: 100%; 163 | margin-left: 0; } 164 | 165 | @media screen and (max-width: 860px) { 166 | .three.columns { 167 | display: none; 168 | } 169 | .nine.columns { 170 | width: 98.0%; 171 | } 172 | body { 173 | font-size: 1em; 174 | line-height: 1.35; 175 | } 176 | } 177 | 178 | cite { 179 | font-style: italic !important; } 180 | 181 | 182 | /* Nim search input */ 183 | div#searchInputDiv { 184 | margin-bottom: 1em; 185 | } 186 | input#searchInput { 187 | width: 80%; 188 | } 189 | 190 | /* 191 | * Some custom formatting for input forms. 192 | * This also fixes input form colors on Firefox with a dark system theme on Linux. 193 | */ 194 | input { 195 | -moz-appearance: none; 196 | background-color: var(--secondary-background); 197 | color: var(--text); 198 | border: 1px solid var(--border); 199 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 200 | font-size: 0.9em; 201 | padding: 6px; 202 | } 203 | 204 | input:focus { 205 | border: 1px solid var(--input-focus); 206 | box-shadow: 0 0 3px var(--input-focus); 207 | } 208 | 209 | select { 210 | -moz-appearance: none; 211 | background-color: var(--secondary-background); 212 | color: var(--text); 213 | border: 1px solid var(--border); 214 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 215 | font-size: 0.9em; 216 | padding: 6px; 217 | } 218 | 219 | select:focus { 220 | border: 1px solid var(--input-focus); 221 | box-shadow: 0 0 3px var(--input-focus); 222 | } 223 | 224 | /* Docgen styles */ 225 | /* Links */ 226 | a { 227 | color: var(--anchor); 228 | text-decoration: none; 229 | } 230 | 231 | a span.Identifier { 232 | text-decoration: underline; 233 | text-decoration-color: #aab; 234 | } 235 | 236 | a.reference-toplevel { 237 | font-weight: bold; 238 | } 239 | 240 | a.toc-backref { 241 | text-decoration: none; 242 | color: var(--text); } 243 | 244 | a.link-seesrc { 245 | color: #607c9f; 246 | font-size: 0.9em; 247 | font-style: italic; } 248 | 249 | a:hover, 250 | a:focus { 251 | color: var(--anchor-focus); 252 | text-decoration: underline; } 253 | 254 | a:hover span.Identifier { 255 | color: var(--anchor); 256 | } 257 | 258 | 259 | sub, 260 | sup { 261 | position: relative; 262 | font-size: 75%; 263 | line-height: 0; 264 | vertical-align: baseline; } 265 | 266 | sup { 267 | top: -0.5em; } 268 | 269 | sub { 270 | bottom: -0.25em; } 271 | 272 | img { 273 | width: auto; 274 | height: auto; 275 | max-width: 100%; 276 | vertical-align: middle; 277 | border: 0; 278 | -ms-interpolation-mode: bicubic; } 279 | 280 | @media print { 281 | * { 282 | color: black !important; 283 | text-shadow: none !important; 284 | background: transparent !important; 285 | box-shadow: none !important; } 286 | 287 | a, 288 | a:visited { 289 | text-decoration: underline; } 290 | 291 | a[href]:after { 292 | content: " (" attr(href) ")"; } 293 | 294 | abbr[title]:after { 295 | content: " (" attr(title) ")"; } 296 | 297 | .ir a:after, 298 | a[href^="javascript:"]:after, 299 | a[href^="#"]:after { 300 | content: ""; } 301 | 302 | pre, 303 | blockquote { 304 | border: 1px solid #999; 305 | page-break-inside: avoid; } 306 | 307 | thead { 308 | display: table-header-group; } 309 | 310 | tr, 311 | img { 312 | page-break-inside: avoid; } 313 | 314 | img { 315 | max-width: 100% !important; } 316 | 317 | @page { 318 | margin: 0.5cm; } 319 | 320 | h1 { 321 | page-break-before: always; } 322 | 323 | h1.title { 324 | page-break-before: avoid; } 325 | 326 | p, 327 | h2, 328 | h3 { 329 | orphans: 3; 330 | widows: 3; } 331 | 332 | h2, 333 | h3 { 334 | page-break-after: avoid; } 335 | } 336 | 337 | 338 | p { 339 | margin-top: 0.5em; 340 | margin-bottom: 0.5em; 341 | } 342 | 343 | small { 344 | font-size: 85%; } 345 | 346 | strong { 347 | font-weight: 600; 348 | font-size: 0.95em; 349 | color: var(--strong); 350 | } 351 | 352 | em { 353 | font-style: italic; } 354 | 355 | h1 { 356 | font-size: 1.8em; 357 | font-weight: 400; 358 | padding-bottom: .25em; 359 | border-bottom: 6px solid var(--third-background); 360 | margin-top: 2.5em; 361 | margin-bottom: 1em; 362 | line-height: 1.2em; } 363 | 364 | h1.title { 365 | padding-bottom: 1em; 366 | border-bottom: 0px; 367 | font-size: 2.5em; 368 | text-align: center; 369 | font-weight: 900; 370 | margin-top: 0.75em; 371 | margin-bottom: 0em; 372 | } 373 | 374 | h2 { 375 | font-size: 1.3em; 376 | margin-top: 2em; } 377 | 378 | h2.subtitle { 379 | text-align: center; } 380 | 381 | h3 { 382 | font-size: 1.125em; 383 | font-style: italic; 384 | margin-top: 1.5em; } 385 | 386 | h4 { 387 | font-size: 1.125em; 388 | margin-top: 1em; } 389 | 390 | h5 { 391 | font-size: 1.125em; 392 | margin-top: 0.75em; } 393 | 394 | h6 { 395 | font-size: 1.1em; } 396 | 397 | 398 | ul, 399 | ol { 400 | padding: 0; 401 | margin-top: 0.5em; 402 | margin-left: 0.75em; } 403 | 404 | ul ul, 405 | ul ol, 406 | ol ol, 407 | ol ul { 408 | margin-bottom: 0; 409 | margin-left: 1.25em; } 410 | 411 | li { 412 | list-style-type: circle; 413 | } 414 | 415 | ul.simple-boot li { 416 | list-style-type: none; 417 | margin-left: 0em; 418 | margin-bottom: 0.5em; 419 | } 420 | 421 | ol.simple > li, ul.simple > li { 422 | margin-bottom: 0.25em; 423 | margin-left: 0.4em } 424 | 425 | ul.simple.simple-toc > li { 426 | margin-top: 1em; 427 | } 428 | 429 | ul.simple-toc { 430 | list-style: none; 431 | font-size: 0.9em; 432 | margin-left: -0.3em; 433 | margin-top: 1em; } 434 | 435 | ul.simple-toc > li { 436 | list-style-type: none; 437 | } 438 | 439 | ul.simple-toc-section { 440 | list-style-type: circle; 441 | margin-left: 1em; 442 | color: #6c9aae; } 443 | 444 | 445 | ol.arabic { 446 | list-style: decimal; } 447 | 448 | ol.loweralpha { 449 | list-style: lower-alpha; } 450 | 451 | ol.upperalpha { 452 | list-style: upper-alpha; } 453 | 454 | ol.lowerroman { 455 | list-style: lower-roman; } 456 | 457 | ol.upperroman { 458 | list-style: upper-roman; } 459 | 460 | ul.auto-toc { 461 | list-style-type: none; } 462 | 463 | 464 | dl { 465 | margin-bottom: 1.5em; } 466 | 467 | dt { 468 | margin-bottom: -0.5em; 469 | margin-left: 0.0em; } 470 | 471 | dd { 472 | margin-left: 2.0em; 473 | margin-bottom: 3.0em; 474 | margin-top: 0.5em; } 475 | 476 | 477 | hr { 478 | margin: 2em 0; 479 | border: 0; 480 | border-top: 1px solid #aaa; } 481 | 482 | blockquote { 483 | font-size: 0.9em; 484 | font-style: italic; 485 | padding-left: 0.5em; 486 | margin-left: 0; 487 | border-left: 5px solid #bbc; 488 | } 489 | 490 | .pre { 491 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 492 | font-weight: 500; 493 | font-size: 0.85em; 494 | color: var(--text); 495 | background-color: var(--third-background); 496 | padding-left: 3px; 497 | padding-right: 3px; 498 | border-radius: 4px; 499 | } 500 | 501 | pre { 502 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 503 | color: var(--text); 504 | font-weight: 500; 505 | display: inline-block; 506 | box-sizing: border-box; 507 | min-width: 100%; 508 | padding: 0.5em; 509 | margin-top: 0.5em; 510 | margin-bottom: 0.5em; 511 | font-size: 0.85em; 512 | white-space: pre !important; 513 | overflow-y: hidden; 514 | overflow-x: visible; 515 | background-color: var(--secondary-background); 516 | border: 1px solid var(--border); 517 | -webkit-border-radius: 6px; 518 | -moz-border-radius: 6px; 519 | border-radius: 6px; } 520 | 521 | .pre-scrollable { 522 | max-height: 340px; 523 | overflow-y: scroll; } 524 | 525 | 526 | /* Nim line-numbered tables */ 527 | .line-nums-table { 528 | width: 100%; 529 | table-layout: fixed; } 530 | 531 | table.line-nums-table { 532 | border-radius: 4px; 533 | border: 1px solid #cccccc; 534 | background-color: ghostwhite; 535 | border-collapse: separate; 536 | margin-top: 15px; 537 | margin-bottom: 25px; } 538 | 539 | .line-nums-table tbody { 540 | border: none; } 541 | 542 | .line-nums-table td pre { 543 | border: none; 544 | background-color: transparent; } 545 | 546 | .line-nums-table td.blob-line-nums { 547 | width: 28px; } 548 | 549 | .line-nums-table td.blob-line-nums pre { 550 | color: #b0b0b0; 551 | -webkit-filter: opacity(75%); 552 | text-align: right; 553 | border-color: transparent; 554 | background-color: transparent; 555 | padding-left: 0px; 556 | margin-left: 0px; 557 | padding-right: 0px; 558 | margin-right: 0px; } 559 | 560 | 561 | table { 562 | max-width: 100%; 563 | background-color: transparent; 564 | margin-top: 0.5em; 565 | margin-bottom: 1.5em; 566 | border-collapse: collapse; 567 | border-color: var(--third-background); 568 | border-spacing: 0; 569 | font-size: 0.9em; 570 | } 571 | 572 | table th, table td { 573 | padding: 0px 0.5em 0px; 574 | border-color: var(--third-background); 575 | } 576 | 577 | table th { 578 | background-color: var(--third-background); 579 | border-color: var(--third-background); 580 | font-weight: bold; } 581 | 582 | table th.docinfo-name { 583 | background-color: transparent; 584 | } 585 | 586 | table tr:hover { 587 | background-color: var(--third-background); } 588 | 589 | 590 | /* rst2html default used to remove borders from tables and images */ 591 | .borderless, table.borderless td, table.borderless th { 592 | border: 0; } 593 | 594 | table.borderless td, table.borderless th { 595 | /* Override padding for "table.docutils td" with "! important". 596 | The right padding separates the table cells. */ 597 | padding: 0 0.5em 0 0 !important; } 598 | 599 | .first { 600 | /* Override more specific margin styles with "! important". */ 601 | margin-top: 0 !important; } 602 | 603 | .last, .with-subtitle { 604 | margin-bottom: 0 !important; } 605 | 606 | .hidden { 607 | display: none; } 608 | 609 | blockquote.epigraph { 610 | margin: 2em 5em; } 611 | 612 | dl.docutils dd { 613 | margin-bottom: 0.5em; } 614 | 615 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 616 | overflow: hidden; } 617 | 618 | 619 | div.figure { 620 | margin-left: 2em; 621 | margin-right: 2em; } 622 | 623 | div.footer, div.header { 624 | clear: both; 625 | text-align: center; 626 | color: #666; 627 | font-size: smaller; } 628 | 629 | div.footer { 630 | padding-top: 5em; 631 | } 632 | 633 | div.line-block { 634 | display: block; 635 | margin-top: 1em; 636 | margin-bottom: 1em; } 637 | 638 | div.line-block div.line-block { 639 | margin-top: 0; 640 | margin-bottom: 0; 641 | margin-left: 1.5em; } 642 | 643 | div.topic { 644 | margin: 2em; } 645 | 646 | div.search_results { 647 | background-color: antiquewhite; 648 | margin: 3em; 649 | padding: 1em; 650 | border: 1px solid #4d4d4d; 651 | } 652 | 653 | div#global-links ul { 654 | margin-left: 0; 655 | list-style-type: none; 656 | } 657 | 658 | div#global-links > simple-boot { 659 | margin-left: 3em; 660 | } 661 | 662 | hr.docutils { 663 | width: 75%; } 664 | 665 | img.align-left, .figure.align-left, object.align-left { 666 | clear: left; 667 | float: left; 668 | margin-right: 1em; } 669 | 670 | img.align-right, .figure.align-right, object.align-right { 671 | clear: right; 672 | float: right; 673 | margin-left: 1em; } 674 | 675 | img.align-center, .figure.align-center, object.align-center { 676 | display: block; 677 | margin-left: auto; 678 | margin-right: auto; } 679 | 680 | .align-left { 681 | text-align: left; } 682 | 683 | .align-center { 684 | clear: both; 685 | text-align: center; } 686 | 687 | .align-right { 688 | text-align: right; } 689 | 690 | /* reset inner alignment in figures */ 691 | div.align-right { 692 | text-align: inherit; } 693 | 694 | p.attribution { 695 | text-align: right; 696 | margin-left: 50%; } 697 | 698 | p.caption { 699 | font-style: italic; } 700 | 701 | p.credits { 702 | font-style: italic; 703 | font-size: smaller; } 704 | 705 | p.label { 706 | white-space: nowrap; } 707 | 708 | p.rubric { 709 | font-weight: bold; 710 | font-size: larger; 711 | color: maroon; 712 | text-align: center; } 713 | 714 | p.topic-title { 715 | font-weight: bold; } 716 | 717 | pre.address { 718 | margin-bottom: 0; 719 | margin-top: 0; 720 | font: inherit; } 721 | 722 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 723 | margin-left: 2em; 724 | margin-right: 2em; } 725 | 726 | pre.code .ln { 727 | color: grey; } 728 | 729 | /* line numbers */ 730 | pre.code, code { 731 | background-color: #eeeeee; } 732 | 733 | pre.code .comment, code .comment { 734 | color: #5c6576; } 735 | 736 | pre.code .keyword, code .keyword { 737 | color: #3B0D06; 738 | font-weight: bold; } 739 | 740 | pre.code .literal.string, code .literal.string { 741 | color: #0c5404; } 742 | 743 | pre.code .name.builtin, code .name.builtin { 744 | color: #352b84; } 745 | 746 | pre.code .deleted, code .deleted { 747 | background-color: #DEB0A1; } 748 | 749 | pre.code .inserted, code .inserted { 750 | background-color: #A3D289; } 751 | 752 | span.classifier { 753 | font-style: oblique; } 754 | 755 | span.classifier-delimiter { 756 | font-weight: bold; } 757 | 758 | span.option { 759 | white-space: nowrap; } 760 | 761 | span.problematic { 762 | color: #b30000; } 763 | 764 | span.section-subtitle { 765 | /* font-size relative to parent (h1..h6 element) */ 766 | font-size: 80%; } 767 | 768 | span.DecNumber { 769 | color: var(--number); } 770 | 771 | span.BinNumber { 772 | color: var(--number); } 773 | 774 | span.HexNumber { 775 | color: var(--number); } 776 | 777 | span.OctNumber { 778 | color: var(--number); } 779 | 780 | span.FloatNumber { 781 | color: var(--number); } 782 | 783 | span.Identifier { 784 | color: var(--identifier); } 785 | 786 | span.Keyword { 787 | font-weight: 600; 788 | color: var(--keyword); } 789 | 790 | span.StringLit { 791 | color: var(--literal); } 792 | 793 | span.LongStringLit { 794 | color: var(--literal); } 795 | 796 | span.CharLit { 797 | color: var(--literal); } 798 | 799 | span.EscapeSequence { 800 | color: var(--escapeSequence); } 801 | 802 | span.Operator { 803 | color: var(--operator); } 804 | 805 | span.Punctuation { 806 | color: var(--punctuation); } 807 | 808 | span.Comment, span.LongComment { 809 | font-style: italic; 810 | font-weight: 400; 811 | color: var(--comment); } 812 | 813 | span.RegularExpression { 814 | color: darkviolet; } 815 | 816 | span.TagStart { 817 | color: darkviolet; } 818 | 819 | span.TagEnd { 820 | color: darkviolet; } 821 | 822 | span.Key { 823 | color: #252dbe; } 824 | 825 | span.Value { 826 | color: #252dbe; } 827 | 828 | span.RawData { 829 | color: var(--raw-data); } 830 | 831 | span.Assembler { 832 | color: #252dbe; } 833 | 834 | span.Preprocessor { 835 | color: #252dbe; } 836 | 837 | span.Directive { 838 | color: #252dbe; } 839 | 840 | span.Command, span.Rule, span.Hyperlink, span.Label, span.Reference, 841 | span.Other { 842 | color: var(--other); } 843 | 844 | /* Pop type, const, proc, and iterator defs in nim def blocks */ 845 | dt pre > span.Identifier, dt pre > span.Operator { 846 | color: var(--identifier); 847 | font-weight: 700; } 848 | 849 | dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, 850 | dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { 851 | color: var(--identifier); 852 | font-weight: inherit; } 853 | 854 | /* Nim sprite for the footer (taken from main page favicon) */ 855 | .nim-sprite { 856 | display: inline-block; 857 | width: 51px; 858 | height: 14px; 859 | background-position: 0 0; 860 | background-size: 51px 14px; 861 | -webkit-filter: opacity(50%); 862 | background-repeat: no-repeat; 863 | background-image: var(--nim-sprite-base64); 864 | margin-bottom: 5px; } 865 | 866 | span.pragmadots { 867 | /* Position: relative frees us up to make the dots 868 | look really nice without fucking up the layout and 869 | causing bulging in the parent container */ 870 | position: relative; 871 | /* 1px down looks slightly nicer */ 872 | top: 1px; 873 | padding: 2px; 874 | background-color: var(--third-background); 875 | border-radius: 4px; 876 | margin: 0 2px; 877 | cursor: pointer; 878 | font-size: 0.8em; 879 | } 880 | 881 | span.pragmadots:hover { 882 | background-color: var(--hint); 883 | } 884 | span.pragmawrap { 885 | display: none; 886 | } 887 | 888 | span.attachedType { 889 | display: none; 890 | visibility: hidden; 891 | } -------------------------------------------------------------------------------- /memlib.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Memlib - Load Windows DLL from memory 4 | # (c) Copyright 2021-2022 Ward 5 | # 6 | #==================================================================== 7 | 8 | ## This module is designed to be a drop-in replacement for `dynlib pragma` 9 | ## and `dynlib module` in Windows. The main part of this module is a pure nim 10 | ## implementation of the famous MemoryModule library. 11 | ## So that the we can embed all DLLs into the main EXE file. 12 | 13 | import tables, macros, md5, locks, os, terminal, strutils, dynlib 14 | import winim/lean, minhook 15 | import memlib/private/sharedseq 16 | 17 | when (compiles do: import std/exitprocs): 18 | import std/exitprocs 19 | proc addQuitProc(cl: proc() {.noconv.}) = addExitProc(cl) 20 | 21 | template atexit(body: untyped): untyped = 22 | addQuitProc proc () {.noconv.} = 23 | body 24 | 25 | when not defined(windows): 26 | {.fatal: "Only implementation for Windows".} 27 | 28 | type 29 | DllEntryProc = proc (dll: HINSTANCE, reason: DWORD, reserved: pointer): bool {.stdcall, gcsafe.} 30 | ExeEntryProc = proc (): int {.stdcall, gcsafe.} 31 | 32 | NameOrdinal = object 33 | cname: LPCSTR 34 | ordinal: int 35 | 36 | MemoryModuleObj = object 37 | headers: PIMAGE_NT_HEADERS 38 | codeBase: pointer 39 | initialized: bool 40 | isDll: bool 41 | isRelocated: bool 42 | entry: pointer 43 | modules: SharedSeq[HMODULE] 44 | symbols: SharedSeq[NameOrdinal] 45 | hash: MD5Digest 46 | reference: int 47 | name: string 48 | 49 | MemoryModule* = ptr MemoryModuleObj 50 | ## Pointer to a MemoryModule object. 51 | 52 | DllContent* = distinct string 53 | ## Represents DLL file in binary format. 54 | 55 | var 56 | memLibs: SharedSeq[MemoryModule] 57 | rtLibs: SharedSeq[HMODULE] 58 | gLock: Lock 59 | hookEnabled: bool 60 | 61 | proc `[]`[T](x: T, U: typedesc): U {.inline.} = 62 | ## syntax sugar for cast 63 | when sizeof(U) > sizeof(x): 64 | when sizeof(x) == 1: cast[U](cast[uint8](x).uint64) 65 | elif sizeof(x) == 2: cast[U](cast[uint16](x).uint64) 66 | elif sizeof(x) == 4: cast[U](cast[uint32](x).uint64) 67 | else: cast[U](cast[uint64](x)) 68 | else: 69 | cast[U](x) 70 | 71 | proc `{}`[T](x: T, U: typedesc): U {.inline.} = 72 | ## syntax sugar for zero extends cast 73 | when sizeof(x) == 1: x[uint8][U] 74 | elif sizeof(x) == 2: x[uint16][U] 75 | elif sizeof(x) == 4: x[uint32][U] 76 | elif sizeof(x) == 8: x[uint64][U] 77 | else: {.fatal.} 78 | 79 | proc `{}`[T](p: T, x: SomeInteger): T {.inline.} = 80 | ## syntax sugar for pointer (or any other type) arithmetics 81 | (p[int] +% x{int})[T] 82 | 83 | template `++`[T](p: var ptr T) = 84 | ## syntax sugar for pointer increment 85 | p = cast[ptr T](p[int] +% sizeof(T)) 86 | 87 | template alignUp[T: uint|pointer](value: T, alignment: uint): T = 88 | cast[T]((cast[uint](value) + alignment - 1) and not (alignment - 1)) 89 | 90 | template alignDown[T: uint|pointer](value: T, alignment: uint): T = 91 | cast[T](cast[uint](value) and not (alignment - 1)) 92 | 93 | template MAKEINTRESOURCE(i: untyped): untyped = (i and 0xffff)[LPTSTR] 94 | 95 | iterator sections(ntHeader: PIMAGE_NT_HEADERS): var IMAGE_SECTION_HEADER = 96 | let sections = IMAGE_FIRST_SECTION(ntHeader)[ptr UncheckedArray[IMAGE_SECTION_HEADER]] 97 | for i in 0 ..< int ntHeader.FileHeader.NumberOfSections: 98 | yield sections[i] 99 | 100 | proc getPageSize(): uint {.inline.} = 101 | var sysInfo: SYSTEM_INFO 102 | GetNativeSystemInfo(sysInfo) 103 | return sysInfo.dwPageSize{uint} 104 | 105 | proc symErrorMessage(sym: LPCSTR): string = 106 | let msg = if HIWORD(sym{uint}) == 0: "ordinal " & $(sym{uint}) else: "symbol " & $sym 107 | result = "Could not find " & msg 108 | 109 | proc validate(data: pointer, size: int): MD5Digest = 110 | if data == nil or size < sizeof(IMAGE_DOS_HEADER): 111 | raise newException(LibraryError, "Invalid data") 112 | 113 | let dosHeader = data[PIMAGE_DOS_HEADER] 114 | 115 | if dosHeader.e_magic != IMAGE_DOS_SIGNATURE: 116 | raise newException(LibraryError, "Invalid data") 117 | 118 | if size < dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS): 119 | raise newException(LibraryError, "Invalid data") 120 | 121 | let ntHeader = data{dosHeader.e_lfanew}[PIMAGE_NT_HEADERS] 122 | if ntHeader.Signature != IMAGE_NT_SIGNATURE: 123 | raise newException(LibraryError, "Invalid data") 124 | 125 | when defined(cpu64): 126 | if ntHeader.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64: 127 | raise newException(LibraryError, "Incorrect architecture") 128 | else: 129 | if ntHeader.FileHeader.Machine != IMAGE_FILE_MACHINE_I386: 130 | raise newException(LibraryError, "Incorrect architecture") 131 | 132 | # Only support section alignments that are a multiple of 2 133 | if (ntHeader.OptionalHeader.SectionAlignment and 1) != 0: 134 | raise newException(LibraryError, "Invalid data") 135 | 136 | if size < ntHeader.OptionalHeader.SizeOfHeaders: 137 | raise newException(LibraryError, "Invalid data") 138 | 139 | var ctx: MD5Context 140 | ctx.md5Init() 141 | ctx.md5Update(data[cstring], size) 142 | ctx.md5Final(result) 143 | 144 | proc newMemoryModule(): MemoryModule = 145 | result = createShared(MemoryModuleObj) 146 | if result == nil: 147 | raise newException(LibraryError, "Out of memory") 148 | 149 | result.modules = newSharedSeq[HMODULE]() 150 | result.symbols = newSharedSeq[NameOrdinal]() 151 | 152 | proc dealloc(lib: MemoryModule) {.inline.} = 153 | deallocShared(lib) 154 | 155 | proc allocMemory(lib: MemoryModule, ntHeader: PIMAGE_NT_HEADERS, pageSize: uint) = 156 | var lastSectionEnd = 0'u 157 | for section in ntHeader.sections: 158 | let endOfSection = section.VirtualAddress{uint}{ 159 | if section.SizeOfRawData == 0: ntHeader.OptionalHeader.SectionAlignment 160 | else: section.SizeOfRawData 161 | } 162 | 163 | if endOfSection > lastSectionEnd: 164 | lastSectionEnd = endOfSection 165 | 166 | let alignedImageSize = alignUp(ntHeader.OptionalHeader.SizeOfImage{uint}, pageSize) 167 | if alignedImageSize == 0 or alignedImageSize != alignUp(lastSectionEnd, pageSize): 168 | raise newException(LibraryError, "Invalid data") 169 | 170 | # reserve memory for image of library 171 | var codeBase = VirtualAlloc(ntHeader.OptionalHeader.ImageBase[pointer], 172 | alignedImageSize[SIZE_T], MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE) 173 | 174 | if codeBase == nil: 175 | # try to allocate memory at arbitrary position 176 | codeBase = VirtualAlloc(nil, alignedImageSize[SIZE_T], MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE) 177 | if codeBase == nil: 178 | raise newException(LibraryError, "Out of memory") 179 | 180 | when defined(cpu64): 181 | var blocked: seq[pointer] 182 | try: 183 | # Memory block may not span 4 GB boundaries 184 | while (codeBase[uint] shr 32) < ((codeBase[uint] + alignedImageSize) shr 32): 185 | blocked.add codeBase 186 | codeBase = VirtualAlloc(nil, alignedImageSize[SIZE_T], MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE) 187 | 188 | if codeBase == nil: 189 | raise newException(LibraryError, "Out of memory") 190 | 191 | finally: 192 | for p in blocked: 193 | VirtualFree(p, 0, MEM_RELEASE) 194 | 195 | lib.codeBase = codeBase 196 | 197 | proc copyHeaders(lib: MemoryModule, dosHeader: PIMAGE_DOS_HEADER, ntHeader: PIMAGE_NT_HEADERS) = 198 | # commit memory for headers 199 | let headers = VirtualAlloc(lib.codeBase, ntHeader.OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE) 200 | 201 | copyMem(headers, dosHeader, ntHeader.OptionalHeader.SizeOfHeaders) 202 | lib.headers = headers{dosHeader.e_lfanew}[PIMAGE_NT_HEADERS] 203 | 204 | lib.headers.OptionalHeader.ImageBase = lib.codeBase[ntHeader.OptionalHeader.ImageBase.type] 205 | lib.isDll = (ntHeader.FileHeader.Characteristics and IMAGE_FILE_DLL) != 0 206 | 207 | proc copySections(lib: MemoryModule, data: pointer, size: int, ntHeader: PIMAGE_NT_HEADERS) = 208 | let codeBase = lib.codeBase 209 | 210 | for section in lib.headers.sections: 211 | if section.SizeOfRawData == 0: 212 | # section doesn't contain data in the dll itself, but may define uninitialized data 213 | let sectionSize = ntHeader.OptionalHeader.SectionAlignment 214 | if sectionSize > 0: 215 | var dest = VirtualAlloc(codeBase{section.VirtualAddress}, sectionSize, MEM_COMMIT, PAGE_READWRITE) 216 | if dest == nil: 217 | raise newException(LibraryError, "Out of memory") 218 | 219 | # Always use position from file to support alignments smaller¡¡ 220 | # than page size (allocation above will align to page size). 221 | dest = codeBase{section.VirtualAddress} 222 | 223 | # NOTE: On 64bit systems we truncate to 32bit here but expand 224 | # again later when "PhysicalAddress" is used. 225 | section.Misc.PhysicalAddress = dest[DWORD] 226 | zeroMem(dest, sectionSize) 227 | 228 | continue 229 | 230 | if size <% (section.PointerToRawData +% section.SizeOfRawData): 231 | raise newException(LibraryError, "Invalid data") 232 | 233 | # commit memory block and copy data from dll 234 | var dest = VirtualAlloc(codeBase{section.VirtualAddress}, section.SizeOfRawData, MEM_COMMIT, PAGE_READWRITE) 235 | if dest == nil: 236 | raise newException(LibraryError, "Out of memory") 237 | 238 | # Always use position from file to support alignments smaller 239 | # than page size (allocation above will align to page size). 240 | dest = codeBase{section.VirtualAddress} 241 | 242 | # NOTE: On 64bit systems we truncate to 32bit here but expand 243 | # again later when "PhysicalAddress" is used. 244 | section.Misc.PhysicalAddress = dest[DWORD] 245 | copyMem(dest, data{section.PointerToRawData}, section.SizeOfRawData) 246 | 247 | proc performBaseRelocation(lib: MemoryModule, ntHeader: PIMAGE_NT_HEADERS) = 248 | 249 | iterator relocations(codeBase: pointer, directory: IMAGE_DATA_DIRECTORY): PIMAGE_BASE_RELOCATION = 250 | if directory.Size != 0: 251 | var relocation = codeBase{directory.VirtualAddress}[PIMAGE_BASE_RELOCATION] 252 | while relocation.VirtualAddress != 0: 253 | yield relocation 254 | relocation = relocation{relocation.SizeOfBlock} 255 | 256 | iterator pairs(relocation: PIMAGE_BASE_RELOCATION): (int, int) = 257 | let info = relocation{IMAGE_SIZEOF_BASE_RELOCATION}[ptr UncheckedArray[uint16]] 258 | for i in 0 ..< (relocation.SizeOfBlock{uint} - IMAGE_SIZEOF_BASE_RELOCATION) div 2: 259 | let 260 | kind = info[i] shr 12 # the upper 4 bits define the type of relocation 261 | off = info[i] and 0xfff # the lower 12 bits define the offset 262 | 263 | yield (int kind, int off) 264 | 265 | let delta = int(lib.headers.OptionalHeader.ImageBase - ntHeader.OptionalHeader.ImageBase) 266 | if delta != 0: 267 | let 268 | codeBase = lib.codeBase 269 | directory = lib.headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] 270 | 271 | if directory.Size == 0: 272 | lib.isRelocated = false 273 | return 274 | 275 | for relocation in codeBase.relocations(directory): 276 | let dest = codeBase{relocation.VirtualAddress} 277 | 278 | for kind, off in relocation: 279 | if kind == IMAGE_REL_BASED_HIGHLOW: 280 | let p = dest{off}[ptr int32] 281 | p[] = p[]{delta} 282 | 283 | when defined(cpu64): 284 | if kind == IMAGE_REL_BASED_DIR64: 285 | let p = dest{off}[ptr int64] 286 | p[] = p[]{delta} 287 | 288 | lib.isRelocated = true 289 | 290 | proc buildImportTable(lib: MemoryModule) = 291 | 292 | iterator descriptors(codeBase: pointer, directory: IMAGE_DATA_DIRECTORY): IMAGE_IMPORT_DESCRIPTOR = 293 | if directory.Size != 0: 294 | var desc = codeBase{directory.VirtualAddress}[PIMAGE_IMPORT_DESCRIPTOR] 295 | while (IsBadReadPtr(desc, UINT_PTR sizeof(IMAGE_IMPORT_DESCRIPTOR)) == 0) and (desc.Name != 0): 296 | yield desc[] 297 | ++desc 298 | 299 | iterator refs(codeBase: pointer, desc: IMAGE_IMPORT_DESCRIPTOR): (ptr pointer, ptr pointer) = 300 | var 301 | funcRef = codeBase{desc.FirstThunk}[ptr pointer] 302 | thunkRef = 303 | if desc.OriginalFirstThunk != 0: codeBase{desc.OriginalFirstThunk}[ptr pointer] 304 | else: funcRef 305 | 306 | while thunkRef[] != nil: 307 | yield (thunkRef, funcRef) 308 | ++thunkRef 309 | ++funcRef 310 | 311 | var 312 | codeBase = lib.codeBase 313 | directory = lib.headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] 314 | 315 | for desc in codeBase.descriptors(directory): 316 | let 317 | cname = codeBase{desc.Name}[LPCSTR] 318 | handle = LoadLibraryA(cname) 319 | 320 | if handle == 0: 321 | raise newException(LibraryError, $cname & " not found") 322 | 323 | lib.modules.add handle 324 | 325 | for thunkRef, funcRef in refs(codeBase, desc): 326 | if IMAGE_SNAP_BY_ORDINAL(thunkRef[][int]): 327 | let ordinal = IMAGE_ORDINAL(thunkRef[][int]) 328 | 329 | funcRef[] = GetProcAddress(handle, ordinal[LPCSTR]) 330 | if funcRef[] == nil: 331 | raise newException(LibraryError, $ordinal & " not found in " & $cname) 332 | 333 | else: 334 | let 335 | thunkData = codeBase{thunkRef[][int]}[PIMAGE_IMPORT_BY_NAME] 336 | cfunc = thunkData.Name[0].addr[LPCSTR] 337 | 338 | funcRef[] = GetProcAddress(handle, cfunc) 339 | if funcRef[] == nil: 340 | raise newException(LibraryError, $cfunc & " not found in " & $cname) 341 | 342 | proc finalizeSections(lib: MemoryModule, pageSize: uint) = 343 | type 344 | SectionData = object 345 | address: pointer 346 | alignedAddr: pointer 347 | size: uint 348 | characteristics: DWORD 349 | last: bool 350 | 351 | proc realSize(lib: MemoryModule, section: IMAGE_SECTION_HEADER): uint = 352 | result = section.SizeOfRawData{uint} 353 | if result == 0: 354 | if (section.Characteristics and IMAGE_SCN_CNT_INITIALIZED_DATA) != 0: 355 | result = lib.headers.OptionalHeader.SizeOfInitializedData{uint} 356 | elif (section.Characteristics and IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0: 357 | result = lib.headers.OptionalHeader.SizeOfUninitializedData{uint} 358 | 359 | proc finalizeSection(lib: MemoryModule, data: SectionData) = 360 | if data.size == 0: 361 | return 362 | 363 | if (data.characteristics and IMAGE_SCN_MEM_DISCARDABLE) != 0: 364 | if data.address == data.alignedAddr and 365 | (data.last or 366 | lib.headers.OptionalHeader.SectionAlignment{uint} == pageSize or 367 | (data.size mod pageSize) == 0 368 | ): 369 | # Only allowed to decommit whole pages 370 | VirtualFree(data.address, data.size[SIZE_T], MEM_DECOMMIT) 371 | return 372 | 373 | const flags = [ 374 | [[PAGE_NOACCESS, PAGE_WRITECOPY], 375 | [PAGE_READONLY, PAGE_READWRITE]], 376 | [[PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY], 377 | [PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE]] 378 | ] 379 | 380 | # determine protection flags based on characteristics 381 | var 382 | executable = int((data.characteristics and IMAGE_SCN_MEM_EXECUTE) != 0) 383 | readable = int((data.characteristics and IMAGE_SCN_MEM_READ) != 0) 384 | writeable = int((data.characteristics and IMAGE_SCN_MEM_WRITE) != 0) 385 | protect = DWORD flags[executable][readable][writeable] 386 | oldProtect: DWORD 387 | 388 | if (data.characteristics and IMAGE_SCN_MEM_NOT_CACHED) != 0: 389 | protect = protect or PAGE_NOCACHE 390 | 391 | if VirtualProtect(data.address, data.size[SIZE_T], protect, &oldProtect) == 0: 392 | raise newException(LibraryError, "protecting page failed") 393 | 394 | when defined(cpu64): 395 | # "PhysicalAddress" might have been truncated to 32bit above, expand to 64bits again. 396 | let imageOffset = lib.headers.OptionalHeader.ImageBase and 0xffffffff00000000 397 | else: 398 | const imageOffset = 0 399 | 400 | var 401 | firstSection = true 402 | data: SectionData 403 | 404 | for section in lib.headers.sections: 405 | if firstSection: 406 | data.address = (section.Misc.PhysicalAddress{uint} or imageOffset{uint})[pointer] 407 | data.alignedAddr = alignDown(data.address, pageSize) 408 | data.size = lib.realSize(section) 409 | data.characteristics = section.Characteristics 410 | data.last = false 411 | firstSection = false 412 | continue 413 | 414 | let 415 | address = (section.Misc.PhysicalAddress{uint} or imageOffset{uint})[pointer] 416 | alignedAddr = alignDown(address, pageSize) 417 | size = lib.realSize(section) 418 | 419 | # Combine access flags of all sections that share a page 420 | if data.alignedAddr == alignedAddr or data.address{data.size} > alignedAddr: 421 | let combine = data.characteristics or section.Characteristics 422 | if (section.Characteristics and IMAGE_SCN_MEM_DISCARDABLE) == 0 or 423 | (data.characteristics and IMAGE_SCN_MEM_DISCARDABLE) == 0: 424 | data.characteristics = combine and (not IMAGE_SCN_MEM_DISCARDABLE) 425 | else: 426 | data.characteristics = combine 427 | 428 | data.size = address{size}[uint] - data.address[uint] 429 | continue 430 | 431 | lib.finalizeSection(data) 432 | data.address = address 433 | data.alignedAddr = alignedAddr 434 | data.size = size 435 | data.characteristics = section.Characteristics 436 | 437 | data.last = true 438 | lib.finalizeSection(data) 439 | 440 | proc executeTLS(lib: MemoryModule) = 441 | type 442 | PIMAGE_TLS_CALLBACK = proc (DllHandle: PVOID, Reason: DWORD, Reserved: PVOID) {.stdcall, gcsafe.} 443 | 444 | let 445 | codeBase = lib.codeBase 446 | directory = lib.headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS] 447 | 448 | if directory.VirtualAddress != 0: 449 | var 450 | tls = codeBase{directory.VirtualAddress}[PIMAGE_TLS_DIRECTORY] 451 | callback = tls.AddressOfCallBacks[ptr PIMAGE_TLS_CALLBACK] 452 | 453 | if callback != nil: 454 | while callback[] != nil: 455 | callback[](codeBase, DLL_PROCESS_ATTACH, nil) 456 | ++callback 457 | 458 | proc addFunctionTable(lib: MemoryModule) = 459 | when defined(cpu64): 460 | let 461 | codeBase = lib.codeBase 462 | directory = lib.headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION] 463 | 464 | var 465 | funcTablePtr = codeBase{directory.VirtualAddress}[PRUNTIME_FUNCTION] 466 | 467 | RtlAddFunctionTable(funcTablePtr, (directory.Size div sizeof(RUNTIME_FUNCTION).DWORD), codeBase[DWORD64]) 468 | 469 | proc initialize(lib: MemoryModule) = 470 | lib.entry = lib.codeBase{lib.headers.OptionalHeader.AddressOfEntryPoint} 471 | if lib.entry != nil and lib.isDll: 472 | let ok = lib.entry[DllEntryProc](lib.codeBase[HINSTANCE], DLL_PROCESS_ATTACH, nil) 473 | if not ok: 474 | raise newException(LibraryError, "Initialize failed") 475 | 476 | lib.initialized = true 477 | 478 | proc gatherSymbols(lib: MemoryModule) = 479 | 480 | iterator entries(codeBase: pointer, exports: PIMAGE_EXPORT_DIRECTORY): (LPCSTR, int) = 481 | var 482 | nameRef = codeBase{exports.AddressOfNames}[ptr uint32] 483 | ordinal = codeBase{exports.AddressOfNameOrdinals}[ptr uint16] 484 | 485 | for i in 0 ..< exports.NumberOfNames: 486 | let 487 | name = codeBase{nameRef[]}[LPCSTR] 488 | index = int ordinal[] 489 | 490 | yield (name, index) 491 | 492 | ++nameRef 493 | ++ordinal 494 | 495 | let 496 | codeBase = lib.codeBase 497 | directory = lib.headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] 498 | 499 | if directory.Size != 0: 500 | let exports = codeBase{directory.VirtualAddress}[PIMAGE_EXPORT_DIRECTORY] 501 | for cname, ordinal in codeBase.entries(exports): 502 | lib.symbols.add NameOrdinal(cname: cname, ordinal: ordinal) 503 | 504 | lib.symbols.sort() do (x, y: NameOrdinal) -> int: 505 | result = lstrcmpA(x.cname, y.cname) 506 | 507 | proc findSymbol(lib: MemoryModule, name: LPCSTR): pointer = 508 | block main: 509 | if lib == nil: break main 510 | 511 | let 512 | codeBase = lib.codeBase 513 | directory = lib.headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] 514 | 515 | if directory.Size == 0: break main 516 | 517 | let exports = codeBase{directory.VirtualAddress}[PIMAGE_EXPORT_DIRECTORY] 518 | if exports.NumberOfFunctions == 0: break main 519 | 520 | var index = 0 521 | if HIWORD(name{uint}) == 0: 522 | if LOWORD(name{uint})[DWORD] <% exports.Base: break main 523 | index = LOWORD(name{uint})[DWORD] -% exports.Base 524 | 525 | else: 526 | var found = lib.symbols.binarySearch(name) do (x: NameOrdinal, y: LPCSTR) -> int: 527 | result = lstrcmpA(x.cname, y) 528 | 529 | if found < 0: break main 530 | index = lib.symbols[found].ordinal 531 | 532 | if index >% exports.NumberOfFunctions: break main 533 | 534 | let rva = codeBase{exports.AddressOfFunctions}{index * 4}[ptr uint32][] 535 | return codeBase{rva} 536 | 537 | raise newException(LibraryError, symErrorMessage(name)) 538 | 539 | proc register(lib: MemoryModule, hash: MD5Digest) = 540 | withLock(gLock): 541 | lib.hash = hash 542 | lib.reference = 1 543 | memLibs.add lib 544 | 545 | proc unregister(lib: MemoryModule) = 546 | withLock(gLock): 547 | let found = memLibs.find lib 548 | if found >= 0: 549 | memLibs.del found 550 | 551 | proc reloadCheck(hash: MD5Digest, lib: var MemoryModule): bool = 552 | withLock(gLock): 553 | for i in 0 ..< memLibs.len: 554 | if memLibs[i].hash == hash: 555 | memLibs[i].reference.inc 556 | lib = memLibs[i] 557 | return true 558 | 559 | proc unloadLib(lib: MemoryModule, force: bool) = 560 | if lib != nil: 561 | if lib.reference > 1 and not force: 562 | lib.reference.dec 563 | return 564 | 565 | if lib.entry != nil and lib.isDll and lib.initialized: 566 | discard lib.entry[DllEntryProc](lib.codeBase[HINSTANCE], DLL_PROCESS_DETACH, nil) 567 | 568 | lib.unregister() 569 | 570 | for handle in lib.modules: 571 | FreeLibrary(handle) 572 | 573 | lib.modules.dealloc() 574 | lib.symbols.dealloc() 575 | 576 | if lib.codeBase != nil: 577 | VirtualFree(lib.codeBase, 0, MEM_RELEASE) 578 | 579 | lib.name = "" 580 | lib.dealloc() 581 | 582 | proc loadLib(data: pointer, size: int): MemoryModule = 583 | try: 584 | let hash = validate(data, size) 585 | if reloadCheck(hash, result): return 586 | 587 | let 588 | pageSize = getPageSize() 589 | dosHeader = data[PIMAGE_DOS_HEADER] 590 | ntHeader = data{dosHeader.e_lfanew}[PIMAGE_NT_HEADERS] 591 | 592 | result = newMemoryModule() 593 | result.allocMemory(ntHeader, pageSize) 594 | result.copyHeaders(dosHeader, ntHeader) 595 | result.copySections(data, size, ntHeader) 596 | result.performBaseRelocation(ntHeader) 597 | result.buildImportTable() 598 | result.finalizeSections(pageSize) 599 | result.executeTLS() 600 | result.addFunctionTable() 601 | result.initialize() 602 | result.gatherSymbols() 603 | result.register(hash) 604 | 605 | except: 606 | result.unloadLib(force=true) 607 | raise getCurrentException()[ref LibraryError] 608 | 609 | proc globalExit() = 610 | for lib in @memLibs: 611 | lib.unloadLib(force=true) 612 | 613 | proc globalInit() = 614 | gLock.initLock() 615 | memLibs = newSharedSeq[MemoryModule]() 616 | rtLibs = newSharedSeq[HMODULE]() 617 | 618 | once: 619 | globalInit() 620 | 621 | atexit: 622 | globalExit() 623 | 624 | 625 | # exported procs from here 626 | 627 | proc checkedLoadLib*(data: DllContent): MemoryModule {.inline.} = 628 | ## Loads a DLL from memory. Raise `LibraryError` if the DLL could not be loaded. 629 | result = loadLib(&data.string, data.string.len) 630 | 631 | proc checkedLoadLib*(data: openarray[byte|char]): MemoryModule {.inline.} = 632 | ## Loads a DLL from memory. Raise `LibraryError` if the DLL could not be loaded. 633 | result = loadLib(unsafeaddr data[0], data.len) 634 | 635 | proc checkedSymAddr*(lib: MemoryModule, name: string|LPCSTR): pointer {.inline.} = 636 | ## Retrieves the address of a procedure from DLL by name. 637 | ## Raise `LibraryError` if the symbol could not be found. 638 | result = lib.findSymbol(name) 639 | 640 | proc checkedSymAddr*(lib: MemoryModule, ordinal: range[0..65535]): pointer {.inline.} = 641 | ## Retrieves the address of a procedure from DLL by ordinal. 642 | ## Raise `LibraryError` if the ordinal out of range. 643 | result = lib.findSymbol(ordinal[LPCSTR]) 644 | 645 | proc loadLib*(data: DllContent): MemoryModule {.inline.} = 646 | ## Loads a DLL from memory. Returns `nil` if the DLL could not be loaded. 647 | try: result = checkedLoadLib(data) 648 | except: result = nil 649 | 650 | proc loadLib*(data: openarray[byte|char]): MemoryModule {.inline.} = 651 | ## Loads a DLL from memory. Returns `nil` if the DLL could not be loaded. 652 | try: result = checkedLoadLib(data) 653 | except: result = nil 654 | 655 | proc symAddr*(lib: MemoryModule, name: string|LPCSTR): pointer {.inline.} = 656 | ## Retrieves the address of a procedure from DLL by name. 657 | ## Returns `nil` if the symbol could not be found. 658 | try: result = checkedSymAddr(lib, name) 659 | except: result = nil 660 | 661 | proc symAddr*(lib: MemoryModule, ordinal: range[0..65535]): pointer {.inline.} = 662 | ## Retrieves the address of a procedure from DLL by ordinal. 663 | ## Returns `nil` if the ordinal out of range. 664 | try: result = checkedSymAddr(lib, ordinal) 665 | except: result = nil 666 | 667 | proc unloadLib*(lib: MemoryModule) {.inline.} = 668 | ## Unloads the DLL. 669 | lib.unloadLib(force = false) 670 | 671 | proc run*(lib: MemoryModule): int {.discardable.} = 672 | ## Execute entry point (EXE only). The entry point can only be executed 673 | ## if the EXE has been loaded to the correct base address or it could 674 | ## be relocated (i.e. relocation information have not been stripped by 675 | ## the linker). 676 | ## 677 | ## Important: calling this function will not return, i.e. once the loaded 678 | ## EXE finished running, the process will terminate. 679 | ## 680 | ## Raise `LibraryError` if the entry point could not be executed. 681 | assert lib != nil 682 | 683 | if lib.codeBase == nil or lib.entry == nil: 684 | raise newException(LibraryError, "No entry point") 685 | 686 | if lib.isDll: 687 | raise newException(LibraryError, "Cannot run a DLL file") 688 | 689 | if not lib.isRelocated: 690 | raise newException(LibraryError, "Cannot run without relocation") 691 | 692 | result = lib.entry[ExeEntryProc]() 693 | 694 | # for resources 695 | 696 | proc findResource*(lib: HMODULE, name: LPCTSTR, typ: LPCTSTR, lang: WORD = 0): HRSRC = 697 | ## Find the location of a resource with the specified type, name and language. 698 | 699 | proc RtlImageNtHeader(base: HMODULE): PIMAGE_NT_HEADERS {.stdcall, importc, dynlib: "ntdll".} 700 | 701 | proc searchResourceEntry(root: pointer, resources: PIMAGE_RESOURCE_DIRECTORY, key: LPCTSTR): PIMAGE_RESOURCE_DIRECTORY_ENTRY = 702 | let entries = resources{sizeof IMAGE_RESOURCE_DIRECTORY}[ptr UncheckedArray[IMAGE_RESOURCE_DIRECTORY_ENTRY]] 703 | 704 | if IS_INTRESOURCE(key): 705 | let 706 | first = resources.NumberOfNamedEntries.int 707 | last = first + resources.NumberOfIdEntries.int - 1 708 | 709 | let found = binarySearch(entries.toOpenArray(first, last), 710 | cast[WORD](key)) do (x: IMAGE_RESOURCE_DIRECTORY_ENTRY, y: WORD) -> int: 711 | 712 | result = system.cmp(x.Name[WORD], y) 713 | 714 | if found >= 0: 715 | return &entries[found + first] 716 | 717 | else: 718 | let 719 | first = 0 720 | last = resources.NumberOfNamedEntries.int - 1 721 | 722 | let found = binarySearch(entries.toOpenArray(first, last), 723 | key) do (x: IMAGE_RESOURCE_DIRECTORY_ENTRY, y: LPCTSTR) -> int: 724 | 725 | let 726 | stru = root{x.NameOffset}[PIMAGE_RESOURCE_DIR_STRING_U] 727 | length = int32 stru.Length 728 | lpstr = addr stru.NameString[0] 729 | 730 | result = CompareStringEx(LOCALE_NAME_INVARIANT, LINGUISTIC_IGNORECASE, 731 | lpstr, length, y, -1, nil, nil, 0) - 2 732 | 733 | if found >= 0: 734 | return &entries[found + first] 735 | 736 | let 737 | codeBase = lib 738 | headers = RtlImageNtHeader(lib) 739 | 740 | if headers == nil: 741 | raise newException(LibraryError, "Invalid handle") 742 | 743 | let directory = headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE] 744 | if directory.Size == 0: 745 | raise newException(LibraryError, "Resource data not found") 746 | 747 | let 748 | lang = 749 | if lang == 0: LANGIDFROMLCID(GetThreadLocale()) 750 | else: lang 751 | 752 | root = codeBase{directory.VirtualAddress}[PIMAGE_RESOURCE_DIRECTORY] 753 | 754 | var 755 | typeDir, nameDir: PIMAGE_RESOURCE_DIRECTORY 756 | typeEntry, nameEntry, langEntry: PIMAGE_RESOURCE_DIRECTORY_ENTRY 757 | 758 | typeEntry = searchResourceEntry(root, root, typ) 759 | if typeEntry == nil: 760 | raise newException(LibraryError, "Resource type not found") 761 | 762 | typeDir = root{typeEntry[].OffsetToDirectory}[PIMAGE_RESOURCE_DIRECTORY] 763 | nameEntry = searchResourceEntry(root, typeDir, name) 764 | if nameEntry == nil: 765 | raise newException(LibraryError, "Resource name not found") 766 | 767 | nameDir = root{nameEntry[].OffsetToDirectory}[PIMAGE_RESOURCE_DIRECTORY] 768 | langEntry = searchResourceEntry(root, nameDir, lang[LPCTSTR]) 769 | if langEntry == nil: 770 | # requested lang not found, use first available 771 | if nameDir.NumberOfIdEntries == 0: 772 | raise newException(LibraryError, "Resource language not found") 773 | 774 | langEntry = nameDir{sizeof(IMAGE_RESOURCE_DIRECTORY)}[PIMAGE_RESOURCE_DIRECTORY_ENTRY] 775 | 776 | return root{langEntry[].OffsetToDirectory}[HRSRC] 777 | 778 | proc sizeOfResource*(lib: HMODULE, resource: HRSRC): DWORD = 779 | ## Get the size of the resource in bytes. 780 | let entry = resource[PIMAGE_RESOURCE_DATA_ENTRY] 781 | if entry != nil: 782 | result = entry.Size 783 | 784 | proc loadResource*(lib: HMODULE, resource: HRSRC): HGLOBAL = 785 | ## Get a pointer to the contents of the resource. 786 | let entry = resource[PIMAGE_RESOURCE_DATA_ENTRY] 787 | if entry != nil: 788 | result = lib{entry[].OffsetToData}[HGLOBAL] 789 | 790 | proc loadString*(lib: HMODULE, id: UINT, lang: WORD = 0): string = 791 | ## Load a string resource. 792 | let resource = findResource(lib, MAKEINTRESOURCE((id shr 4) + 1), RT_STRING, lang) 793 | 794 | var data = loadResource(lib, resource)[PIMAGE_RESOURCE_DIR_STRING_U] 795 | for i in 0 ..< (id and 0x0f): 796 | data = data{int(data.Length + 1) * sizeof(WCHAR)}[PIMAGE_RESOURCE_DIR_STRING_U] 797 | 798 | if data.Length == 0: 799 | raise newException(LibraryError, "Resource name not found") 800 | 801 | let pucaWchar = cast[ptr UncheckedArray[WCHAR]](&data.NameString[0]) 802 | result = $$toOpenArray(pucaWchar, 0, int data.Length - 1) 803 | 804 | proc findResource*(lib: MemoryModule, name: LPCTSTR, typ: LPCTSTR, 805 | lang: WORD = 0): HRSRC {.inline.} = 806 | ## Find the location of a resource with the specified type, name and language. 807 | if lib == nil: 808 | raise newException(LibraryError, "Invalid handle") 809 | 810 | result = findResource(lib.codeBase[HMODULE], name, typ, lang) 811 | 812 | proc sizeOfResource*(lib: MemoryModule, resource: HRSRC): DWORD {.inline.} = 813 | ## Get the size of the resource in bytes. 814 | if lib == nil: 815 | raise newException(LibraryError, "Invalid handle") 816 | 817 | result = sizeOfResource(lib.codeBase[HMODULE], resource) 818 | 819 | proc loadResource*(lib: MemoryModule, resource: HRSRC): HGLOBAL {.inline.} = 820 | ## Get a pointer to the contents of the resource. 821 | if lib == nil: 822 | raise newException(LibraryError, "Invalid handle") 823 | 824 | result = loadResource(lib.codeBase[HMODULE], resource) 825 | 826 | proc loadString*(lib: MemoryModule, id: UINT, lang: WORD = 0): string {.inline.} = 827 | ## Load a string resource. 828 | if lib == nil: 829 | raise newException(LibraryError, "Invalid handle") 830 | 831 | result = loadString(lib.codeBase[HMODULE], id, lang) 832 | 833 | # for hooks 834 | 835 | # hook LdrLoadDll will crash on some version of Windows by unknow reason. 836 | # aoivd to use undocumented api ? 837 | 838 | proc myLoadDll(name: string): HMODULE = 839 | withLock(gLock): 840 | for i in 0 ..< memLibs.len: 841 | if memLibs[i].name.toLowerAscii == name.toLowerAscii: 842 | return memLibs[i][HMODULE] 843 | 844 | proc myLoadLibraryExA(lpLibFileName: LPCSTR, hFile: HANDLE, dwFlags: DWORD): HMODULE {.stdcall, minhook: LoadLibraryExA.} = 845 | result = myLoadDll($lpLibFileName) 846 | if result == 0: 847 | result = LoadLibraryExA(lpLibFileName, hFile, dwFlags) 848 | 849 | proc myLoadLibraryExW(lpLibFileName: LPCWSTR, hFile: HANDLE, dwFlags: DWORD): HMODULE {.stdcall, minhook: LoadLibraryExW.} = 850 | result = myLoadDll($lpLibFileName) 851 | if result == 0: 852 | result = LoadLibraryExW(lpLibFileName, hFile, dwFlags) 853 | 854 | proc myLoadLibraryA(lpLibFileName: LPCSTR): HMODULE {.stdcall, minhook: LoadLibraryA.} = 855 | result = myLoadDll($lpLibFileName) 856 | if result == 0: 857 | result = LoadLibraryA(lpLibFileName) 858 | 859 | proc myLoadLibraryW(lpLibFileName: LPCWSTR): HMODULE {.stdcall, minhook: LoadLibraryW.} = 860 | result = myLoadDll($lpLibFileName) 861 | if result == 0: 862 | result = LoadLibraryW(lpLibFileName) 863 | 864 | proc myGetProcAddress(hModule: HMODULE, lpProcName: LPCSTR): FARPROC {.stdcall, minhook: GetProcAddress.} = 865 | result = GetProcAddress(hModule, lpProcName) 866 | if result == nil: 867 | withLock(gLock): 868 | for i in 0 ..< memLibs.len: 869 | if hModule == memLibs[i][HANDLE]: 870 | return memLibs[i].symAddr(lpProcName) 871 | 872 | proc unhook*(lib: MemoryModule) = 873 | ## Removes the hooks. 874 | assert lib != nil 875 | lib.name = "" 876 | 877 | withLock(gLock): 878 | for i in 0 ..< memLibs.len: 879 | if memLibs[i].name != "": 880 | return 881 | 882 | if hookEnabled: 883 | try: 884 | queueDisableHook(LoadLibraryExA) 885 | queueDisableHook(LoadLibraryExW) 886 | queueDisableHook(LoadLibraryA) 887 | queueDisableHook(LoadLibraryW) 888 | queueDisableHook(GetProcAddress) 889 | applyQueued() 890 | except: discard 891 | hookEnabled = false 892 | 893 | proc hook*(lib: MemoryModule, name: string) = 894 | ## Hooks the system API (LoadLibrary and GetProcAddress only) with specified name. 895 | ## Following requests will be redirected to the memory module 896 | assert lib != nil 897 | lib.unhook() 898 | lib.name = name 899 | 900 | withLock(gLock): 901 | if not hookEnabled: 902 | try: 903 | queueEnableHook(LoadLibraryExA) 904 | queueEnableHook(LoadLibraryExW) 905 | queueEnableHook(LoadLibraryA) 906 | queueEnableHook(LoadLibraryW) 907 | queueEnableHook(GetProcAddress) 908 | applyQueued() 909 | except: discard 910 | hookEnabled = true 911 | 912 | 913 | # for memlib macro 914 | 915 | template memlookup(callPtr: ptr pointer, dll: DllContent, sym: LPCSTR, loadLib, symAddr: untyped) = 916 | var 917 | hash = toMD5(string dll) 918 | lib: MemoryModule 919 | 920 | withLock(gLock): 921 | for i in 0 ..< memLibs.len: 922 | if memLibs[i].hash == hash: 923 | lib = memLibs[i] 924 | break 925 | 926 | if lib == nil: 927 | lib = loadLib(dll) 928 | 929 | callPtr[] = lib.symAddr(sym) 930 | 931 | template rtlookup(callPtr: ptr pointer, name: string, sym: LPCSTR, errorLib, errorSym: untyped) = 932 | var handle = LoadLibrary(name) 933 | if handle == 0: 934 | errorLib 935 | 936 | else: 937 | withLock(gLock): 938 | var found = false 939 | for i in 0 ..< rtLibs.len: 940 | if rtLibs[i] == handle: 941 | found = true 942 | break 943 | 944 | if found: 945 | FreeLibrary(handle) # decrease reference count only 946 | 947 | else: 948 | rtLibs.add handle 949 | 950 | callPtr[] = GetProcAddress(handle, sym) 951 | if callPtr[] == nil: 952 | errorSym 953 | 954 | proc checkedMemlookup*(callPtr: ptr pointer, dll: DllContent, sym: LPCSTR) = 955 | ## A helper used by `memlib` macro. 956 | memlookup(callPtr, dll, sym, checkedLoadLib, checkedSymAddr) 957 | 958 | proc memlookup*(callPtr: ptr pointer, dll: DllContent, sym: LPCSTR) = 959 | ## A helper used by `memlib` macro. 960 | memlookup(callPtr, dll, sym, loadLib, symAddr) 961 | 962 | proc checkedLibLookup*(callPtr: ptr pointer, lib: MemoryModule, sym: LPCSTR) = 963 | ## A helper used by `memlib` macro. 964 | callPtr[] = lib.checkedSymAddr(sym) 965 | 966 | proc libLookup*(callPtr: ptr pointer, lib: MemoryModule, sym: LPCSTR) = 967 | ## A helper used by `memlib` macro. 968 | callPtr[] = lib.symAddr(sym) 969 | 970 | proc checkedRtlookup*(callPtr: ptr pointer, name: string, sym: LPCSTR) = 971 | ## A helper used by `memlib` macro. 972 | rtlookup(callPtr, name, sym) do: 973 | raise newException(LibraryError, "Could not load " & name) 974 | do: 975 | raise newException(LibraryError, symErrorMessage(sym)) 976 | 977 | proc rtlookup*(callPtr: ptr pointer, name: string, sym: LPCSTR) = 978 | ## A helper used by `memlib` macro. 979 | rtlookup(callPtr, name, sym) do: 980 | return 981 | do: 982 | return 983 | 984 | proc rewritePragma(def: NimNode, hasRaises: bool): (NimNode, NimNode) = 985 | var 986 | sym = def.name.toStrLit 987 | newPragma = newTree(nnkPragma) 988 | typPragma = newTree(nnkPragma) 989 | procty = newTree(nnkProcTy, def.params, typPragma) 990 | 991 | # Procs imported from Dll implies gcsafe and raises: [] 992 | typPragma.add ident("gcsafe") 993 | newPragma.add ident("gcsafe") 994 | 995 | for node in def.pragma: 996 | # ignore single importc 997 | if node.kind == nnkIdent and $node == "importc": 998 | continue 999 | 1000 | # ignore importc: symbol, but copy the symbol 1001 | if node.kind == nnkExprColonExpr and $node[0] == "importc": 1002 | sym = node[1] 1003 | if sym.kind == nnkStrLit: 1004 | sym.strVal = sym.strVal.multiReplace(("$$", "$"), ("$1", $def[0])) 1005 | continue 1006 | 1007 | # only procDef accept discardable 1008 | if node.kind == nnkIdent and $node == "discardable": 1009 | newPragma.add node 1010 | continue 1011 | 1012 | newPragma.add node 1013 | typPragma.add node 1014 | 1015 | def.pragma = newPragma 1016 | return (sym, procty) 1017 | 1018 | proc addParams(def: NimNode) = 1019 | for node in def.params: 1020 | if node.kind == nnkIdentDefs: 1021 | for i in 0 ..< node.len - 2: 1022 | assert def.body[^1].kind == nnkCall 1023 | def.body[^1].add node[i] 1024 | 1025 | proc isVarargs(def: NimNode): bool = 1026 | for i in def.pragma: 1027 | if i.eqIdent("varargs"): return true 1028 | return false 1029 | 1030 | proc compose(dll, def: NimNode, hasRaises: bool): NimNode = 1031 | var 1032 | (sym, procty) = def.rewritePragma(hasRaises) 1033 | memlookup = ident(if `hasRaises`: "checkedmemlookup" else: "memlookup") 1034 | libLookup = ident(if `hasRaises`: "checkedLibLookup" else: "libLookup") 1035 | rtlookup = ident(if `hasRaises`: "checkedrtlookup" else: "rtlookup") 1036 | isVarargs = def.isVarargs() 1037 | name = if isVarargs: ident(def.name.strVal) else: ident("call") 1038 | 1039 | # We must lookup the symbol on import for proc with varargs. 1040 | # See https://github.com/khchen/memlib/issues/2. 1041 | 1042 | var code = quote do: 1043 | var 1044 | `name` {.global.}: `procty` 1045 | sym: LPCSTR 1046 | 1047 | when `sym` is string: 1048 | sym = cast[LPCSTR](cstring(`sym`)) 1049 | elif `sym` is SomeInteger: 1050 | sym = `sym`.int[LPCSTR] 1051 | else: 1052 | {.fatal: "importc only allows string or integer".} 1053 | 1054 | if `name`.isNil: 1055 | {.gcsafe.}: 1056 | when `dll` is DllContent: 1057 | `memlookup`(`name`.addr[ptr pointer], `dll`, sym) 1058 | 1059 | elif `dll` is MemoryModule: 1060 | `libLookup`(`name`.addr[ptr pointer], `dll`, sym) 1061 | 1062 | elif `dll` is string: # Load dll at runtime 1063 | `rtlookup`(`name`.addr[ptr pointer], `dll`, sym) 1064 | 1065 | else: 1066 | {.fatal: "memlib only accepts DllContent, MemoryModule, or string".} 1067 | 1068 | `name`() 1069 | 1070 | if isVarargs: 1071 | code.del(code.len - 1) # remove last call 1072 | result = code 1073 | 1074 | else: 1075 | def.body = code 1076 | def.addParams() # add params to last call 1077 | result = def 1078 | 1079 | macro checkedMemlib*(dll, def: untyped): untyped = 1080 | ## `dynlib` pragma replacement to load DLL from memory or at runtime. 1081 | ## Raise `LibraryError` if error occurred. 1082 | ## See `memlib` for details. 1083 | def.expectKind(nnkProcDef) 1084 | result = compose(dll, def, hasRaises = true) 1085 | 1086 | macro memlib*(dll, def: untyped): untyped = 1087 | ## `dynlib` pragma replacement to load DLL from memory or at runtime. 1088 | ## Accepts a `MemoryModule`, `DllContent`, or `string`. **The program will 1089 | ## crash if error occurred, so only use this for trusted DLL.** 1090 | ## ============ ============================================================ 1091 | ## Parameter Meaning 1092 | ## ============ ============================================================ 1093 | ## MemoryModule Uses the DLL loaded by `loadLib` or `checkedLoadLib`. 1094 | ## DllContent Loads DLL in binary format that returned by `staticReadDll`. 1095 | ## string Loads the DLL at runtime by system API. 1096 | ## ============ ============================================================ 1097 | 1098 | runnableExamples: 1099 | const dll = staticReadDll("sqlite3_64.dll") 1100 | let lib = loadLib(dll) 1101 | proc libversion(): cstring {.cdecl, memlib: lib, importc: "sqlite3_libversion".} 1102 | echo libversion() 1103 | 1104 | runnableExamples: 1105 | const dll = staticReadDll("sqlite3_64.dll") 1106 | proc libversion(): cstring {.cdecl, memlib: dll, importc: "sqlite3_libversion".} 1107 | echo libversion() 1108 | 1109 | runnableExamples: 1110 | proc libversion(): cstring {.cdecl, memlib: "sqlite3_64.dll", importc: "sqlite3_libversion".} 1111 | echo libversion() 1112 | 1113 | def.expectKind(nnkProcDef) 1114 | result = compose(dll, def, hasRaises = false) 1115 | 1116 | proc reformat(pragma, n: NimNode): NimNode = 1117 | result = n 1118 | 1119 | if n.kind == nnkProcDef: # and n[^1].kind == nnkEmpty: 1120 | # a proc def without body, node[4] is pragma 1121 | if n[4].kind == nnkEmpty: n[4] = newNimNode(nnkPragma) 1122 | pragma.copyChildrenTo(n[4]) 1123 | 1124 | elif n.len != 0: 1125 | for i in 0 ..< n.len: 1126 | n[i] = reformat(pragma, n[i]) 1127 | 1128 | macro withPragma*(pragma, body: untyped): untyped = 1129 | ## `push` pragma replacement. 1130 | 1131 | runnableExamples: 1132 | proc test() = 1133 | const dll = staticReadDll("sqlite3_64.dll") 1134 | withPragma { cdecl, memlib: dll, importc: "sqlite3_libversion" }: 1135 | proc libversion(): cstring 1136 | echo libversion() 1137 | 1138 | result = reformat(pragma, body) 1139 | 1140 | macro buildPragma*(pragma, body: untyped): untyped = 1141 | ## `pragma` pragma replacement. 1142 | 1143 | runnableExamples: 1144 | proc test() = 1145 | const dll = staticReadDll("sqlite3_64.dll") 1146 | buildPragma { cdecl, memlib: dll, importc: "sqlite3_libversion" }: mylib 1147 | proc libversion(): cstring {.mylib.} 1148 | echo libversion() 1149 | 1150 | pragma.expectKind({nnkCurly, nnkTableConstr}) 1151 | var 1152 | name = body[0] 1153 | pragma = pragma.repr 1154 | 1155 | result = quote do: 1156 | macro `name`(def: untyped): untyped = 1157 | result = newStmtList( 1158 | newTree(nnkCommand, 1159 | ident("withPragma"), 1160 | parseExpr(`pragma`), 1161 | newStmtList(def) 1162 | )) 1163 | 1164 | proc staticReadDllWithName*(dll: string, hint = true): (string, DllContent) {.compiletime.} = 1165 | ## Compile-time find and read library proc for DLL embedding. 1166 | ## Returns the path and the binary in DllContent format. 1167 | ## Supports `dynlib` name patterns. For example: `libtcl(|8.5|8.4)`. 1168 | proc checkDir(dir, filename: string): string = 1169 | result = normalizedPath(dir / filename) 1170 | if fileExists(result): 1171 | return result 1172 | 1173 | result = normalizedPath(dir / addFileExt(filename, ".dll")) 1174 | if fileExists(result): 1175 | return result 1176 | 1177 | return "" 1178 | 1179 | proc showHint(path: string) = 1180 | echo ansiForegroundColorCode(fgGreen), "Hint: ", 1181 | ansiForegroundColorCode(fgWhite), path, 1182 | ansiForegroundColorCode(fgCyan), " [StaticDllRead]", 1183 | ansiForegroundColorCode(fgWhite) 1184 | 1185 | proc staticReadDllOne(dll: string, hint = true): (string, DllContent) {.compiletime.} = 1186 | var 1187 | (dir, name, ext) = splitFile(dll) 1188 | filename = name & ext 1189 | dirs: seq[string] 1190 | 1191 | if dir.len == 0: 1192 | dirs.add getProjectPath() 1193 | dirs.add splitFile(getCurrentCompilerExe())[0] 1194 | for path in getEnv("PATH").split(';'): 1195 | dirs.add path 1196 | 1197 | else: 1198 | if isAbsolute(dir): 1199 | dirs.add dir 1200 | 1201 | else: 1202 | dirs.add getProjectPath() / dir 1203 | 1204 | for dir in dirs: 1205 | let path = checkDir(dir, filename) 1206 | if path != "": 1207 | if hint: showHint(path) 1208 | return (extractFilename(path), DllContent staticRead(path)) 1209 | 1210 | var dest: seq[string] 1211 | libCandidates(dll, dest) 1212 | 1213 | if dest.len != 1: 1214 | dest.insert(dll, 0) 1215 | 1216 | for i in dest: 1217 | result = staticReadDllOne(i, hint) 1218 | if result[0].len != 0: 1219 | return 1220 | 1221 | raise newException(LibraryError, "Cannot locate the DLL file: " & dll) 1222 | 1223 | proc staticReadDll*(dll: string, hint = true): DllContent {.compiletime.} = 1224 | ## Compile-time find and read library proc for DLL embedding. 1225 | ## Returns the binary in DllContent format. 1226 | ## Supports `dynlib` name patterns. For example: `libtcl(|8.5|8.4)`. 1227 | result = staticReadDllWithName(dll, hint)[1] 1228 | --------------------------------------------------------------------------------