OK, I've tried to get a reasonably compact version for testing.
There are 3 nim files: TESTmain, TESTmodule and Testlang.
* Testmain
* sets things up and creates a test text file to work with
* does the loading and calling of TESTmodule
* TESTmodule
* 3 callable procs : initModule, speechModule, endModule
* TESTlang
* has procs for loading the text file into a Table and extracting from that
Table
# TESTmain : should load and call TESTmodule procs - crash on call to
initModule
# compile with : nim c -mm:orc --threads:on -d:useNimRtl TESTmain.nim
# also tried without threads
import os
import strformat, strutils
import std/times
import dynlib
import random
import TESTdata
###################################################################################################
###################################################################################################
# DYNAMIC LIB MODULE LOAD & CALL
#
# all modules only have 3 procs which have a standard call
# - initModule (called with Settings, returns true or false)
# - speechModule (called with SimState and returns SimState = SimData +
speech queue)
# - endModule void
###################################################################################################
###################################################################################################
type
InitModuleProc = proc(s: Settings): bool {.nimcall}
SpeechModuleProc = proc(sd: SimState): SimState {.nimcall}
EndModuleProc = proc() {.nimcall}
# used .nimcall. as per https://forum.nim-lang.org/t/1400 that got me
farthest on my first tries
# plus, since it's the default for a nim proc and all is pure nim, this
seems the optimal choice
var
libModule: LibHandle
initModuleAddr: pointer
initModule: InitModuleProc
speechModuleAddr: pointer
speechModule: SpeechModuleProc
endModuleAddr: pointer
endModule: EndModuleProc
moduleFile: string
moduleLoaded: string = ""
moduleAvailable: bool = false
simstate: SimState
proc loadModule(moduleName: string): bool =
result = false
# create path to module (working dir if development version)
when defined(release):
moduleFile = os.joinPath(pathBase, "Modules", moduleName & ".dll")
else:
moduleFile = moduleName & ".dll"
debugEcho(fmt("Loading module file {moduleFile:s}"))
# try loading the module
try:
libModule = loadLib(moduleFile)
result = true
except:
debugEcho(fmt("ERROR: could not load module {moduleName}."))
sleep(250)
result = false
# get address(es) of the proc(s) in the dynamic lib
var moduleErrors: string = ""
if libModule != nil:
debugEcho(fmt("Loaded {moduleName} module."))
initModuleAddr = libModule.symAddr("initModule")
speechModuleAddr = libModule.symAddr("speechModule")
endModuleAddr = libModule.symAddr("endModule")
if initModuleAddr != nil:
initModule = cast[InitModuleProc] (initModuleAddr)
else:
moduleErrors = moduleErrors & ("- initModule not found\n")
if speechModuleAddr != nil:
speechModule = cast[SpeechModuleProc] (speechModuleAddr)
else:
moduleErrors = moduleErrors & ("- speechModule not found\n")
if endModuleAddr != nil:
endModule = cast[EndModuleProc] (endModuleAddr)
else:
moduleErrors = moduleErrors & ("- endModule not found\n")
else:
moduleErrors = moduleErrors & ("- something wrong with module")
if moduleErrors > "":
debugEcho(fmt("ERRORS: loading {moduleName} gave problem(s):\n") &
moduleErrors)
result = false
if result: # seems to have loaded fine : init
moduleLoaded = moduleName
else:
moduleLoaded = ""
return result
proc unloadModule() =
if moduleLoaded != "":
endModule()
unloadLib(libModule)
proc projectModule(simstate: SimState): SimState =
if simstate.simdat.projectName == "":
debugEcho(fmt("WARNING: No project {simstate.simdat.projectName}."))
return # nothing to do TODO: default module, so speech enabled for
debugging ?
var
sims = simstate # copy to modify
debugEcho(fmt("In projectModule {sims.simdat.projectName}
(loaded={moduleLoaded}).--------------------------------------------------"))
# not same lib module: unload old and load correct one
if moduleLoaded != sims.simdat.projectName:
unloadModule()
if loadModule(sims.simdat.projectName):
debugEcho(fmt("Module {moduleLoaded} loaded successfully..."))
# set data needed for initialization of module
var settings: Settings
# set some data for testing
settings.pathFile = "C:/Users/ivan"
settings.pathData = "C:/Users/ivan/Data"
settings.aBool = true
settings.anInt = 1504
settings.aFloat = 15.04
settings.aString = "Nim dll test"
settings.lang = "en"
debugEcho("Calling initModule with:")
debugEcho(settings)
discard initModule(settings) # initialize !
debugEcho("Returned from call to initModule")
else:
debugEcho(fmt("WARNING: Could not load the project
{sims.simdat.projectName} module: {moduleFile}."))
if libModule != nil:
try:
sims = speechModule(sims)
debugEcho(fmt("Returned from speechModule (with {sims.speakQ.len}
sentences)"))
except:
debugEcho("ERROR: Call to project module failed!")
else:
debugEcho(fmt("ERROR: Module not loaded or defined! ({moduleFile})"))
return sims
proc createTestfile() =
# create a 500 line test text file
var txt: string
for i in 1..500:
let r = rand(i)
txt = txt & (fmt("TXT_{i:3d}:Random {(r)} out of {i}.\n"))
if i in @[7, 12, 33, 88, 100, 105, 161, 163, 200, 300, 400, 488]:
txt = txt & (fmt("// Just comment {i}\n")) # fake some comment in
the file
try:
writeFile("TEST-en.txt", txt)
except:
debugEcho("Could not write test file")
proc main() =
debugEcho("Creating test text file")
createTestfile()
simstate.simdat.projectName = "TESTmodule"
debugEcho("Testing calls to speechModule")
let testData = @["Ivan:Hi bro!:71", "Johan:Hi you:11", "Ivan:Are you
coming?:7", "Johan:No, I'm going.:3", "Ma:Oh no!:1"]
for i in 0..testData.high:
let randomItem = rand(testData.high)
simstate.simdat.actionlist.add( (0, testData[i]) )
simstate.simdat.actionlist.add( (0, testData[(randomItem)]) )
simstate = projectModule(simstate)
debugEcho("Back from calling module with this to say:")
for spk in simstate.speakQ:
debugEcho(fmt("{spk.who:7s}|{spk.speaktime:8.1f}|{spk.sentence}"))
sleep(1234)
when isMainModule:
main()
Run
# plugin to be loaded and called by TESTmain
# FIRST create NimRtl.dll with "nim c -mm:orc -o:NimRtl.dll
<path-to>NimRtl.nim
# compile with : nim c -mm:orc -d:useNimRtl --noMain --app:lib
TESTmodule.nim
import os
import std/times
import strformat, strutils
import TESTdata
import TESTlang
var
data: int = 0
timeNow: float
timeStart: float = epochTime()
moduleSettings: Settings
sims: SimState
proc processMessage(m: string) =
debugEcho(fmt("Processing Message {m}"))
let mesg = m.split(":")
if mesg.len == 3: # otherwise invalid data expecting
"<name>:<sentence>:<lookupkey>"
var spk: SpeakData
spk.who = mesg[0]
spk.sentence = mesg[1] & " " & getTXT(mesg[2])
spk.speaktime = epochTime() - timeStart + 2.0
spk.expiretime = spk.speaktime + 10.0
sims.speakQ.add(spk)
proc processFile(f: string) =
debugEcho(fmt("File to process = {f}"))
proc NimMain() {.nimcall, importc.}
#{.push dynlib exportc.}
proc initModule(settings: Settings): bool {.exportc, dynlib, nimcall.} =
# also tried .dynlib, exportc.
debugEcho("Arrived in initModule")
NimMain() # Be sure to call this! It will set up garbage
collector and initialize any global memory
debugEcho("NimMain has been called")
moduleSettings = settings # copy to dll global var
debugEcho("initModule finished")
return true
proc endModule() {.exportc, dynlib, nimcall.} =
debugEcho("Closing down, these are the settings:\n", moduleSettings)
GC_FullCollect()
proc speechModule*(simstate: SimState): SimState {.exportc, dynlib,
nimcall.} =
sims = simstate # copy to a global for this module
# load project specific data
loadSpeechData(simstate.simdat.projectName, "")
let alist = sims.simdat.actionlist
debugEcho(fmt("Action List contains {(sims.simdat.actionlist.len):02d}
items"))
# just iterate over (dummy) text file loaded with loadSpeechData
for act in alist:
moduleSettings.anInt += 1
if act.what == ACT_MSG:
debugEcho(fmt("- PROCESSING: {act.what:s}|{act.info}"))
processMessage(act.info)
elif act.what == ACT_FIL:
debugEcho(fmt("- TO PROCESS: {act.what:s}|{act.info}"))
processFile(act.info)
# done processing
sims.simdat.actionlist = @[] # clear the actionlist
return sims
#{.pop}
Run
# Text data : can load different languages, but now only en English is
supplied
import times
import tables
import strformat
import strutils
import os
#import zippy/ziparchives
#import random
var
# lookup table with simple speech (words or sentences) found in
data-<language> currently only 'en' is implemented
# in data file starts with TXT_ to extract to this table
textTable* = initOrderedTable[string, string]()
countR, countT: int
index: string
proc uncomment*(s: string): string =
# strip comments (start with //) from string
var commentpos = s.find("//")
if commentpos == 0: # found at start
return ""
elif commentpos > 0:
return s[0..commentpos-1].strip()
else:
return s
proc loadSpeechData*(project:string = "", folder: string, language: string
= "en") =
# project is not used in this TEST version, just fixed TEST-en.txt (later
to become <project>-en.txt)
debugEcho("---------------------------------------In loadSpeechData")
var
speechData: string = ""
resourceFilename: string = "TEST-en.txt"
debugEcho(fmt("Loading speech data from {resourceFilename}\n"))
# read in the data file from the zipped data file (TODO : depending on
language)
# Open the file (no zip for TEST)
try:
speechData = readFile(resourceFilename)
except:
debugEcho(fmt("- ERROR : could not find speech data file
{resourceFilename}\n"))
debugEcho("---------------------------------------")
debugEcho("DUMP speechData:")
debugEcho(" - size: ", (sizeof(speechData)))
debugEcho(" - length: ", (speechData.len))
debugEcho(" - lines: ", (speechData.countLines))
debugEcho(speechData)
# clear the data
textTable.clear
for d in speechData.splitLines():
inc countR
let dataLine = d.uncomment
debugEcho(fmt("READ {countR:4d}|{dataLine}"))
if dataLine.startsWith("TXT_"):
# simple text (words or phrases)
let s = dataLine.split(':')
if s.len > 1:
index = s[0].replace("TXT_", "")
if not textTable.hasKey(index):
# does not exist : create one
textTable[index] = s[1]
inc countT
debugEcho(fmt("- checked {countR-1:d} items and used {countT}."))
return
proc getTXT*(index: string): string =
# get text from textTable
if textTable.hasKey(index):
return textTable[index]
else:
# returned string may sound weird in TTS, but otherwise tracing what
goes wrong will be very difficult
return fmt("|{index} not found| ")
Run
My tries two days ago got me a crash when iterating over the loaded text file.
Now I don't even get that far.
TESTmain
Creating test text file
In projectModule TESTmodule
(loaded=).--------------------------------------------------
Loading module file TESTmodule.dll
Loaded TESTmodule module.
Module TESTmodule loaded successfully...
Calling initModule with:
(pathFile: "C:/Users/ivan", pathData: "C:/Users/ivan/Data", lang: "en",
aBool: true, anInt: 1504, aFloat: 15.04, aString: "Nim dll test")
Traceback (most recent call last)
C:\Users\Ivan\GFR\TESTmain.nim(178) TESTmain
C:\Users\Ivan\GFR\TESTmain.nim(169) main
C:\Users\Ivan\GFR\TESTmain.nim(132) projectModule
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Run