F # runtime compilation code ends up missing assemblies

I have a question related to the code provided in the answer to this question .

The problem is that the three referenced assmeblies (System.dll, FSharp.Core.dll, FSharp.Powerpack.dll) that are passed to CompilerParameters

were not found at runtime. The error I am getting:

unknown-file (0,0): Error 0: Error FS0218: Cannot read assembly 'c: \ user s \ utente \ documents \ visual studio 2010 \ Projects \ TrashSolution \ TrashSolution \ bin \ D ebug \ FSharp.Core.dll'

How do I tell the compiler to search for these assemblies in the GAC instead of the project's bin directory? If I open the namespace in string code, how do I know which assemblies to add? Where can I get this information?

+3


source to share


1 answer


The code from the answer you linked has a line at the bottom:

let asm = Reflection.Assembly.LoadFrom(fileinfo.Value.FullName)

      

If you call Reflection.Load

and pass the full assembly name to it, it will try to load the assembly from the GAC (and several other places if the assembly is not in the GAC).

let asm =
    Assembly.Load "SampleAssembly, Version=1.0.2004.0, Culture=neutral, PublicKeyToken=8744b20f8da049e3"

      

If you don't know the full assembly name, you need to create AssemblyName

with a simple assembly name, then call Reflection.Load

that takes AssemblyName

instead string

.

let asmName = AssemblyName "Your.Assembly.Name"
let asm = Assembly.Load asmName

      

As far as I know which assemblies to load - I don't think there is an easy way to determine this programmatically. The only two solutions I can think of right now are:

  • If you have some information about the code you are giving (as a string), you can parse it with FSharpCodeProvider

    and see what namespaces / modules are open and what types are in use. If you want to see if any particular namespace or type is being used (that is, that you will need to include an assembly reference when compiling your code), you can create a map (in yours .fsx

    that compiles) the namespaces and / or namespaces types for assembly names and use them to refer to the corresponding assemblies.
  • You can brute-force search the GAC by using the semi-documented Fusion API to list all assemblies installed in the GAC and then using Reflection to examine each assembly and determine if you need it. This will probably be very slow, so I could avoid it at all costs. If you choose to go this route, you must also use the method Assembly.ReflectionOnlyLoad

    to load assemblies! This allows assemblies to be unloaded after you examine them - if you are using normal Reflection, assemblies cannot be unloaded and your program will most likely crash using OutOfMemoryException

    or similar.


EDIT: It turns out that loading an assembly by simple name is done in fsi

and not in normal F # code because it fsi

automatically sets a handler for AppDomain.AssemblyResolve . This event is triggered by the CLR when trying to load an assembly and cannot be resolved; the event gives you the option to "manually" enable the assembly and / or generate the assembly dynamically and return it.

If you look at the FileNotFoundException

one raised when trying to run the code in an F # project, you will see something like this in the Fusion Log property of the exception:

=== Pre-bind state information ===
LOG: User = Jack-Laptop\Jack
LOG: DisplayName = System
 (Partial)
WRN: Partial binding information was supplied for an assembly:
WRN: Assembly Name: System | Domain ID: 1
WRN: A partial bind occurs when only part of the assembly display name is provided.
WRN: This might result in the binder loading an incorrect assembly.
WRN: It is recommended to provide a fully specified textual identity for the assembly,
WRN: that consists of the simple name, version, culture, and public key token.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.
LOG: Appbase = file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : StackOverflow1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System.DLL.
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System/System.DLL.
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System.EXE.
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System/System.EXE.

      

Looking at the bottom of this log, you can see where the CLR was looking for an assembly before it gave up.

Here's a simple handler to give you an idea of ​​how to use the AppDomain.AssemblyResolve handler to manually resolve an assembly. (NOTE: A handler must be added before the code that tries to load the assembly!)

System.AppDomain.CurrentDomain.add_AssemblyResolve (
    System.ResolveEventHandler (fun _ args ->
        let resolvedAssembly =
            System.AppDomain.CurrentDomain.GetAssemblies ()
            |> Array.tryFind (fun loadedAssembly ->
                // If this assembly has the same name as the one we're looking for,
                // assume it correct and load it. NOTE : It may not be the _exact_
                // assembly we're looking for -- then you'll need to adjust the critera below.
                args.Name = loadedAssembly.FullName
                || args.Name = loadedAssembly.GetName().Name)

        // Return null if the assembly couldn't be resolved.
        defaultArg resolvedAssembly null))

      

If you add this code to a new F # Console project, and then the code that uses AssemblyName

Assembly.Load, you can load the assembly System

because by default it references an F # project and it will be loaded when the project starts. If you try to resolve System.Drawing

it won't work because our custom event handler won't be able to find the assembly. Obviously, if you need more complex assembly logic, you must create it in your event handler in any way that makes sense for your application.

Finally, here's a link to the MSDN whitepaper mentioned in the exception post: Assembly Loading Guidelines . It is worth reading if you get stuck and cannot figure out how to resolve the assemblies you need.

+7


source







All Articles