I want to be able to load/unload program modules depending on user selection and availability.
It's about a program which feeds data (always the same format) to a (helper/sub)program which simulates hardware. While the main program remains the same, the hardware changes and new hardware is introduced. So I want to keep a basic core program and just update/add modules that simulate the hardware.
I looked into dynamic libraries, but that doesn't seem to be the solution, since the dynamic libraries need to be known at compile time.
This is what I want to do:
This very simplified example more or less shows a version with some basic output from the (helper/sub)programs. Kept as simple as possible, with some pseudocode
(moduleEcho.nim)
outPutText*(s: string) =
# just show the text on screen
echo s
(moduleLog.nim)
outPutText*(s: string) =
# append the text to a log file
# code to open file, append s and close it again
(main.nim)
# assume above code snippets are compiled to moduleEcho.exe and moduleLog.exe)
import os
# extra code...
optionList = @[""]
for outOption in walkFiles "module*.exe":
optionList.add(outOption.replace("module","").replace(".exe",""))
# extra code...
text = getTextLineFromUser()
optionChosen = letUserChoose(optionList)
execCmd(fmt("module{optionChosen}.exe {text}")
Now if I run main, we would get the outOptions "Echo" and "Log"
In the future, let's assume I also want the option for the text to be spoken.
I would just create moduleSay.exe :
(moduleSay.nim)
import osproc
outPutText*(s: string) =
discard execCmd("espeak 'Hello world!'")
And main would let the user now select "Say" in addition to "Echo" and "Log" .
The exe approach would work to some extent, but not very efficient and there's little to no feedback possible to the main program. Furthermore, during runtime, the user should be able to return to the main menu and select other 'hardware', meaning the previous loaded 'hardware module' can be removed to free memory.
What I want to do is of course more complex. First the argument passed would need to be binary (type of ints, floats, bools, seqs... and what more). Plus having the (helper/sub)program have access to some global variables (for updating them) would be a major benefit.
But as far as I see, dynamic libraries (even with load/unload) aren't the way to go. Or am I missing something / does someone have better ideas how to go about ?
You can use DLLs for that via the dynlib module.
But it's a bad idea because plugin systems in general are a bad idea. You're introducing a massive amount of complexity when you haven't even written the program yet and it's likely acceptable to recompile the program should a new feature arrive.
Thanks, I seem to have overlooked dynlib. Will do now.
Maybe it's a bad idea coding wise, but for a game DLC it's a benefit.
Plugin systems are great, complexity is just an unfortunate side-effect. Take your favourite editor for example, would you like to recompile it every time you install a plugin? Recompile your browser for an extension? That's just madness, and requires either a full reinstall for users (which leads to an extreme amount of versions if you want to support installing some plugins but not others, the combinations quickly become unmanageable) or each of your users need to have your entire build chain on their machines (and you need to give them your source code).
Instead you use a plugin system, you define some functions that can be called from your program, and require some functions that should exist in the program (like init, deinit, processX etc.) and allow users to write their extensions in whatever language they want (as long as it can interface) and load the plugins during runtime.
Of course WASM vs. dynamic library vs. script language all have their pros and cons. What we really need is better libraries/tools to create these kinds of systems more easily. Genny seems promising, and I know @ElegantBeef has done some work on WASM library loading, but I'm not sure how this work is structured or if it is easy to reuse.