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:  System.Environment.CurrentDirectory <- __SOURCE_DIRECTORY__ 

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:  open System Environment.SetEnvironmentVariable("Path", Environment.GetEnvironmentVariable("Path") + ";" + __SOURCE_DIRECTORY__) 

Tell Windows: SetDllDirectory

We can also tell Windows directly where to look by calling the SetDllDirectory function:

 1: 2: 3: 4: 5: 6: 7:  open System.Runtime.InteropServices module Kernel = [] extern bool SetDllDirectory(string lpPathName); Kernel.SetDllDirectory(__SOURCE_DIRECTORY__) 

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.

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:  module Kernel = [] extern IntPtr LoadLibrary(string lpFileName); Kernel.LoadLibrary(Path.Combine(__SOURCE_DIRECTORY__, "libiomp5md")) Kernel.LoadLibrary(Path.Combine(__SOURCE_DIRECTORY__, "MathNet.Numerics.MKL.dll")) 

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:

1. Create a new F# Console Application project
2. 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 
3. Open the Matrices.fsx file in Samples/MathNet.Numerics.FSharp
4. 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:  open System open System.IO open MathNet.Numerics open MathNet.Numerics.Algorithms.LinearAlgebra.Mkl Control.LinearAlgebraProvider <- MklLinearAlgebraProvider() let m = matrix [[1.; 2.]; [3.; 4.]] let v = vector [4.;5.] m.LU().Solve(v) 

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:  Environment.SetEnvironmentVariable("Path", Environment.GetEnvironmentVariable("Path") + ";" + Path.Combine(__SOURCE_DIRECTORY__,@"..\..\")) 

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...

namespace System
type Environment =
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
property System.Environment.CurrentDirectory: string
Environment.SetEnvironmentVariable(variable: string, value: string) : unit
Environment.SetEnvironmentVariable(variable: string, value: string, target: EnvironmentVariableTarget) : unit
Environment.GetEnvironmentVariable(variable: string) : string
Environment.GetEnvironmentVariable(variable: string, target: EnvironmentVariableTarget) : string
namespace System.Runtime
namespace System.Runtime.InteropServices
Multiple items
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
type CharSet =
| None = 1
| Ansi = 2
| Unicode = 3
| Auto = 4

Full name: System.Runtime.InteropServices.CharSet
field CharSet.Auto = 4
type bool = Boolean

Full name: Microsoft.FSharp.Core.bool
val SetDllDirectory : lpPathName:string -> bool

Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val lpPathName : string
module Kernel

Multiple items
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
namespace System.IO
namespace System.Numerics
namespace Microsoft.FSharp.Control
val m : obj

val v : obj

type Path =
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([<ParamArray>] paths: string []) : string
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