2016-07-28 11 views
4

別のスクリプト以外のRoslynコンパイルでスクリプトを動的アセンブリとして再利用したいのですが、その仕事をするRoslynスクリプト提出を他のRoslynコンパイルのアセンブリとして使用する方法

例えば、私は通常の方法でスクリプトを作成し、その後のようなもの使用して、バイトストリームにアセンブリとしてスクリプトを発する言う:

var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); 
var compilation = script.GetCompilation().WithOptions(compilationOptions); 
using (var ms = new MemoryStream()) 
{ 
    EmitResult result = compilation.Emit(ms); 
    ms.Seek(0, SeekOrigin.Begin); 
    assembly = Assembly.Load(ms.ToArray()); 
} 

を、のは、私はにそのアセンブリを供給したいとしましょう他の非スクリプティングコンパイルを参考にしてください。 MetadataReference.CreateFrom...()メソッドのどれも実際のAssemblyインスタンスを渡すことをサポートしていないので、私はassemblyを使うことはできません。ダイナミックアセンブリとしては場所がないので、MetadataReference.CreateFromFile()は使用できません。

これまで成功したこのタイプのものにMetadataReference.CreateFromStream()を使用しましたが、アセンブリがスクリプトの提出を表すときには動作していないようです(理由はわかりません)。

System.InvalidCastException: [A]Foo cannot be cast to [B]Foo. Type A originates from 'R*19cecf20-a48e-4a31-9b65-4c0163eba857#1-0, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' in a byte array. Type B originates from 'R*19cecf20-a48e-4a31-9b65-4c0163eba857#1-0, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' in a byte array. 

私は対を評価する際には提出アセンブリが異なるコンテキストにあるとは何かを持って推測している:できるだけ早くあなたがのようなエラーを取得し提出からタイプを使用しようとすると、コンパイルが進むが、バイト配列としてロードされます。私は、後のスクリプト以外のコンパイルでスクリプト提出で定義されたオブジェクトとメソッドを使用する最良の方法についての洞察や指導が大好きです。

更新7/29

私は、問題を示し、最小限のREPROを得ることができました。それはhttps://github.com/daveaglick/ScriptingAssemblyReuseで見つけることができます。

この問題の重要な要素は、スクリプトがクラスのうちの1つのクラスのTypeを呼び出しコードに渡してから、呼び出しコードがTypeのインスタンスをインスタンス化することです。オブジェクトを作成し、インスタンスをスクリプトアセンブリを参照するコンパイルに渡します。この不一致は、ホストアプリケーションによって作成された型のインスタンスから参照元のコンパイル内の型にキャストするときに発生します。私はそれが非常に混乱して聞こえることを再読んで、次のコードは、それを明確にすることを願っています。

namespace ScriptingAssemblyReuse 
{ 
    public class Globals 
    { 
     public IFactory Factory { get; set; }  
    } 

    public interface IFactory 
    { 
     object Get(); 
    } 

    public class Factory<T> : IFactory where T : new() 
    { 
     public object Get() => new T(); 
    } 

    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      new Program().Run(); 
     } 

     private Assembly _scriptAssembly; 

     public void Run() 
     { 
      AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; 

      // Create the script 
      Script<object> script = CSharpScript.Create(@" 
       public class Foo { } 
       Factory = new ScriptingAssemblyReuse.Factory<Foo>(); 
       ", ScriptOptions.Default.WithReferences(MetadataReference.CreateFromFile(typeof(IFactory).Assembly.Location)), typeof(Globals)); 

      // Create a compilation and get the dynamic assembly 
      CSharpCompilationOptions scriptCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); 
      Compilation scriptCompilation = script.GetCompilation().WithOptions(scriptCompilationOptions); 
      byte[] scriptAssemblyBytes; 
      using (MemoryStream ms = new MemoryStream()) 
      { 
       EmitResult result = scriptCompilation.Emit(ms); 
       ms.Seek(0, SeekOrigin.Begin); 
       scriptAssemblyBytes = ms.ToArray(); 
      } 
      _scriptAssembly = Assembly.Load(scriptAssemblyBytes); 

      // Evaluate the script 
      Globals globals = new Globals(); 
      script.RunAsync(globals).Wait(); 

      // Create the consuming compilation 
      string assemblyName = Path.GetRandomFileName(); 
      CSharpParseOptions parseOptions = new CSharpParseOptions(); 
      SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@" 
       public class Bar 
       { 
        public void Baz(object obj) 
        { 
         Script.Foo foo = (Script.Foo)obj; // This is the line that triggers the exception 
        } 
       }", parseOptions, assemblyName); 
      CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); 
      string assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); 
      CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[] {syntaxTree}, 
       new[] 
       { 
        MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")), 
        MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")), 
        MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")), 
        MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")) 
       }, compilationOptions); 
      using (MemoryStream ms = new MemoryStream(scriptAssemblyBytes)) 
      { 
       compilation = compilation.AddReferences(MetadataReference.CreateFromStream(ms)); 
      } 

      // Get the consuming assembly 
      Assembly assembly; 
      using (MemoryStream ms = new MemoryStream()) 
      { 
       EmitResult result = compilation.Emit(ms); 
       ms.Seek(0, SeekOrigin.Begin); 
       byte[] assemblyBytes = ms.ToArray(); 
       assembly = Assembly.Load(assemblyBytes); 
      } 

      // Call the consuming assembly 
      Type barType = assembly.GetExportedTypes().First(t => t.Name.StartsWith("Bar", StringComparison.Ordinal)); 
      MethodInfo bazMethod = barType.GetMethod("Baz"); 
      object bar = Activator.CreateInstance(barType); 
      object obj = globals.Factory.Get(); 
      bazMethod.Invoke(bar, new []{ obj }); // The exception bubbles up and gets thrown here 
     } 

     private Assembly OnAssemblyResolve(object sender, ResolveEventArgs args) 
     { 
      if (_scriptAssembly != null && args.Name == _scriptAssembly.FullName) 
      { 
       // Return the dynamically compiled script assembly if given it's name 
       return _scriptAssembly; 
      } 
      return null; 
     } 
    } 
} 
+0

