在 F# Interactive 中创建新的 AppDomain

Creating new AppDomain in F# Interactive

我需要能够在 F# 交互式中创建一个新的 AppDomain,以便托管多个 WPF 应用程序。在编译的 F# 应用程序中获得必要的功能我没有任何问题,但由于某种原因让它在 F# interactive 中工作似乎是不可能的。

这是最简单的情况:-

#r "PresentationCore.dll"
#r "PresentationFramework.dll"
#r "System.Xaml.dll"
#r "WindowsBase.dll"

open System    
open System.Threading
open System.Windows

type myClass() = 
    let domain = AppDomain.CreateDomain("another domain")

    //this function starts a WPF app
    let funct() =                
        let WPFStart() =
            let app = Application()
            let win = Window()            
            app.Run(win) |> ignore
        let thread = Thread WPFStart
        thread.IsBackground <- true
        thread.SetApartmentState ApartmentState.STA
        thread.Start()

    do CrossAppDomainDelegate(funct) |> domain.DoCallBack

myClass();;

我总是按照

的方式找回一些东西
System.Runtime.Serialization.SerializationException: Type is not resolved 
for member 'FSI_0002+-ctor@24,FSI-ASSEMBLY, Version=0.0.0.0, 
Culture=neutral, PublicKeyToken=null'.
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at FSI_0002.myClass..ctor()
at <StartupCode$FSI_0005>.$FSI_0005.main@()
Stopped due to error

我需要做什么才能让它在 F# 交互式中工作?

来自 docs 的介绍:

F# Interactive attempts to compile the code and, if successful, it executes the code and prints the signature of the types and values that it compiled.

主要问题在于编译步骤

typeof<myClass>.Assembly.FullName

输出:

val it : string =
  "FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"

为了编译代码 fsi 使用动态程序集来托管在会话期间创建的所有类型。这里的关键因素是,如果不引用包含程序集的类型,其他域将无法解析这些类型。但是,事实证明,从其他应用程序域获取程序集并非易事。主要是因为我们处理的是动态程序集。

let asm = typeof<myClass>.Assembly 
asm.IsDynamic // val it : bool = true

意思是,它只存在于fsi的默认appdomain的内存中。下面的两个查找都抛出

System.NotSupportedException: The invoked member is not supported in a dynamic assembly.

asm.Location
asm.CodeBase

通常,您希望首先持久化到磁盘,参考 remarks - 发送到远程应用程序域的限制:

Some scenarios require a dynamic assembly to be created and executed in a remote application domain. Reflection emit does not allow a dynamic assembly to be emitted directly to a remote application domain. The solution is to emit the dynamic assembly in the current application domain, save the emitted dynamic assembly to disk, and then load the dynamic assembly into the remote application domain.

成功地将动态程序集转换为 AssemblyBuilder 将公开一个 Save 方法。不幸的是,这个工作流程也被封锁了。

open System.Reflection.Emit
let builder = asm :?> AssemblyBuilder

投掷

System.InvalidCastException: Unable to cast object of type 'System.Reflection.Emit.InternalAssemblyBuilder' to type 'System.Reflection.Emit.AssemblyBuilder'

我们正在处理内部类型,显然我们不想亲自动手。来自 referencesource.microsoft.com:

In the past, when InternalAssemblyBuilder was AssemblyBuilder, the untrusted user could down cast the Assembly to an AssemblyBuilder and emit code with the elevated permissions of the trusted code which origionally created the AssemblyBuilder via DefineDynamicAssembly. Today, this can no longer happen because the Assembly returned via AssemblyGetAssemblies() will be an InternalAssemblyBuilder.

或者,您可以使用 new AssemblyBuilderSystem.Reflection.Emit 命名空间中的其他助手来反映动态程序集和 reconstruct them 中的类型,但这一切似乎有点乏味的一面。

总而言之,按照目前的实施方式,您将逆流而行,试图将 fsi 生成的类型暴露给其他域。