タグ別アーカイブ: C#の実行

ごみ箱を空にする

ダウンロード

ごみ箱を空にするのが好きな人はいませんか?ごみ箱がいつも空でないと気持ち悪い人はいませんか?

 

実装

Win32API の SHEmptyRecycleBin を呼び出す C# ソースを、Invoke-CsRemote.ps1 で実行しています。

ごみ箱に捨てる

PowerShell でもごみ箱を使いたい

Explorer でファイルを削除した場合、それは通常ごみ箱に移り、ごみ箱を空にするまでは保持されます。従って、うっかり間違えて削除してしまった場合でも復旧は簡単です。 ところが、PowerShell の Remove-Item で削除したファイルは、そのままダイレクトに消えてなくなります。これでは危険なので、ごみ箱に入れるスクリプトを作り、profile.ps1 で「rm」というエイリアスをつけて使っています。 レジストリキーなどファイルシステムでないものは、ごみ箱に入らずそのまま削除されますのでご注意ください。

ダウンロード

以下より、ファイルをごみ箱に入れるスクリプトをダウンロードできます。

 

用例

用例1: 引数で削除

PS> Remove-ToRecycleBin a.txt, b.txt

用例2: パイプラインで削除

PS> Get-ChildItem | Remove-ToRecycleBin

メモリリークのない C# の実行

アプリケーションドメイン

前回、「PowerShell で C# の実行」では、メモリ内に動的アセンブリをコンパイルする方法をご紹介しました。しかし、この方法では、PowerShell を終了するまで解放できない動的アセンブリがメモリ内に次々に増えていくことを問題点として挙げました。今回ご紹介するのは、アプリケーションドメインを使ってアセンブリを解放する方法です。アプリケーションドメインについての詳細は、以下をご覧ください。

 

アプリケーションドメインを使う

.NET のプロセスは、1 つ以上のアプリケーションドメインで成り立っていて、アセンブリは、いずれかのアプリケーションドメイン内にロードされます。アセンブリだけを単独で解放する手段はありませんが、アプリケーションドメインを解放することで、そこに読み込まれたアセンブリが解放されます。そこで、新しくアプリケーションドメインを作成し、そこにコンパイル済みのアセンブリを読み込んで実行し、終了後にアプリケーションドメインを解放する、という方法を採ることにします。

 

ダウンロード

以下より、メモリリーク無しで C# を実行するスクリプトをダウンロードできます。

 

用例

前回の用例がそのまま使えますので、ご覧ください。ただし、前回の Invoke-Cs.ps1 では可能であった、「新しい型を作成して使用すること」が、今回の Invoke-CsRemote.ps1 ではできなくなっています。

用例: 新しい型の作成

PS > Get-Content hello.cs
class Program {
    static object Main()
    {
        return new Program();
    }
    public string Hello()
    {
        return "Hello World!";
    }
}
PS > $a = Invoke-Cs hello.cs
PS > $a.Hello()
Hello World!
    ※ Invoke-Cs では成功
PS > $a = Invoke-CsRemote hello.cs
"5" 個の引数を指定して "InvokeMember" を呼び出し中に例外が発生しました: "呼び出しのターゲットが例外をスローしました。" 発生場所 Invoke-CsRemote.ps1:70 文字:57 +         $result = [Runtime.Remoting.ObjectHandle].InvokeMember( <<<< ‘Unwrap’, ‘InvokeMethod’, $null, $objectHandle, $null)
    ※ Invoke-CsRemote では失敗

これはなぜかと言うと、新しい型を作成したアセンブリが別のアプリケーションドメインにあるために、型情報を読み込むことができないからです。

Invoke-Cs と Invoke-CsRemote の使い分け

Invoke-Cs は、 PowerShell を終了するまでアセンブリを保持し続けます。これはメモリリークの原因となりますので、できる限り Invoke-CsRemote を使うのが良いでしょう。ただし、新しい型を作る必要がある場合には Invoke-Cs のような方法を使わざるをえません。その場合でも、作ったアセンブリをその度に使い捨てにするのではなく、一度作成した型を何度も再利用することで、メモリリークを防ぐことができます。また、頻繁に同じ C# ソースを実行するようであれば、恒常的なアセンブリにコンパイルすることを検討すべきかもしれません。

PowerShell で C# の実行

なぜ C# なのか?

PowerShell は、プログラミング言語としての側面と、手軽なシェルとしての側面を持っています。このバランスは奇跡的ともいえるくらい絶妙なものですが、やはり本業はシェルということで、C# や VisualBasic.NET、JScript.NET などの本格的プログラミング言語に比べると、いくらか省略された機能もあります。

それらの機能が欲しいときには、本格的プログラミング言語に頼るのが最も簡単です。中でも C# は、.NET Framework のために生まれた言語であり、PowerShell の使う .NET Framework と高い親和性を持っています。

なぜすべて C# でやらないのか?

単に C# ソースをコンパイルして実行ファイルを作り、それを実行したのでは、コンパイルの工程が増えます。また、そのプログラムとは文字列しかやり取りすることができません。

インタプリタのごとく、C# のソースを与えれば即座に動き、また、文字列以外のオブジェクトをもやり取りすることができる、そんなことが PowerShell では可能です。

ダウンロード

以下より、C# ソースを実行するスクリプトがダウンロードできます。

用例

用例1: Hello World

PS> Invoke-Cs -Source @"
>> class Program
>> {
>>   static string Main()
>>   {
>>     return "Hello World!";
>>   }
>> }
>> "@
>> Hello World!

用例2: 引数

PS> Get-Content Add.cs
class Program {
    static int Main(int arg1, int arg2)
    {
        return arg1 + arg2;
    }
}
PS> Invoke-CS Add.cs -Argument (5, 15)
20

メモリリークの問題

スクリプト中で他の言語を使うために、よく紹介されているのが、メモリ内に動的アセンブリを作る方法です。今回のスクリプトでも、その方法を使っています。

しかし、これにはひとつ問題があります。変数と違い、動的アセンブリは、ガベージコレクションされません。それどころか、一度作られた動的アセンブリは、PowerShell を終了するまで解放する手段がありません。つまり、そういうスクリプトは何度も呼び出すことによって、その度にメモリリークしていくのです。

以下のコマンドは、現在ロードされているアセンブリのリストを出力します。

PS> [AppDomain]::CurrentDomain.GetAssemblies()

これで調べてみると、 Invoke-Cs.ps1 を実行するたびに無名のアセンブリが追加されていることがわかります。もちろん、PowerShell を終了すれば解放されるので、通常の使い方では問題ないはずですが、常に例外と安全を考慮する、そして「使用上の注意」を減らすのが、正しい開発者の道でしょう。次回は、用途は多少制限されますが、メモリリークのないスクリプトをご紹介する予定です。