スタックトレース全体を共有できますか?実際にそれを引き起こしたコードは?あなたが説明しているシナリオはうまくいきます。また、どのようにスクリプトから型を参照していますか?それらは不正な識別子( '#')を持っています。 –

+0

@EliArbelアプリケーションはかなり複雑なので、私はそれをより単純なreproに分解しようとしています。私は上記の正確なInvalidCastExceptionを取得するのが難しいですし、その代りに消費型コンパイルで送信からタイプを使用しようとすると、TypeLoadExceptionを取得しています。私はこれが興味深く、おそらく関連していると感じます。私がこれまで持っていた再現策はここにあります:https://github.com/daveaglick/ScriptingAssemblyReuse – daveaglick

+0

@EliArbelリンクされたGitHubリポジトリと上の両方にサンプルコードを追加しました – daveaglick

答えて

1

私は謎を解決したと思う:

は、ここでは、この問題をトリガーするために、すべてのコードです。次のようになります。

  • スクリプトを作成します。
  • スクリプトのコンパイルを取得しますが、コンパイルオプションをオーバーライドします。具体的には、ScriptClassNameというデフォルト値を使用します。これは、スクリプトAPIによって生成されるものではなくScriptです(Submission#0など)。これは問題の要点です
  • オーバーライドされたオプション&を使用してアセンブリを発行し、ストリームからメモリにロードします。
  • スクリプトを実行します。この時点でと2つの異なるがあり、同じ名前の互換性のないアセンブリがバイト配列を使用してAppDomainにロードされています。
  • ストリームを新しいコンパイルのメタデータ参照として追加し、コードで使用します。これは、オーバーライドされたオプションから生成されたアセンブリを使用しているため、正常にコンパイルされます。 Submission#0のような名前はC#では不正ですので、スクリプトで作成された実際のアセンブリを使用してコンパイルすることはできません。そうでなければ、実際のスクリプトAssemblyインスタンスをグローバルに入れてOnAssemblyResolveに使用することができます。
  • のパラメータを持つBazメソッドを呼び出し、Script+Fooにキャストしようとします。

結論として、私は現在のRoslyn Scripting APIを使用してこれが可能であるとは考えていません。ただし、これらのAPIはスクリプトをコンパイルする唯一の方法ではありません。あなた自身でコンパイルを作成し、SourceCodeKindScriptに設定することができます。私はスクリプトのアセンブリにPDBをロードしたいので(例外には行情報があるので)、in RoslynPadのようなことをやったことがあります。

+0

ああ、ハ!それは理にかなっている。私が理解すれば、あなたはコンパイルをビルドするときにスクリプトコンテナのクラス名を置き換えるだけだと言っているので、スクリプトを評価すると、その中の何かが別のクラスに含まれます(C#では違法になります)。 私は自分の "スクリプトホスト"からスクリプトエンジンにコードを移植することを試みていました(主に、クラスとメソッドを含むクラスへのクールな方法を利用するためです)。今のところ私はカスタムロールスクリプトソリューションに戻るように見えます。 助けてくれてありがとう! – daveaglick

+1

はい。これは、(スクリプトによって作成されたオプションを使用するのではなく) 'ScriptClassName'のデフォルトを持つ' CSharpCompilationOptions'の新しいインスタンスを作成するためです。 –

+0

Gotcha、通常のスクリプトランナーに別の 'CSharpCompilationOptions'を提供できないので、彼らは決して同じ名前を持つことはありません。第二の考えでは、これを回避する方法があるかもしれません。私が 'Script .RunAsync()'メソッドを使用せず、リフレクションを使って発行されたアセンブリからスクリプトを*のみ*評価すると、動作する可能性があります。ちょっと足場が必要かもしれませんが、Roslynのソースからコピーするのは簡単です。少なくとも私はどこを見るか知っています。 – daveaglick

関連する問題