Type provider calling another dll in F#

2019-01-24 17:26发布

问题:

In the construction of the internal representation for a provided type, I call indirectly in the quotation for the internal representation of my type, another dll, whose code I can modify.

Currently, when I consume the type provider, tells me it can't find such dll:

"Could not load file or Assembly xxxx or one of its dependency"

When I inspect VS using Process Explorer, though, I can see the dll XXX loaded... Are there anything to do to have the code in quotations accept code from outside dll ?

** update **

I tried with a simplified example, and it seems that it is possible to call such outside dll without any special thing to do. In my case, all the dll XXX depends on are loaded, I see them in Process explorer as well as in the modules windows when I debug devenv.exe itself....

I have no idea where to look to. Here is the inner exception.

** update **

If I copy the xxx dll and its dependencies in one of this path, the compiler works fine. I still wonder what can trigger devenv.exe to correctly show them loaded but not accessible.

=== Pre-bind state information ===
LOG: User = xxx\Administrator
LOG: DisplayName = bloombergapi, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/
LOG: Initial PrivatePath = NULL
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\Users\Administrator\AppData\Local\Microsoft\VisualStudio\11.0\devenv.exe.config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\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:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/bloombergapi/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/PublicAssemblies/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/PublicAssemblies/bloombergapi/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/PrivateAssemblies/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/PrivateAssemblies/bloombergapi/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/CommonExtensions/Microsoft/TemplateProviders/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/CommonExtensions/Microsoft/TemplateProviders/bloombergapi/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/CommonExtensions/Platform/Debugger/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/CommonExtensions/Platform/Debugger/bloombergapi/bloombergapi.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/Microsoft Visual Studio 11.0/Common7/IDE/PrivateAssemblies/DataCollectors/bloombergapi.DLL.

...

ANSWER

It seems impossible to call a function that takes as an argument a Type from another library. A union type works, but not a type proper... This is illustrated below

--Library.dll

namespace Library

module SampleModule = 
     type LocalUnion = | LocalUnion  of int

     type Localtype() = 
        member x.value = 2

--LibraryTP.dll

namespace LibraryTP

module Module =
   open System.Reflection
   open Samples.FSharp.ProvidedTypes
   open FSharpx.TypeProviders.DSL
   open Microsoft.FSharp.Core.CompilerServices

   let f a = 
     Library.SampleModule.sampleFunction a a 

   let g (a:Library.SampleModule.LocalUnion) = 
      let (Library.SampleModule.LocalUnion(v)) = a
      v

   let ftype (a:Library.SampleModule.Localtype) = 
        a.value

   let createTP ns =
       erasedType<obj> (Assembly.GetExecutingAssembly()) ns "Outside"
       |> staticParameter "file"
          (fun typeName (parameterValues:string) ->
               erasedType<obj> (Assembly.GetExecutingAssembly()) ns typeName
               |+!> (   provideProperty 
                           "test"                             //OK
                           typeof<float>                   
                           (fun args -> <@@  g ( f 2 ) @@>)      
                         |> makePropertyStatic
                     )
               |+!> (   provideProperty 
                           "test2"                             //KO
                           typeof<int>                   
                           (fun args -> <@@ ftype ( Library.SampleModule.Localtype())  @@>)   
                        |> makePropertyStatic   
                     )
                  )

   [<TypeProvider>]
   type public CustomTypeProvider(cfg:TypeProviderConfig) as this =
      inherit TypeProviderForNamespaces()

      do this.AddNamespace("TP", [createTP "TP"])

   [<TypeProviderAssembly>]
   do()

--Program.fs

   type sampleValue = TP.Outside<""> 

   [<EntryPoint>] 
   let main(args) = 
      let t = sampleValue.Test        //OK
      let tt = sampleValue.Test2      //KO 

回答1:

Your assembly may be loaded into the different binding context. When quotation is deserialized it may try to load the assembly to the one binding context and this attempt may fail even if assembly was already loaded to another context. As workaround in your type provider design time assembly you can add handler to the AssemblyResolve event and try to find target assembly in the list of already loaded assemblies.

    do System.AppDomain.CurrentDomain.add_AssemblyResolve(fun _ args ->
        let name = System.Reflection.AssemblyName(args.Name)
        let existingAssembly = 
            System.AppDomain.CurrentDomain.GetAssemblies()
            |> Seq.tryFind(fun a -> System.Reflection.AssemblyName.ReferenceMatchesDefinition(name, a.GetName()))
        match existingAssembly with
        | Some a -> a
        | None -> null
        )