はじめに
ここで1つのプロジェクトに取り組んでいる間に、サードパーティの開発者がその場でプロジェクトの機能を拡張する必要があり、コードをその場で編集できるように、可能な限り拡張しました。 したがって、プラグインは、編集後に絶えず再コンパイルする必要があるため、これにはあまり適していませんでした。 終了:スクリプト。 それ以前は、私は長い間スクリプトを使っていましたが、C ++のLuaでした。 いくつかのマイナスではない場合、良いオプション:
- .NET / Monoの通常の実装またはレイヤーの欠如-私が見たすべてにいくつかの欠陥がありました(たぶん見た目が悪かった-少なくともTaoFrameworkでLuaを見逃していました)
- ほとんどの場合、.NET / MonoランタイムがLua環境と正常にやり取りできるように、多数のバインダーを作成する必要がありました。
それから疑問に思った-。 そして答えはイエスでした。 名前空間「 System.CodeDom.Compiler 」は、私が必要としていたものでした。可能な限り.NET / Mono環境に接続するスクリプトを実装する機能です。 確かに、内部からスクリプトのメカニズムを見ると、たまたまスクリプトではありませんが、C#で記述されたコードは動的にコンパイルされ、実行のためにメモリにロードされます。 しかし、そのような「偽物」にもかかわらず、私は結果を達成しました-アプリケーションで直接オンザフライで再コンパイルすることでコードを編集できました。 同時に、少なくともC#とVB.NETのコンパイラーは.NETとMonoで直接動作するため、Visual Studioやその他の開発ツールがインストールされていないマシンでも動作します。
この記事で説明されている例は、 http : //zebraxxl.programist.ru/ScriptsInDotNet.zipからダウンロードできます。
名前空間 " System.CodeDom.Compiler "
この記事で実際に最も重要なこと。 これは、.NET / Monoアセンブリでコードをコンパイルするためにクラスがコンパイルされる場所です。 作業を行うメインクラスはCodeDomProviderクラスです。 実際、その核となるのは、コンパイル用のデータを単に準備し、必要なパラメーターを使用して選択した言語のコンパイラーを呼び出す、コンパイラー上のインターフェースです。 さて、順番に始めましょう。
また、一般的にどの言語がサポートされていますか?
調べるには、静的メソッドCodeDomProvider.GetAllCompilerInfoを呼び出すだけです 。 このメソッドは、 ompilerInfoの配列を返します。 理論的には、もちろん、1つのコンパイラーが一度に複数の言語をサポートしている可能性がありますが、実際には、これまでこのようなことに遭遇したことはありません。 ただし、コンパイラが.NET / Monoで「多言語」である場合は、個々のコンパイラに関する情報を最初に取得し、次に誰がどの言語をサポートしているかを確認するように正確に作成されています。 例からこの情報を出力する方法の例を次に示します。
0 compiler languages: c# cs csharp 1 compiler languages: vb vbs visualbasic vbscript 2 compiler languages: js jscript javascript 3 compiler languages: vj# vjs vjsharp 4 compiler languages: c++ mc cpp
ここで見られるように、実際には、C#、VB.NET、J#、JScript、C ++の5つの言語を自由に使用できます。 出力は、各コンパイラが1つの言語に対応していることも示しています。
ここで、他のコンピューター(Visual Studioがインストールされていない場合、ITに関係のない友人のマシンとリポジトリ(2.6.7)からのUbuntuサーバー11.04)に同じ結果を追加できます。
また、多くの言語をサポートすると何が得られますか? また、アプリケーションでの処理を考慮することなく、さまざまな言語で同時にスクリプトを記述できることを示しています。 そしてこれにより、サードパーティ開発者の参入レベルが向上します。
そして今、彼女はそうです! 編集
それでは、何かをコンパイルしてみましょう。 C#でテストクラスを作成しましょう。 ファイル「TestScript.cs」にあります。 クラス自体は非常に単純で、2つのメソッドだけです。 1つは静的で、もう1つは静的ではありません。 どちらも単純にテキストをコンソールに出力し、文字列を返します。 また、1つのパラメーターが静的に渡されます。
コンパイルを開始するには、コンパイルするものを取得する必要があります。 これを行うには、静的メソッド「 CodeDomProvider.CreateProvider 」を実行します。 パラメータとして、コンパイルする言語の名前を取ります。 前のステップで受け取った名前自体。
次のステップは、コンパイルオプションを入力することです。 すべてのパラメーターは、「 CompilerParameters 」クラスに格納されます。 最も興味深い分野:
- CompilerOptions-コンパイラーへの追加パラメーター(例:C#の場合はcsc)
- GenerateExecutable-実行可能ファイルにアセンブリを生成します。 一般に、実際には、とにかく行われます。 このフィールドがfalseの場合のみ、実行可能ファイルは一時フォルダーに生成されます。 そして、もしtrueなら、それは「 OutputAssembly 」フィールドが言うところです
- GenerateInMemory-メモリ内にアセンブリを生成します。 繰り返しますが、実際には、最初はアセンブリは実行可能ファイルにあり、そこからロードされます。
- IncludeDebugInformationは非常に便利なフィールドです。 ここにtrueを記述すると、デバッグ情報が生成され、メインアプリケーションと並行してスクリプトを直接デバッグすることができます。
- ReferencedAssemblies-コンパイル時に使用するアセンブリ。 また、目的のフィールド。 私のプロジェクトでは、スクリプトがその構造にアクセスし、アプリケーションと完全に統合できるように、メインアプリケーションの名前をここに書き留めました。
さて、今ではすべてがシンプルです-ソースの取得元に応じてCodeDomProvider.CompileAssemblyFrom *を呼び出します 。 次の3つのオプションがあります。
- CompileAssemblyFromFile-ファイルから。 最後のパラメーターは、コンパイルするファイル名のリストです。
- CompileAssemblyFromSource-文字列から。 ソースファイルを既に読み込んでおり、最後のパラメーターの文字列の配列に格納されていることが理解されます
- CompileAssemblyFromDom -DOMソースツリーから。 html(またはxml)ドキュメントのDOMのようなもの
これらすべてのメソッドの最初のパラメーターは、「 CompilerParameters 」パラメーターです。 その結果、「 CompilerResults 」を取得します。 ここから、コンパイルの進行状況に関するすべての情報を取得できます。 最も興味深い必須フィールド「 CompilerResults 」:
- CompiledAssembly-メモリにロードされた結果のアセンブリ(GenerateInMemoryフィールドが設定されている場合)
- エラー -コンパイラが発行したエラーと警告
したがって、 CompiledAssemblyが null でない場合(つまり、アセンブリが正常にコンパイルされた場合)、スクリプトを完全な.NET / Monoアセンブリとして既に使用して、リフレクションを通じて必要な関数とフィールドにアクセスできます。 この部分については、これ以上詳しく説明しません。すべて同じですが、これは別のトピックです。
スクリプトは簡単ですか?
このような強力な言語をスクリプトとして使用することは確かに良いことです-アプリケーションの機能を拡張する大きな機会を切り開きますが、場合によっては爪詰まりの顕微鏡になることがあります。 コードの複雑さに関する2つのオプションの違いは明らかです。
using System; namespace Script { public class Script { public static void M() { Console.WriteLine(“Hello world”); } } }
または、このオプション:
WriteLine(“Hello World”);
基本的に同じことを行う2つのコード(2番目のバージョンでは、 WriteLineはSystem.Console.WriteLineの呼び出しを意味します)-ただし、「違いを感じてください」
この単純化を実現する方法は多数あります。 そして、それぞれに長所と短所があります。 ただし、それらはすべて1つになります。「小さな」コードから「大きな」コードを動的に生成することです。 たとえば、私が思いつく最も簡単な方法を取りました。
- スクリプト(この例ではMiniScriptWorkerクラス)でグローバルなすべてのメソッドを含む別のクラス(以下、実装クラスと呼びます)を作成しました。
- 「大きな」コードを生成するためのテンプレートを作成しました-実装クラスの1つの子と1つの空のメソッド(「ScriptTemplate.cs」ファイル)
- 「小さな」コードは、その空のクラステンプレートメソッド(ファイル「MiniScript.cs」)の実装に単純に挿入されます。
おわりに
このような簡単な方法で、追加の依存関係をドラッグすることなく、.NET / Monoアプリケーションで非常に強力なスクリプトをほぼ無料で入手できます。 はい-場合によっては、この方法はおそらく冗長でさえありますが、それはすべてタスクに依存します-私の場合、そのような実装は最良かつ最も便利でした。
繰り返しになりますが、この方法の長所は、スクリプトとして、.NET / Mono環境にほぼ完全にアクセスできる強力な言語を使用できることです(コンパイル時に追加するアセンブリをスクリプトで使用可能にするかどうかを決定するのは、どれだけ完全か)。 また、アプリケーションをスクリプトのアセンブリのリストに追加するだけで、アプリケーションとほぼ完全に統合され、ユーザーまたはスクリプト開発者が望むようにアプリケーションを変更できます。
ただし、これも短所です。 スクリプトからメインアプリケーションの一部を非表示にする必要がある場合があります。 このような部分を「 内部 」スコープに隠すことで、これから身を守ることができます。 確かに、この保護は別の問題によって回避される可能性があります-.NET / Mono環境へのそのような幅広いアクセスによりセキュリティが損なわれます。 たとえば、 System.IO.FileStreamを使用してパスワード付きのファイルを開き、その内容を静かに間違った手に送信することを止めることはできません。 残念ながら、私はまだこの問題に対する解決策を持っていません-問題をより詳細に解析することができませんでした。