This is my strategy which solves all my problems (but not in the most elegant way, and I feel perhaps there is a better method? Please feel free to chime in)
This also solves another problem I had: sharing code between different computers which had different path names.
Broadly I create an Association
with the function names defined in strings, this way I don't have any symbol clashes. These string names point to the compiled code.
I save these details as compiledFunctionDetails
with the function storeCompiledFunctionDetails
. This function also creates the DLL files for later.
These details also Hold
your file path so it doesn't evaluate and you can use them on other PCs assuming your path is similar eg: FileNameJoin@{$UserDocumentsDirectory, "Wolfram Mathematica", "myDLLs"}
I then use a helper function readCompiledFunctionDetails
to call the already compiled code, releasing the held filename. Crucially, the DLLs is referenced by its string name so we can now assign its symbol name to it for the first time.
eg:
f1C = LibraryFunctionLoad @@ readCompiledFunctionDetails["f1C"]
Here is the code
(* 1. Define compiled functions here, <|"functionName"->Compile[etc.],...|> *)
compiledFunctions =
<|
"f1C" ->
Compile[
{
{a, _Real}, {b, _Real}
}
,
a + b
, Parallelization -> True,
CompilationTarget -> "C",
RuntimeOptions -> "Speed",
RuntimeAttributes -> Listable
],
"f2C" ->
Compile[
{
{a, _Real}, {b, _Real}
}
,
a*b
, Parallelization -> True,
CompilationTarget -> "C",
RuntimeOptions -> "Speed",
RuntimeAttributes -> Listable
]
|>
(* 2. Path to store DLLs and the config file: compiledFunctionDetails *)
(* *** YOUR FOLDER HERE *** *)
myDLLfolder = Hold[FileNameJoin@{$UserDocumentsDirectory, "Wolfram Mathematica","myDLLs"}];
(* 3. Function to save DLLs *)
storeCompiledFunctionDetails[cfName_String, cf_CompiledFunction] :=
With[
{
cfList = (cf /. {CompiledFunction -> List, LibraryFunction -> List}),
compiledFunctionsDllsPath = myDLLfolder
},
Module[
{
dllTemporaryFileName = Last[cfList][[1]],
cfNameAndSignature = Last[cfList][[2 ;;]],
libraryFunctionArguments, dllFileBaseName, dllFileName, dllFileNameNew, dllHeldNameToRead
},
(* copy dll locally *)
dllFileBaseName = FileNameTake[dllTemporaryFileName, -1];
dllFileName = FileNameJoin[{ReleaseHold@compiledFunctionsDllsPath,dllFileBaseName}]; (* unhold to copy *)
CopyFile[dllTemporaryFileName, dllFileName, OverwriteTarget -> True];
(* rename *)
dllFileNameNew = FileNameJoin@{ReleaseHold@compiledFunctionsDllsPath, cfName <> ".dll"}; (* unhold to rename *)
RenameFile[dllFileName, dllFileNameNew, OverwriteTarget -> True];
(* instructions for LibraryFunctionLoad *)
dllHeldNameToRead = FileNameJoin@{compiledFunctionsDllsPath, cfName <> ".dll"}; (* hold so is works on other computers *)
libraryFunctionArguments = Join[{dllHeldNameToRead}, cfNameAndSignature];
<|cfName -> libraryFunctionArguments|>
]
];
(* 4. Function to read DLLs *)
compiledFunctionDetailsFileName = FileNameJoin[{ReleaseHold[myDLLfolder], "CompiledFunctionDetails.mx"}];
readCompiledFunctionDetails = Map[ReleaseHold, Import[compiledFunctionDetailsFileName], {2}];
(* 5. Export the compiled code *)
compiledFunctionDetails = Association[ KeyValueMap[storeCompiledFunctionDetails, compiledFunctions]]
(* 6. Export instruction the config file *)
Export[compiledFunctionDetailsFileName, compiledFunctionDetails];
(* Now place this in your paclet / package or elsewhere *)
(* Load the function *)
f1C = LibraryFunctionLoad @@ readCompiledFunctionDetails["f1C"]
f2C = LibraryFunctionLoad @@ readCompiledFunctionDetails["f2C"]
f1C[40, 2] (* 42. *)
f2C[40, 2] (* 80. *)