Loading native DLLs in F# Interactive
F# Interactive (FSI) is a very convenient environment to execute pieces of F# code on the fly. You can even reference managed assemblies using the #r
and #I
preprocessor directives. However, if one of the referenced assemblies tries to use a native DLL using p/invoke you might end up with a DllNotFoundException
even if the native DLL is in the same folder as the managed assembly, and if the folder has been included with the #I
directive. Note that it is not possible to reference native DLLs explicitly in .Net.
The reason is that finding and loading such DLLs in .Net works the same way as all native applications in Windows and follows the standard search order. When launched from within VisualStudio, the working directory of the F# Interactive process is the path where it is installed, in my case C:\Program Files (x86)\Microsoft SDKs\F#\3.0\Framework\v4.0. Naturally it has no chance to find a DLL in your script folder and fails.
There are multiple ways how you can tell Windows where to look for the DLL:
Any technique described here is only needed when using F# Interactive or run F# scripts, but not in normal compiled applications (where you'd just tell the IDE or build tool to copy the native DLLs to the output path)
Change the Working Directory
The simplest way is to set the process' working directory to the directory where the DLL is located. If it is in the same place as your script file, you can do this with a one-liner:
1:
|
|
Set Path Environment Variable
Beside of the working directory, Windows also considers the Path environment variables to look for the DLL. Environment variables can be defined globally or on your user account, but also locally to a running process.
You can append the path to the process-local Path
variable like this:
1: 2: 3: 4: |
|
Tell Windows: SetDllDirectory
We can also tell Windows directly where to look by calling the SetDllDirectory function:
1: 2: 3: 4: 5: 6: 7: |
|
However, note that every time this method is called it overrides what was set in the last call. So this can work well for a while - until someone else within your process starts calling it as well.
Load Explicitly: LoadLibrary
If a library module with the same name is already loaded into memory, it will be used directly without even starting to look for it in the file system. We can leverage this by explicitly loading a library.
For example, for the MKL extensions for Math.NET Numerics we could write:
1: 2: 3: 4: 5: 6: |
|
If you were writing this in a long running process where the DLL is used in a well defined section only, then you'd better unload the library once no longer needed with the symmetric FreeLibrary
routine. But for quick experiments in F# Interactive it's probably fine.
Example: Enable the MKL native provider in Math.NET Numerics
Math.NET Numerics provides a few code samples as a NuGet package. Ignoring for now that we really should add much more interesting, complete and applied examples (ideas and contributions are welcome!), these examples also do not currently leverage the Intel MKL native provider for faster linear algebra, so let's change at least that. First get a copy of them:
- Create a new F# Console Application project
-
Open the NuGet Package Manager Console and run
1: 2: 3:
Install-Package MathNet.Numerics.FSharp -Version 2.6.0 Install-Package MathNet.Numerics.FSharp.Sample -Version 2.6.0 Install-Package MathNet.Numerics.MKL.Win-x86 -Version 1.3.0
- Open the Matrices.fsx file in Samples/MathNet.Numerics.FSharp
- Fix the MathNet.Numerics reference line to v2.6.1, or whatever package NuGet installed
Now we would like to enable MKL and verify that it works. The native DLLs have already been copied to the project root directory by the NuGet package in step 2.
Add the following lines right after the original 3 open-lines at the beginning of the file:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
|
If you try to execute this line by line, the last line calling LU and Solve will try to use MKL and fail with a DllNotFoundException
as expected. Let's try to use the environment variables approach by adding the following lines after the open-lines. Our script is two directories down from the project root directory, so we have to fix the path accordingly:
1: 2: |
|
And suddenly it works.
If it does not and you get a BadImageFormatException
, you may have switched F# Interactive to run in 64-bit mode. In this case you should install the MKL.Win-x64
package instead of the x86
one.
PS: We're trying to simplify this in the upcoming v3 release so there is only one package for both platforms and it automatically loads the right one. Maybe we can also do something about this whole 'telling windows where to find the DLLs' thing while we're at it...
static member CommandLine : string
static member CurrentDirectory : string with get, set
static member Exit : exitCode:int -> unit
static member ExitCode : int with get, set
static member ExpandEnvironmentVariables : name:string -> string
static member FailFast : message:string -> unit + 1 overload
static member GetCommandLineArgs : unit -> string[]
static member GetEnvironmentVariable : variable:string -> string + 1 overload
static member GetEnvironmentVariables : unit -> IDictionary + 1 overload
static member GetFolderPath : folder:SpecialFolder -> string + 1 overload
...
nested type SpecialFolder
nested type SpecialFolderOption
Full name: System.Environment
Environment.SetEnvironmentVariable(variable: string, value: string, target: EnvironmentVariableTarget) : unit
Environment.GetEnvironmentVariable(variable: string, target: EnvironmentVariableTarget) : string
type DllImportAttribute =
inherit Attribute
new : dllName:string -> DllImportAttribute
val EntryPoint : string
val CharSet : CharSet
val SetLastError : bool
val ExactSpelling : bool
val PreserveSig : bool
val CallingConvention : CallingConvention
val BestFitMapping : bool
val ThrowOnUnmappableChar : bool
member Value : string
Full name: System.Runtime.InteropServices.DllImportAttribute
--------------------
DllImportAttribute(dllName: string) : unit
| None = 1
| Ansi = 2
| Unicode = 3
| Auto = 4
Full name: System.Runtime.InteropServices.CharSet
Full name: Microsoft.FSharp.Core.bool
Full name: loadingnativedllsinfsharpinteractive.Kernel.SetDllDirectory
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
from loadingnativedllsinfsharpinteractive
type IntPtr =
struct
new : value:int -> nativeint + 2 overloads
member Equals : obj:obj -> bool
member GetHashCode : unit -> int
member ToInt32 : unit -> int
member ToInt64 : unit -> int64
member ToPointer : unit -> unit
member ToString : unit -> string + 1 overload
static val Zero : nativeint
static member Add : pointer:nativeint * offset:int -> nativeint
static member Size : int
...
end
Full name: System.IntPtr
--------------------
IntPtr()
IntPtr(value: int) : unit
IntPtr(value: int64) : unit
IntPtr(value: nativeptr<unit>) : unit
Full name: loadingnativedllsinfsharpinteractive.m
Full name: loadingnativedllsinfsharpinteractive.v
static val DirectorySeparatorChar : char
static val AltDirectorySeparatorChar : char
static val VolumeSeparatorChar : char
static val InvalidPathChars : char[]
static val PathSeparator : char
static member ChangeExtension : path:string * extension:string -> string
static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
static member GetDirectoryName : path:string -> string
static member GetExtension : path:string -> string
static member GetFileName : path:string -> string
...
Full name: System.IO.Path
Path.Combine(path1: string, path2: string) : string
Path.Combine(path1: string, path2: string, path3: string) : string
Path.Combine(path1: string, path2: string, path3: string, path4: string) : string