Wolfram言語から.NETを呼び出す

はじめに

.NET/Link はWolfram言語ユーザが任意の.NETの型とWolfram言語から直接インタラクトすることができるようにしてくれる.ユーザはWolfram言語言語の中で直接オブジェクトを作成し,メソッドやプロパティを呼び出すことができる..NETコードを書いたり,使いたい.NETの型を特別な方法で準備したりする必要はない.またWolfram Symbolic Transfer Protocol (WSTP)についての知識も全く必要ない.実際,.NETのすべてがWolfram言語のトランスペアレントな拡張となり,現存あるいは将来の.NETの型がすべてWolfram言語言語自体で書かれたもののようにさえ思えるようになる.

この便利さをここでは「インストールできる.NET」と呼ぶ.というのは .NET/Link は,Wolfram言語がこれまででも常にInstall関数を通して他の言語で書かれた拡張をプラグインすることができている能力を一般化するものだからである.しかし,CあるいはC++といった他の言語に比べて,.NET/Link は中間段階を完全になくしてしまうものであり,このために.NETがWolfram言語のトランスペアレントな拡張になったということができるのである.

.NETはインタープリタ型環境と呼ばれることがあるが,これは実際は誤った呼び名である..NETを使うためには, C#等の言語で完全なプログラムを書き,それをコンパイルしてから実行する必要がある.Wolfram言語ユーザは,関数を試してみたり1度に1行ずつプログラムを構築してテストしてみたりすることができる真の意味でのインタープリタ型でインタラクティブな環境で作業をするという贅沢を味わうことができる..NET/Link はこの同じ生産的な環境を.NETプログラマーに提供する.Wolfram言語が.NETのスクリプト言語になったとさえ言える.

Wolfram言語ユーザにとっては, .NET/Link の「インストールできる.NET」の機能がWolfram言語の拡張としての.NETの型の世界へのドアを開いてくれる..NETユーザにとっては,.NETプログラムをインタラクティブに開発,実験,そしてテストする際のシェルとして,.NET/Link が大変強力で用途の広いWolfram言語環境を使うことを可能にしてくれる.

このユーザガイドではWolfram言語から.NETランタイムに呼出しをかけることについて触れる..NETのアセンブリや型をWolfram言語にロードし,これらの型のオブジェクトを作成し,メソッドとプロパティを呼び出す等のやり方を見ていく.また .NET/Link を使って標準のCスタイルDLL関数だけでなくCOMオブジェクトを呼び出す方法についても触れる.

簡単な例:

ProcessPriority.nb

GUIの例:

Circumcircle.nb

PackageHelper.nb

SimpleAnimationWindow.nb

RealTimeAlgebra.nb

AsteroidsGame.nb

DLLの呼出し:

BZip2Compression.nb

EnumWindows.nb

WindowsAPI.nb

COMオブジェクトの呼出し:

ExcelPieChart.nb

NETLink`パッケージをロードする

.NET/Link を使う前に .NET/Link パッケージをロードする必要がある.

.NETランタイムを起動する

InstallNET関数を使って.NETランタイムを起動する.

Wolfram言語で使用するために.NETクラスやその他の型を積極的に開発しているのであれば,変更したバージョンのクラスをもう1度ロードする前に.NETランタイムを再起動する必要がある.ReinstallNET関数を使って.NETランタイムを終了してから再起動するとよい.ほとんどのユーザについては,.NETを終了したり再起動したりすることが必要になることは全くないはずで,ReinstallNETあるいはUninstallNETを呼び出すことは避けるべきである..NETランタイムがWolframシステムのセッションの多くのプログラムで共有されている可能性があることに注意する必要がある..NETランタイムを終了したり再起動したりすることによって,これらのプログラムに予期せぬ結果をもたらすことにもなり得る.

InstallNET[].NETランタイムを起動し,Wolfram言語から使用できるように準備する
ReinstallNET[].NETランタイムを終了してから再起動する
NETLink[].NETランタイムと交信するのに使われているLinkObjectを与える

.NETランタイムを起動する.

.NETのアセンブリと型をロードする

.NETアセンブリ

.NETのためのプログラムとライブラリはアセンブリと呼ばれる構成単位にパッケージされている.アセンブリは,バージョン付きの自己記述的なバイナリ(DLLあるいはEXE)であり,型(クラス,インターフェース,ストラクト等)とオプショナルなリソースの集合が含んでいると定義することができる.アセンブリは複数のファイルにまたがる(マルチファイルアセンブリ),あるいは単独のファイルの中に1つ以上のアセンブリを含めることが可能であるが,通常の場合アセンブリは単独のDLLまたはEXEのファイルからなる..NETアセンブリはDLL拡張子を取ることができるが,これらのアセンブリの内部は従来のCスタイルのDLLとは大きく異なる.しかし,どちらも他のプログラムによってロードされ呼び出されることを意図したコードのライブラリであるという点で,.NET DLLとCスタイルのDLLは概念的には似ている. EXEアセンブリは直接起動することができる実行可能プログラムであるが,このアセンブリは他のプログラムで使用するDLLのような型をエキスポートすることもできる.

アセンブリはシステムのどこに置いてもよい.システム上のすべての.NETプログラムが容易にアセンブリを見付けられるように,.NET Frameworkはアセンブリを保持しておくことができる特別の場所をシステム上に維持する.この場所はグローバルアセンブリキャッシュ(GAC:Global Assembly Cache)と呼ばれ,Windowsのルートディレクトリのアセンブリサブディレクトリにある..NET Framework自体の一部であるアセンブリは,GACに置かれ,ユーザがインストールする多くの.NETプログラムはそこにアセンブリを置く.アセンブリをGACに置く必要はなく,.NET/Link はシステム上のどこに置かれているアセンブリでも使用することができる.

すべての.NETの型はアセンブリにまとめられるため,Wolfram言語に型をロードする際にはまずそのアセンブリをロードする必要がある.LoadNETAssembly関数を使うとアセンブリを .NET/Link にロードすることができる.アセンブリは,アセンブリファイルへの完全パス等のさまざまなタイプのアセンブリについての情報を指定して,あるいはアセンブリの名前のすべてあるいは一部を使って,ロードすることができる.アセンブリ名については下でより詳しく見ていくが,ここではアセンブリ名はその作成者によって割り当てられるものであって,実際のアセンブリファイルの名前と全く似ても似つかない名前であることもあると言えば十分であろう.簡単なアセンブリの名前の例としてSystem.XMLが挙げられるが,これはXML関係の機能を処理する.NET Framework内のアセンブリである.アセンブリはしばしばそれが定義するもっとも重要なネームスペースにちなんで名付けられる.System.XMLアセンブリの実際のファイル名はSystem.XML.dllであり,GAC内部の奥深くにネストされたどこかに置かれている.

.NET/Link にすべてのアセンブリをロードするように明示的に指示する必要はない..NET Frameworkアセンブリならどれでも,つまり名前がSystemで始まる型(例えばSystem.Windows.Forms.FormSystem.Drawing.RectangleSystem.Data.DataSet等)を含むアセンブリすべてが自動的にロードされる. .NET/Link プログラミングで使用する大部分の型については,システムアセンブリ内に見付けることができる。その他のアセンブリについては,手動でロードする必要がある.このチュートリアルで後ほど説明するLoadNETAssembly関数は,アセンブリを .NET/Link にロードし,それらのアセンブリをWolfram言語から使えるように準備するために使うWolfram言語関数である.

.NETの型

型は.NETプログラミングの基本的な構成単位である.すべての型はクラス,インターフェース,ストラクト(「値の型」),列挙,代表のカテゴリのいずれかに必ず入る..NETのすべてのオブジェクトはいずれかの型のインスタンスである.型の集合の範囲はクラスだけの範囲よりも広いが,型をクラスと考えたほうが分かりやすいこともある.

型はアセンブリで定義される..NET/Link 内で型をロードして使用する場合は,型が入っているアセンブリをまずロードしてから型そのものをロードする必要がある.これらのステップはしばしば1つの操作としてまとめて行うことが可能である.下で説明するLoadNETTypeは型を .NET/Link にロードするために使用するWolfram言語関数で,この関数によって型がWolfram言語から使えるようになる.

LoadNETAssembly

LoadNETAssemblyはアセンブリを .NET/Link にロードしてこのアセンブリに含まれている型をWolfram言語から使えるようにするための関数である.LoadNETAssembly には数多くの異なる引数のシーケンスが含まれる.これだけ異なった種類の引数がすべて使えるということは混乱を招くことのように思われるかも知れないが,その基本的な考えにはアセンブリに関する十分な情報を事実上あらゆる方法で特定できるようにすることによって,.NET/Link がそのアセンブリを見付けられるようにするということがある.

LoadNETAssembly[assemblyName]"System.Web"等の名前に基づいて指定されたアセンブリをロードする
LoadNETAssembly[path]その完全ファイルパスに基づいてアセンブリをロードする
LoadNETAssembly[url]このURLによって提示されたアセンブリをロードする
LoadNETAssembly[assemblyName,dir]アセンブリをその名前とそれが属するディレクトリに基づいてロードする
LoadNETAssembly[assemblyName,context`]アセンブリをその名前とそれが属するアプリケーションコンテキストに基づいてロードする
LoadNETAssembly[dir]このディレクトリ内のアセンブリをすべてロードする
LoadNETAssembly[context`]このアプリケーションコンテキスト内のアセンブリをすべてロードする

アセンブリをロードする.

以下はLoadNETAssemblyを使って.NET Frameworkの部分であるアセンブリをロードする例である.

LoadNETAssemblyの戻り値はNETAssemblyの式である.これは .NETオブジェクト自体ではなく,アセンブリの指定を引数として取るようなさまざまな関数内にロードされたアセンブリを参照するために .NET/Link で使うことができる特別な式に過ぎない.

上の例で使われた名前はアセンブリの単純名である.アセンブリの実際のフルネームあるいは表示名はもっと長く,バージョン情報やその他の情報が含まれている.特定のバージョンがロードされるように強制したい場合にフルネームを使うとよい.(ただし,下のコマンドを今実際に実行してもご使用のマシンにインストールされている.NET Frameworkがここに書かれたバージョンと全く同じものでない限り不成功に終ることに注意.)

このアセンブリはGACにあるので,その他の位置に関する情報がなくてもその名前だけに基づいてロードすることができる.

LoadNETAssemblyのもう1つの例として例えば,あるアセンブリを手に入れたか,あるいはある.NET 言語を使って自分でアセンブリを作成したかして,そのアセンブリをWolfram言語から使えるように .NET/Link にロードしたいとしよう.アセンブリはファイルc:\MyProgram\Bin\Debug\MyAssembly.dllの中に入っているとする.以下がそれをロードする方法である.

アセンブリをそのパスを指定することによってロードする方法は,自分で作成したアセンブリをロードする際に便利である.

LoadNETAssemblyを使って,Wolframシステムアプリケーションディレクトリ内のアセンブリのサブディレクトリからアセンブリをロードすることも可能である.これは .NET/Link を使うアプリケーションを作成している開発者を対象とした関数である.MyAppと呼ばれるWolframシステムアプリケーションディレクトリがあって,それがWolframシステムアプリケーションが通常インストールされる場所のいずれか(例えば<Mathematica dir>\AddOns\Applications )にインストールされている場合には,MyAppディレクトリに「assembly」という名のサブディレクトリを作り,アセンブリのディレクトリの中にアプリケーションで必要となる余分のアセンブリをすべて入れておくことができる.そうするとアセンブリの名前とアプリケーションに対応するコンテキストを与えてやることによってアプリケーションのWolfram言語コードがアプリケーションアセンブリの1つをロードすることができる.

この方法で,アプリケーション開発者は自分たちのアプリケーションレイアウト内のprivateアセンブリをバンドルすることができ,アプリケーションのユーザにGACにアセンブリをコピーする等の特別のインストールのステップを踏むように求める必要がなくなる.

LoadNETType

LoadNETTypeは.NETの型をロードしてWolfram言語から使えるようにするWolfram言語関数である.明示的にLoadNETTypeを呼び出す必要がない場合も多い..NETオブジェクトがWolfram言語に返されるたびにその型がロードされる.これはつまり,特定の型の新しいオブジェクトを作成したい場合に,NETNewを呼び出すだけで,オブジェクトがWolfram言語に返されたときに型がロードされるようになる.直接LoadNETTypeを呼び出す最大の理由は,静的メソッドあるいはプロパティを型から使いたいような場合である.そのような場合,NETNewでオブジェクトを作成しているわけではないので,型を手動でロードする必要がある.

LoadNETType[typeName]指定された型をロードする
LoadNETType[typeName,assemblyName]指定されたアセンブリから型をロードする
LoadNETType[typeName,NETAssembly]NETAssembly式で特定されたアセンブリから型をロードする
LoadNETType[typeName,assemblyName,context`]指定されたアプリケーションコンテキストに属する指定されたアセンブリから型をロードする

型をロードする.

以下はLoadNETTypeを使った簡単な例である.

LoadNETTypeの戻り値はNETTypeの式である.これは.NETオブジェクトそのものではなく,引数として型の指定を取るさまざまな関数における.NETの型を参照するために .NET/Link で使うことができる特別の式に過ぎない.

ネームスペース接頭辞を含めた型のフルネーム(この場合はSystem.Windows.Forms)を供給する必要があることに注意されたい.ロードを成功させるためには,型が属するアセンブリはすでにLoadNETAssemblyを使ってロードされた状態になければならない.先に述べたようにSystemのすべての型に対するアセンブリは .NET/Link で必要とされる場合に自動的にロードされるので,上の例でSystem.Windows.Formsアセンブリを手動でロードする必要がないのである.

ロードされたアセンブリと型を見る

ユーティリティ関数であるLoadedNETAssembliesLoadedNETTypesを使って,現行のWolframシステムセッションにどのアセンブリと型がロードされているかをチェックすることができる.これらは主にデバッグのための関数である.

LoadedNETAssemblies[]Wolfram言語にロードされるすべてのアセンブリのリストを返す
LoadedNETTypes[]Wolfram言語にロードされるすべての型のリストを返す

ロードされたアセンブリと型を見る.

静的な型のメンバのコンテキストと可視性

LoadNETTypeには静的なメソッドとフィールドのネーミングと可視性を制御することができる2つのオプションがある.これらのオプションを理解するためには,これらのオプションが解決してくれる問題を理解する必要がある.このことを説明するために,まだどのようにして.NETメソッドを呼び出すのかということについて触れていないので,少し先走りして話を進める必要がある.型がロードされると,Wolfram言語内に定義が作成されてそのクラスのオブジェクトのメソッド,プロパティ,そしてフィールドを呼び出すことができる.静的メンバは非静的メンバとは全く異なる形で扱われる.例えばネームスペースMyCompany.UtilitiesMyCompanyという名前のクラスがあって,このクラスにはFooという名前の静的メソッドが含まれているとする.このクラスをロードするときにFoo[args]のような名前で呼び出すことができるように,Foo に対する定義を設定する必要がある.それでは,一体どのようなコンテキストで,シンボル Foo を定義して,このコンテキストが可視である(つまり$ContextPath上にある)ようにすることが望ましいのであろうか.

.NET/Link は常にFooの定義をその完全修飾クラスネームであるMyCompany`Utilities`MyClass`Fooのミラーとなるようなコンテキストで作成する.これは別のコンテキストにあるかも知れないFooという名前のシンボルとの衝突を避ける目的で行われる.しかし,MyCompany`Utilities`MyClass`Foo[args]のように毎回コンテキストのフルネームをタイプしてFooを呼び出さなければならないのは要領が悪いことのように思えるかも知れない.オプションAllowShortContext->True(デフォルト設定)を使うと,階層的なネームスペース接頭辞を必要としないクラスネームだけからなる短縮されたコンテキストにアクセスできるようなFooの定義を .NET/Link が作成することにもなる.上の例では,つまりFooを単にMyClass`Foo[args]として呼び出すことができるということである.Wolframシステムセッションにすでに同じ名前を持つコンテキストが存在するために,コンテキストの短縮名を使うことを避けなければならないような場合には, AllowShortContext->Falseを使うとよい.こうすることによって,すべての名前は「深い」コンテキストにのみ置かれるようになる.AllowShortContext->Trueを使った場合でも,静的なものの名前も深いコンテキストに置かれるため,シンボルを参照する際に常に深いコンテキストを使うことも可能である.

つまり,AllowShortContextでシンボル名が定義されるコンテキストを制御することができる.もう1つのオプションであるStaticsVisibleはこのコンテキストが可視になっている($ContextPath上に置かれている)かどうかを制御する.このデフォルトはStaticsVisible->Falseであるので,MyClass`Foo[args]の場合のようにシンボルを参照するときにコンテキスト名を使う必要がある.StaticsVisible->Trueと設定されると,MyClass`$ContextPath上に置かれるので,Foo[args]と書くだけでよい.デフォルト設定を Trueとすることは少し危険である.というのは,クラスをロードするたびに数多くの名前が突然Wolframシステムセッションの中で作成され可視にされるようになる可能性が潜在的に存在し,同じ名前のシンボルがすでに存在する場合にさまざまな「シャドーイング」の問題が起る可能性があるからである.(コンテキストとシャドーイングの問題についてはコンテキストを参照のこと.)

このため,StaticsVisible->Trueは自分が書いたクラス,あるいは使い慣れたコンテンツのクラスに対してだけ使用する方がよい.そのような場合には,タイプしなければならない量が減り,コードがもっと読みやすいものとなり,クラスネームの接頭辞をタイプし忘れるというような安易なバグを防ぐことができる.典型的な例としては由緒ある「addtwo」のWSTPの例題プログラムを実装することが挙げられる.C#では,これは下のような形になる.

デフォルト設定のStaticsVisible->Falseを使うと,AddTwoAddTwo`AddTwo[3, 4]として呼び出す必要がある. 設定StaticsVisible->Trueを使うことによってもっと分かりやすくAddTwo[3, 4]と書くことができるようになるのである.

これらのオプションは静的なメソッドとフィールドにだけ使えるものである.後に触れるように,非静的なものはコンテキストや可視性の問題が完全になくなるような方法で処理される.

StaticsVisible->True特別なコンテキストの中でではなく,静的なメソッドとフィールドをその名前だけでアクセスできるようにする
AllowShortContext->False階層的なネームスペースのフルネームをミラーするコンテキストの中でのみ静的なメソッドとフィールドにアクセスできるようにする

LoadNETTypeのオプション.

.NETとWolfram言語の間で型を変換する

.NETオブジェクトを作成してメソッドを呼び出す操作を行う前に,Wolfram言語と.NETの間の型のマッピングを調べる必要がある..NETメソッドが結果をWolfram言語に返す際に,結果は自動的にWolfram言語式に変換される.例えば,.NETの整数の型(例えばInt32Byte等)はWolfram言語の整数に変換され,.NETの実数の型(SingleDouble)はWolfram言語の実数に変換される.下の表では変換のすべての事例を示している.これらの変換は両方向に行えるものであり,例えばWolfram言語の整数がByteの値を必要とする.NET メソッドに送られると,整数は自動的に.NET Byteに変換される.

この表では.NET Frameworkで使用される型の名前が挙げられている.異なる言語ではこれらの内在する型をマップするのにそれ独自のキーワードを使うことが多い.例えばC#では,キーワードintInt32の型のエイリアスであり,Visual Basic .NETではInt32の型はIntegerと呼ばれる.

.NETの型
Wolfram言語の型
Byte, SByte, Char, Int16, UInt16, Int32, UInt32, Int64, UInt64Integer
DecimalIntegerあるいはReal
Single, DoubleReal
BooleanTrueあるいはFalse
StringString
ArrayList
controlled by userComplex
ObjectNETObject
Expr任意の式
nullNull

.NETとWolfram言語で対応する型.

.NETの配列は適切な深さのWolfram言語リストにマップされる.したがって,double[](C#表記)を取るメソッドを呼び出す際には,それに{1.0, 2.0, N[Pi], 1.23}を渡すかも知れない.同様に,整数の深さ2の配列(C#表記ではint[,])を返すメソッドはWolfram言語に式{{1, 2, 3}, {5, 3, 1}}を返すかも知れない.

オブジェクトを作成する

.NETオブジェクトを構築するためにはNETNew関数を使う.NETNewの第1引数はLoadNETTypeから返されるNETType式として,あるいは型の完全修飾名(つまりネームスペース接頭辞を含む)を与える文字列として指定されるオブジェクトの型である.オブジェクトのコンストラクタに任意の引数を供給したいような場合には,引数は型の後ろに数列として続く.

NETNew[typeName,arg1,...]指定されたクラスの新しいオブジェクトを構築して,それをWolfram言語に返す
NETNew[NETType,arg1,...]指定されたクラスの新しいオブジェクトを構築して,それをWolfram言語に返す

.NETオブジェクトを構築する.

例えば,以下は新しいFormを作成する.

NETNewからの戻り値は,山カッコの中に入っているということを除いて,頭部NETObjectを持つように見える変な式である.山カッコは,式が表示されている形式はその内部表示形式とは全く異なるということを示している.これらの式はNETObject式と呼ばれる.NETObject式は型の名前を示すような形で表示されるが,ばらばらにしたり中をのぞいたりすることができないという意味で不透明であると考えられるべきものである.これらの式はNETObject式を取る .NET/Link 関数でのみ使うことができる.

NETNew は渡されようとしている引数の型に適切な.NETのコンストラクタを呼び出し,事実上オブジェクトの参照となるものをWolfram言語に返す.NETObject式はこのように,C#あるいはVisual Basic .NETのような.NET言語におけるオブジェクトの参照と大変よく似ている.NET オブジェクトへの参照であるとして考えられるべきである.Wolfram言語に返されるものは,どの型のオブジェクトを構築しているかにかかわらず,大きなものではない.特にオブジェクトのデータ(つまりそのフィールド)はWolfram言語に送り返されない.実際のオブジェクトは.NET側に残され,Wolfram言語はそれに対する参照を得る.

上の例ではクラスはその名前を文字列として与えることによって指定された.代りにNETType式を使うこともできる.NETType式はクラスを特定するLoadNETTypeによって返される特別式である.クラスネームを文字列として指定すると,すでにロードされていない場合にはクラスがロードされる.

NETNewだけがWolfram言語で.NETオブジェクトへの参照を与える方法ではない.多くのメソッドとプロパティがオブジェクトを返し,そのようなメソッドやプロパティを呼び出すときはNETObject式が作成される.そのようなオブジェクトはNETNewを使って明示的に構築するオブジェクトと同じ方法で使うことができる.

メソッド,プロパティ,フィールドを呼び出す

構文

.NET メソッドを呼び出してフィールドにアクセスするためのWolfram言語構文は,C#およびVisual Basic .NETで使用される構文に大変よく似ている.下のボックスは,Wolfram言語とC#でコンストラクタ,メソッド,プロパティ,フィールド,静的メソッド,静的プロパティ,静的フィールドを呼び出す方法を比べたものである..NETを使うWolfram言語プログラムはC#(あるいはVB .NET)プログラムとほとんど全く同じように書かれていることが分かる.ただし,Wolfram言語では引数に()ではなく[]を使い,「メンバアクセス」演算子として「.」(ドット)ではなく@を使う.

例外は,静的メソッドにWolfram言語ではコンテキストマーク`をC# およびVBで使われるドットの代りに使うということである.これらの状況におけるドットの使用は実際(C++における「::」のような)スコープ変換演算子ようなものであるので,この用法はこれらの言語における使用方法に匹敵するものである.Wolfram言語ではこの用語を使用しないが,Wolfram言語のスコープ変換演算子はコンテキストマークである..NET のネームスペースの名前は直接Wolfram言語の階層的なコンテキストにマップする.

コンストラクタ
C#:MyClass obj = new MyClass(args);
Wolfram言語:obj = NETNew["MyClass", args];
メソッド
C#:obj.MethodName(args);
Wolfram言語:obj@MethodName[args]
プロパティとフィールド
C#:obj.PropertyOrFieldName = 1; value = obj.PropertyOrFieldName;
Wolfram言語:obj@PropertyOrFieldName = 1; value = obj@PropertyOrFieldName;
静的メソッド
C#:MyClass.StaticMethod(args);
Wolfram言語:MyClass`StaticMethod[args];
静的なプロパティとフィールド
C#:MyClass.StaticPropertyOrField = 1; value = MyClass.StaticPropertyOrField;
Wolfram言語:MyClass`StaticPropertyOrField = 1; value = MyClass`StaticPropertyOrField;

C#とWolfram言語の構文比較.

読者の中には,関数を引数に適用するためのWolfram言語演算子として@を使うことになじみがある方もおそらくいらっしゃるであろう. f @ xは通常もっとよく使われるf[x]と同じである..NET/Link は何か特別の操作のための@を奪うわけではなく,単に普通の関数アプリケーションが少し形を変えたというだけのものである.つまり@を全く使う必要がないということである.以下はメソッドを呼び出すための全く同じ方法である.

最初の形式は,ほとんどの.NET言語の構文をWolfram言語にマップする自然な方法をそのまま維持するもので,このチュートリアルではもっぱらこの形式を使用する.

メソッド,プロパティ,フィールドのいずれかを呼び出し,結果を返すようにする場合,.NET/Link は上に示す表に従って引数と結果をそのWolfram言語表記へ,そして表記から自動的に変換する.

オブジェクト指向の言語では,メソッドとフィールドの名前はそれらが呼び出されるオブジェクトによってスコープされる.つまりobj.Meth()と書く際に 他のクラスにもMethという名前の他のメソッドがあるような場合にでも.NET 言語はobjのクラスに属するMethという名前のメソッドを呼び出しているということを知っている..NET/Link はWolfram言語シンボルへのこのスコープを保存し,同じ名前の既存のシンボルとの衝突が決して起らないようにする.obj@Meth[]と書くと,システム中のMethという名前の他のシンボルとの衝突が起らない.つまり,この呼出しの評価でWolfram言語が使うシンボルMethは,このクラスのために .NET/Link が設定したものである.以下はフィールドを使った例である.まずPointオブジェクトを作成する.

PointクラスはXYという名前のフィールドを持ち,フィールドがその座標を持つ.しかしユーザのセッションの中にもXあるいはYという名前のシンボルがあるかも知れない.評価されたときにそれが分かるXの定義を設定することができる.

今度はXという名前のフィールドの値を設定する.(これはC#あるいはVBではpt.X = 42と書かれる.)

ここではgotchaが出力されていない.Print定義を持つGlobal`コンテキストの中のシンボルXと,このコードの行の評価中に使用されるシンボルXとの間には衝突はない..NET/Link@の右辺にあるメンバの名前にプロテクトをかけるので,これらの名前が可視のコンテキストでこれらのシンボルに対して存在するかも知れない任意の定義と衝突したり,この定義に頼ったりすることがない.

要約すると,非静的なメソッド,プロパティ,フィールドについては,どのようなコンテキストにあっても,つまり現在どの$ContextPathであっても,名前の衝突とシャドーイングを心配する必要は全くない.しかし,静的メンバについてはこの限りではない.静的なメソッドとフィールドはオブジェクトへの参照なしにフルネームで呼び出されるので,名前をスコープするオブジェクトが前面に出ることはない.以下は.NETのガベージコレクタを呼び出す静的メソッドの簡単な例である.静的メソッドを呼び出す前にLoadNETTypeを呼び出して,クラスがきちんとロードされるようにする必要がある.

静的メンバは自身のコンテキスト(この例ではGC`)で定義されるため,名前をスコープすることは通常静的メンバでは問題ない.これらのコンテキストは通常$ContextPathにはないので,Global`コンテキスト,あるいは読み込まれているパッケージに同じ名前のシンボルが存在することを心配する必要はない.セッションにすでにGC`という名前のコンテキストが存在し,自身の関数Collectを持つ場合は,静的メンバの型のフルネームに対応する完全に階層的なコンテキストの名前を使って常に衝突を避けることができる.

.NETの名前に含まれるアンダースコア

.NETの名前にはWolfram言語シンボルでは使ってはいけない文字を含めることができる.唯一共通の文字はアンダースコアである..NET/Link はアンダースコアを型,メソッド,プロパティ,フィールドの名前で「U」とマップする.ただし,このマッピングは必要な場合のみ,つまり名前が文字列としてではなく記号形式で使われる場合にだけ使われる.例えば,My_Classという名前のクラスがあるとしよう.文字列としてこのクラスの名前を参照するときはアンダースコアを使う.

しかし,そのようなクラスで静的メソッドを呼び出す場合には,階層的なコンテキストの名前が記号的であるので,アンダースコアをUに変換する必要がある.

同じ規則がメソッドとフィールドの名前にも適用される.コードでそのような名前を参照する場合には,Uを使う.以下はSome_Propertyという名前のプロパティ名を呼び出す方法である.

型とオブジェクトに関する情報を得る

NETTypeInfo

特定の.NETの型に存在するメソッド,プロパティ,フィールド等の情報を素早く表示できると便利なことがよくある..NET/Link はこの情報を得るためにNETTypeInfo関数を提供する.

NETTypeInfo[typeName]特定の型のメンバすべてについての情報を出力する
NETTypeInfo[typeName,members]特定の型のメンバの希望の型についての情報だけを出力する
NETTypeInfo[typeName,members,"pat"]名前が文字列パターンにマッチするメンバの希望する型についての情報を出力する
NETTypeInfo[obj]オブジェクトの型のメンバすべてについての情報を出力する
NETTypeInfo[NETAssembly]特定のアセンブリの型すべてについての情報を出力する

型とオブジェクトについての情報を得る.

NETTypeInfoは型がすでにロードされていない場合にはその型をロードする.

以下はProcessクラスに関する数多くの情報を表示する.

NETTypeInfoの第2引数は表示したいメンバのオプショナルのリストである.可能な値は,"Type" (型自体について一般的な情報を与える),"Constructors""Methods""Properties""Fields""Events"である.以下はプロパティとメソッドだけを表示する.

以下では"Peak"で始まる名前を持つプロパティだけを表示する.

デフォルトの動作ではC#構文でメンバを表示する. Visual Basic .NETの構文で表示したい場合はLanguageSyntaxオプションを使う.

オプション名
デフォルト値
LanguageSyntax"CSharp"出力がフォーマット化される言語構文は"CSharp"あるいは"VisualBasic"でなければならない
InheritedTrue継承メンバを含むかどうか
IgnoreCaseFalse文字列パターンにマッチする名前の大文字小文字の違いを無視するかどうか

NETTypeInfoのオプション

NETTypeInfoはアセンブリにどの型があるのかを見るのにも便利である.アセンブリを調べるためには, NETAssembly式を第1引数として渡す.NETAssembly式を得るもっとも簡単な方法は(アセンブリがすでにロードされている場合でも)LoadNETAssemblyを呼び出す方法である.以下の行を使うとたくさんの型が表示される.

NETAssembly式を実行している場合には,NETTypeInfoの第2引数は表示したい型のオプショナルなリストである.可能な値としては,"Classes""Interfaces""Structures""Delegates""Enums"がある.以下では名前に"Data"という言葉が含まれているクラスかインターフェースだけを表示する.

その他の便利な関数

NETObjectQ[expr]expr が.NETオブジェクトの有効な参照である場合はTrueを返し,その他の場合はFalseを返す
InstanceOf[obj,type]このオブジェクトが type のインスタンスの場合はTrueを,その他の場合はFalseを返す
GetTypeObject[NETType]NETType式に対応するTypeオブジェクトを返す
GetAssemblyObject[NETAssembly]NETAssembly式に対応するAssemblyオブジェクトを返す

オブジェクトと型のユーティリティ関数.

NETObjectQは式が.NET オブジェクト参照であるかどうかをテストしなくてはならない場合に便利である.関数の定義の中でしばしばパターンテストとして使われる.

.NET/Link は頭部NETTypeLoadNETTypeで返される)およびNETAssemblyLoadNETAssemblyで返される)を持つ特別な式を使って,Wolfram言語の.NET の型とアセンブリを表示する.先に述べたように,型やアセンブリを引数として取る関数(例えばNETNew)にこれらの式を渡すことができる. 特定の型にNETType 式ではなく,実際の.NET Typeオブジェクト参照を使いたい,また同様に特性のアセンブリに使いたい場合があるかも知れない.その場合,GetTypeObjectを使えばNETType式に対応するTypeオブジェクトを,また GetAssemblyObjectを使えば.NET Assembly オブジェクトを得ることができる.

上で見るとNETType式の方がTypeオブジェクトよりもどの型を表示しているのかについてのずっと詳しい情報が得られる.これが .NET/LinkTypeおよびAssemblyのオブジェクトを単に使う代りに,特別なNETTypeおよびNETAssemblyの式を使う理由の1つである.

一旦Typeオブジェクトを得ると,Typeクラスのメソッドとプロパティを使ってそのオブジェクトについて知ることができるようになる.

参照カウントとメモリ管理

Wolfram言語でのオブジェクト参照

先ほどNETObject式の取扱いについて触れた際には,参照カウントや独自性等のよりつっこんだ問題については触れなかった.メソッドあるいはプロパティの結果として,またはNETNewへの明示的な呼出しの結果として,.NET のオブジェクト参照がWolfram言語に返されるたびに,.NET/Link はこのオブジェクトへの参照がこのセッションの前に送られたかどうかをチェックする.もしも送られていなければ,Wolfram言語内にNETObject式が作成され,それに対する定義が数多く設定される.これは比較的時間がかかるプロセスである.このオブジェクトがすでにWolfram言語に送られた場合は,ほとんどの場合 .NET/Link は以前に作成されたものと同じNETObject式を単に作成する.このプロセスの方がずっと速い操作である.

この最後の規則には例外もいくつかある.つまり,オブジェクトがWolfram言語に返されるときに,これと同じオブジェクトが以前Wolfram言語に送られたような場合でも,新しく異なるNETObject式がオブジェクトに対して作られることがある.具体的にはオブジェクトのハッシュ値(オブジェクトの組込みのGetHashCode()メソッドで決定される)が前回Wolfram言語で見られたときから変わった場合はいつも,作成されるNETObject式は異なるものになる.あまりこの点に関して細かいことを心配する必要はないが,NETObject 式を比べてこれらの式が同じオブジェクトを指しているのかどうかを判断するときにはWolfram言語のSameQ関数が使えないということは覚えておいた方がよい.その場合は SameObjectQ 関数を使わなければならない.

SameObjectQ[obj1,obj2]NETObjectobj1 および obj2 が同じ.NETオブジェクトを参照する場合はTrueを,それ以外の場合はFalseを返す

NETObject式を比べる.

以下に例を挙げる.

変数ptは.NET Pointオブジェクトを指す.今度はこれをコンテナに入れて後から取り出せるようにする.

今度は座標の1つの値を変更する.Pointオブジェクトについては,これでそのハッシュ値が変更される.

ptで与えられたNETObject式とArrayListの第1要素をWolfram言語に返すように要求する際に作成されるNETObject式を比べる.これらはどちらも同じ.NETオブジェクトを参照するものであるが,NETObject式は異なる.ArrayList クラスはインデクサ(C# の用語)を定義するため,インデックスで要素を参照する際に[] 表記が使えることを思い出していただきたい.

SameQ (===)を使ってWolfram言語の2つのオブジェクト参照が同じ.NETオブジェクトを指しているのかを判断することはできないので,.NET/Link はこれを行うためにSameObjectQ関数を提供する.

SameObjectQ関数が何の役に立つのか,オブジェクトのEquals()メソッドを呼び出せばいいではないか,と思っておられる方がおそらくいらっしゃるだろう.確かにこの例では正しい結果が返される.

この方法の問題はEquals()がいつもオブジェクト参照を比べる訳ではないという点にある.任意のクラスが自由にEquals()をオーバーライドして,そのクラスの2つのオブジェクトを比べるための希望の動作を提供することができる.クラスの中には,Equals()Stringクラスのようにオブジェクトの「コンテンツ」を比べさせるものもある.このStringクラスは文字列の比較のために使用される.正しいテストを提供する関数は静的メソッドReferenceEquals()である.

SameObjectQは明示的にReferenceEquals()を呼び出す場合と同じ動作を行う便利な関数だと考えることもできる.

稀なケースとしてオブジェクト参照の等価性を何度も比較しなくてはならない場合に,SameQに比べてSameObjectQがゆっくりであるということが問題になる可能性がある.全く同じ.NETオブジェクトを参照する2つのNETObject式がSameQではないようにする唯一のものは,2つのNETObject式が作成される間にオブジェクトのハッシュ値が変わった場合である.これが起っていないことが分かっているのであれば,SameQを使って2つの式が同じオブジェクトを指しているかどうかをテストしても問題ない.

ReleaseNETObject

.NETランタイムには「ガベージコレクション」を呼ばれる組込みの施設があって,プログラムがもはや使わなくなったオブジェクトによって占められているメモリを解放する.オブジェクトへの参照が,おそらく同じように未参照の他のオブジェクトで参照されている以外ではどこにも存在しないときに,そのオブジェクトはガベージコレクションの候補となる.NETNewへの呼出しの結果として,あるいはメソッドかプロパティの戻り値としてオブジェクトがWolfram言語に返されるときには,.NET/Link コードは.NET側のオブジェクトへの特別の参照を持ち,そのオブジェクトがWolfram言語で使用されている間にガベージコレクトされないようにする.特定の.NETオブジェクトをWolfram言語セッションで使わなくてもよくなったことが分かっている場合には,.NET/Link にその参照を解放するように明示的に伝えることができる.これを行う関数がReleaseNETObjectである.Wolfram言語に特定の参照を.NETで解放することに加えて,ReleaseNETObjectはオブジェクトのためにWolfram言語で作成された内部定義もクリアする.後からこのオブジェクトをWolfram言語で使おうとしても使えない.

今度は.NETにもはやこのオブジェクトをWolfram言語から使う必要がないことを伝える.

Wolframシステムセッションからオブジェクトの記号表示が削除されたため,frmを参照するとエラーが出る.解放されたオブジェクトを使おうとすると以下のような出力が返される.

ReleaseNETObject[obj]Wolfram言語で obj をもう使わなくなったということを.NETに伝える
NETBlock[expr]expr の評価中にWolfram言語に返された新しい種類の.NETオブジェクトはすべて expr が終了した際に解放される
BeginNETBlock[]今からマッチするEndNETBlock[]までの間にWolfram言語に返される新しい種類の.NETオブジェクトはすべて解放される
EndNETBlock[]マッチするBeginNETBlock[]以来見られる新しい種類のオブジェクトをすべて解放する
LoadedNETObjects[]Wolfram言語で使用されているすべてのオブジェクトのリストを返す

メモリ管理関数.

ReleaseNETObjectを呼び出すことが必ずしもオブジェクトがガベージコレクトされることを引き起す訳ではない.そのオブジェクトへの他の参照が.NETに存在している可能性も大きい.ReleaseNETObjectが.NETにオブジェクトを捨てるように伝える訳ではなく,Wolfram言語のためだけにオブジェクトを残しておく必要がないことを伝えるだけである.

.NET/Link がWolfram言語に送られるオブジェクトのために維持する参照について重要なことは,何度そのオブジェクトがWolfram言語に返されるかにかかわらず,それぞれのオブジェクトに対して参照は1つしか保存されないということである.ReleaseNETObjectを呼び出した後で,Wolframシステムセッションに存在するかも知れない任意の参照を通してそのオブジェクトを使おうとすることが決してないようにする必要がある.

ReleaseNETObject[frm1]を呼び出すと,Wolfram言語シンボルの frm1が影響を受けるのではなく,frm1 が参照する.NET オブジェクトが影響を受けるのである.このため,frm2を使うこと(あるいはこの同じ Form オブジェクトを参照するこれ以外の方法)も誤りである.

一時的な使用にReleaseNETObjectを呼び出す必要はないことが多い.セッションで.NET を多用するのでなければ,どのオブジェクトが必要であるか,あるいは必要ではなくなったかを気にする必要は普通なく,オブジェクトが増えるまま放っておけばよい.しかし,.NET でメモリ使用が重要になったようなときには,ReleaseNETObjectが提供する追加的なコントロールが必要になることがあるかも知れない.

NETBlock

ReleaseNETObjectは主として他の人が使えるようにコードを書いている開発者のために提供されている.コードがどのように使用されるかを予想することは不可能なので,開発者はコードが作成する不要な参照をコードから取り除くように常に気を付けるべきである.おそらくこの操作に使える最も便利な関数はNETBlockである.

NETBlock は式の評価中に起るオブジェクトの解放のプロセスを自動化する.Wolfram言語プログラムにおいて,NETNewを使って.NETオブジェクトをいくつか作成し,それらのオブジェクトを操作しておそらく他のオブジェクトがメソッドの呼出しの結果としてWolfram言語に返されるようにして,最終的に数字や文字列等の結果を返すようにしなくてはならないことがよくある.この操作中にWolfram言語が出会う.NETオブジェクトはすべてプログラムの存続期間中にだけ必要とされるものであり,これらのオブジェクトはWolfram言語内でBlockおよびModuleによって,そしてC#,C++,Java,およびその他の言語の中でブロックスコーピングコンストラクト(例えば {})によって提供される局所変数に大変似ている.NETBlockを使うと,Wolfram言語の評価中に返された新しいオブジェクトはどれでも一時的なものとして扱われ,NETBlockが終了したときに解放されるべきものであるというプロパティを持つものとして,一連のコードに印を付けることができる.

先の文で「新しいオブジェクト」と言っていることに注目されたい.NETBlockは評価中に出会ったオブジェクトをすべて解放する訳ではなく,初めて出会ったものだけを解放する.Wolfram言語がすでに出会ったことのあるオブジェクトは影響を受けない.つまり,真の意味でその評価で一時的に使われているのではないオブジェクトをNETBlockが積極的に解放してしまうことを心配する必要はない.

NETNewで作成するオブジェクトすべてに対してReleaseNETObjectを単に呼び出すだけでは十分ではない.多くの.NETのメソッドとプロパティがオブジェクトを返すからである.これらの戻り値は重要ではないかも知れない. 他の呼出しに(例えばobj@ReturnsObject[]@Foo[]での例のように)これらのオブジェクトが一緒に縛り付けられているために,これらに指名された変数を割り当てることが全くないかも知れないが,それでもオブジェクトを解放する必要はある.NETBlockの使用は,一連のコードが終了したときに新しい種類のオブジェクトがすべて確実に解放されるようにする簡単な方法なのである.

NETBlock[expr]expr が返されるときにいつも返される.

多くの .NET/Link Wolfram言語プログラムが以下の構造を持つ.

多くのNETObject式を作成し,残りは一時的なものであるのでその中の1つだけを返すというような関数を書くということは大変よくあることである.これを容易にするために,NETBlockの戻り値が単独のNETObjectである場合にはこのオブジェクトは解放されない.

どのオブジェクトがNETBlockから逃げることができるかをもっと制御したい場合は,KeepNETObject関数を使うとよい.単独のオブジェクト,あるいは一連のオブジェクトに対してKeepNETObjectを呼び出すということは,囲んでいる最初のNETBlockが終了するときに,これらのオブジェクトは解放されないということである.しかし外側に囲んでいるNETBlockがある場合には,オブジェクトはその NETBlockが終了したときに解放されるので,オブジェクトが.ネストされた NETBlockの集合から逃げられるようにしたい場合には,各レベルにおいて KeepNETObjectを呼び出す必要がある.もう1つの方法として,KeepNETObject[obj,Manual]を呼び出すことができる.ここでManual引数は,囲んでいるどのNETBlockによってもそのオブジェクトが解放されるべきではないということを .NET/Link に伝える.そのようなオブジェクトが解放されるのは,手動でオブジェクトに対してReleaseNETObjectを呼び出した場合だけである.

KeepNETObject[obj1,obj2,...]NETBlockが終了する際に特定のオブジェクトを解放しない
KeepNETObject[obj1,Manual]任意のNETBlockが終了する際に特定のオブジェクトを解放しない

NETBlockの終了後も.NETオブジェクトを保持する.

以下はKeepNETObjectを使って,2つのオブジェクトを解放することなく返すことができるようにする例である.

BeginNETBlockおよびEndNETBlockを使って,2つ以上の評価を通じてNETBlockと同じ機能を提供することができる.EndNETBlockはWolfram言語に前回のマッチするBeginNETBlock以来返された新しい種類の.NETオブジェクトをすべて解放する.これらの関数は開発中に,セッションに印を設定し,作業を行った後で,その時点以来Wolfram言語に返された新しい種類のオブジェクトをすべて解放したいときに,おもに使用される.BeginNETBlockおよびEndNETBlockはネストすることができる.すべてのBeginNETBlockはマッチするEndNETBlockを持つべきであるが,ネストされたレベルのEndNETBlockを持つような場合にでも, EndNETBlockを呼び出すことを忘れることは大きなエラーとはならない.単にオブジェクトをいくつか解放できなくなるというだけのことである.

LoadedNETObjects

LoadedNETObjects[]は,現在Wolfram言語で参照されているすべての.NETオブジェクトのリストを返す.これにはNETNewで明示的に作成されたすべてのオブジェクトと,.NETのメソッドあるいはプロパティの結果としてWolfram言語に返されたすべてのものが含まれる.ReleaseNETObjectと一緒に,あるいはNETBlockを通して解放されたオブジェクトは含まれない.LoadedNETObjectsはおもにデバックのためのものである.作業中の関数の前後にこれを呼び出すと大変便利である.リストが大きくなると,関数は参照し忘れるので,NETBlockReleaseNETObjectの両方,あるいはどちらか一方の使用をチェックする必要がある.

Enum

.NETの列挙は特別な種類のクラスであり,列挙のそれぞれのメンバはそのクラスの静的定数フィールドとして表される. 列挙定数の値は整数であるが,.NET/Link はそれらをWolfram言語に返すときに整数に変換することはしない.これはなぜならenumの値はおそらく別の.NETメソッドにもう1度返されることになるだけであるからである.だから,これらの値をWolfram言語で整数として操作しようと考えることはほとんどあり得ない.この場合,Wolfram言語内で単なる暗号的な整数値としてよりもクラスのオブジェクトとして表す方がもっと意味のあることなのである.

形の大きさを変えるときに1つ以上の辺に対して固定された位置に形を置くために,しっかり固定したいButtonオブジェクト btn があるとしよう.これを行うためには,ボタンのAnchorプロパティをAnchorStylesのenumからの値に設定する.まず型の静的メンバにアクセスしたい場合には必ず必要になるAnchorStyles型をロードする.

他の任意の静的フィールドと同じように,このenumのメンバを参照する.

enumはWolfram言語では,生の整数値よりもプログラマーにとってはもっと意味のある強く定型化されたオブジェクト参照で表される.この場合の整数値は1である.NETObjectToExpression関数を使ってオブジェクト参照をその整数値に変換することができる.

今度はこれをボタンに適用する.

enumとして定型化された任意の引数については,enumクラスのインスタンス,あるいは生の整数値を渡すことができる.つまり,上の行はもっと理解しがたいものにはなるが,以下のように書くことも可能である.

enumsの中には,その値をビット論理和で結合させることが可能であることを示す[Flags]属性を持つものもある.AnchorStylesのenumは,要素を親コンテナの2つ以上の辺に固定させたい場合もあるので,この属性を持つ.C#ではどのように書かれるのかを以下の例で示す.

これをWolfram言語で行うためには,enumの整数値を得て,NETObjectToExpressionを使えるようにする必要がある.(これがおそらくenumの値をWolfram言語で整数として操作したい唯一のケースである.)

「Out」と「Ref」のパラメータ

.NETはパラメータが参照によって渡せるようにして,その値への変更が呼び出した人にもう一度帰っていくことができるようにする.このような「参照による」パラメータが.NET Frameworkのクラスの中で使われることはめったにないが,サードパーティライブラリの中にはこれをもっと頻繁に使うものもある.C#表記では,このようなパラメータはoutあるいはrefとして印を付けられる.この2つの違いは,refパラメータはメソッドの開始時に初期値が必要であるのに対して,outパラメータでは必要ないという点にある.Visual Basic .NETでは,キーワードByRefを使ってパラメータが参照によって渡されたことを示す.Visual BasicにおけるByRefパラメータはC#におけるrefパラメータのようなものである.Visual Basicでは,outのみのパラメータという考え方は存在しない.IDL表記では,refパラメータは[in, out]と書かれ,outのみのパラメータは[out]と書かれる.

以下はSystem.Uriクラスからのメソッドでのrefパラメータの例である.

このメソッドは文字列(patternパラメータ)と始動indexを取り,必要な場合は%xx形式の16進数表記を解読して文字列内の次の文字を読み取る.またindexの値を解読された文字の最後の少し後ろに進める.refあるいはoutのパラメータを使用するほとんどのメソッドと同様に,このメソッドは2つ以上の情報,つまり解読された文字と文字列の次の位置を返す必要がある.indexパラメータの初期値はメソッドによって使用されるため,ただ単にoutパラメータではなく,refパラメータでなければならない.

outあるいはrefのパラメータ付きのメソッドを,C#およびVisual Basic .NETを含めたほとんどの.NET言語から呼び出すのと全く同じ方法で,Wolfram言語からも呼び出す.refパラメータについては,正しい型(この場合は整数)の初期値を持つシンボルと一緒にメソッドを呼び出す.メソッドが返されると,シンボルに新しい値が割り当てられる.

以下は%20文字(URLでの空白文字[decimal 32]によく使われる符号化)を解読する.

posrefパラメータのスロットに渡されたため,新しい値,つまり%20の後ろにある最初の文字の指数の値が割り当てられる.

refパラメータを呼び出すときによく犯される間違いに,初期値を割り当てることを忘れるということがある.以下はこの誤りの例である.

パラメータがrefではなくoutと印を付けられている場合には,初期値は無視される.したがって,シンボルがメソッドに入ったときの値は,値があったとしても,関係ない.上でも述べたように,Visual Basic .NETではoutのみのパラメータという考え方は存在しない.そのため,Visual Basicで書かれたメソッド内のByRefパラメータは,メソッドが入ってくる値を使わない場合でも,常に正しい型の初期値を必要とする.

Visual Basic .NETと同様に(しかしC#とは違って) .NET/Link ではシンボルの代りに文字通りの値をoutあるいはrefパラメータに渡すことができる.このような場合,パラメータの値になされた変更はすべて失われる.以下はその例である.

「値で」そして「参照で」オブジェクトを返す

参照と値

.NET/Link は特定のWolfram言語式とその対応する.NET 式の間にマッピングを提供する.これはつまりWolfram言語と.NETとの間を移動する際に,これらのWolfram言語式とその対応する.NET式がお互いに相手の形式に自動的に変換されるということである.例えば,.NETの整数の型(Int32Int16Byte等)はWolfram言語の整数に変換され,.NETの実数の型(SingleおよびDouble)はWolfram言語の実数に変換される.もう1つのマッピングでは,.NETオブジェクトはWolfram言語でNETObject式に変換される.これらのNETObject式は.NET オブジェクトへの参照である.つまり,これらはWolfram言語の中では .NET/Link で操作されるということを除いて何の意味も持たない.しかし,.NET オブジェクトの中にはWolfram言語の中で重要な値を持つものもあり,これらのオブジェクトはデフォルトで値に変換される.このようなオブジェクトの例は文字列と配列である.

それでは,.NETオブジェクトはいくつかの特別なケースを除いてデフォルトでWolfram言語に「参照で」返されると言える.これらの特別なケースには数字,文字列,配列,そしてブーリアンがある.これらの例外的なケースは「値で」返されると言える.「.NETとWolfram言語の間で型を変換する 」に掲載の表では,これらの特別な.NETオブジェクトの型がどのようにWolfram言語の値にマップされるかを示している.

要約すると,Wolfram言語で意味のある値の表記を持つ.NETオブジェクトはすべてこの値に変換される.これは単にそれが最も便利は動作であるからである.しかし,このデフォルトの動作をオーバーライドしたいと思う場合もときにはあるかも知れない.オーバーライドしたい理由として最も一般的なものに,WSTP上で大きな式の不要なトラフィックを避けたいということがある.

ReturnAsNETObject[expr]expr で返される.NETオブジェクトは参照の形式である
NETObjectToExpression[obj].NETオブジェクトの値をWolfram言語式として与える

「参照で」と「値で」のコントロール.

ReturnAsNETObject

クラスMyClassarrayAbs()という名前の静的メソッドがあり,このメソッドがdoubleの配列を取って,それぞれの要素が引数配列において対応する要素の絶対値である新しい配列を返す場合を考えてみよう.C#構文によるこのメソッドの宣言はしたがってdouble[] ArrayAbs(double[] a)のようになる.以下はこのようなメソッドをWolfram言語から呼び出した場合である.

上の例がおそらくメソッドが作動するようにしたい場合に取る方法であろう.つまりWolfram言語リストを渡してリストをもらうという方法である.今度はArraySqrt()という名前のメソッドで,Abs()関数の代りにSqrt()関数を実行することを除いてはArrayAbs()のように作動する別のメソッドを考えてみよう.

この計算では,もとのリストは MathLink を通して.NETに送られ,.NET配列がこれらの値を使って作成される.その配列はArrayAbs()への引数として渡され,ArrayAbs()自体が別の配列を作って返す.その後この配列はリストを作成するためにWSTPを通してWolfram言語に送り返され,リストはすぐにArraySqrt()の引数として.NETに送り返される. 配列データをWolfram言語に送り返すのは時間の無駄であることは明らかである.というのは,もうすでに.NET側に完璧な配列(ArrayAbs()メソッドによって返された配列)が存在していて, ArraySqrt()に渡されるばかりになっているにもかかわらず,代りにその内容と同じ値を持つ新しい配列として再び.NETにすぐ返すためだけにWolfram言語へ送り返すことになるからである.この例では,そのコストはたいしたことがないが,これが20万個の要素を持つ配列であったらどうであろうか.

必要なのは,配列データを.NETに留めた状態で,実際のデータ自体ではなく配列への参照だけを返すことができる方法である.これはReturnAsNETObject関数で行うことができる.

以下はReturnAsNETObjectを使った計算がどのようになるかを示している.

上ではArraySqrt()がWolfram言語の実数リストである引数と一緒に呼び出されていたが,ここではdoubleの一次元配列である.NET オブジェクトへの参照と一緒に呼び出されている.すべての引数はWolfram言語の値,あるいは適切な型の.NET オブジェクトへの参照と一緒にWolfram言語から呼び出すことができる.

要約すると,ReturnAsNETObject関数は通常Wolfram言語の値に変換されるオブジェクトを返すメソッドとプロパティが代りに参照を返すようにする.これはWolfram言語と.NETの間で多量のデータが不必要に渡したり渡されたりすることを避けるための最適化として使用されることが多く,そのため大変大きな配列や文字列に主として役立つ..NET のほとんどの型のオブジェクトはWolfram言語の「値で」の表示では意味を持たず,常に「参照で」返されるものである.ReturnAsNETObjectはこれらの場合不必要である.

NETObjectToExpression

前のセクションでは,ReturnAsNETObject関数を使って通常Wolfram言語に値で返されるオブジェクトを参照で返されるようにする方法を見た.これと反対の操作を行う関数,つまり参照を取ってそれをその値の表現に変換する関数が必要である.これを行うのが関数NETObjectToExpressionである.

Wolfram言語で意味のある「値」を持つような.NETオブジェクトを扱っている場合には,Wolfram言語に送られたときにオブジェクトは自動的にその値にほとんどかならず変換される.しかしこの規則にも例外がいくつかあり,そのような場合に便利なのがNETObjectToExpressionである.上でReturnAsNETObject関数を使ってオブジェクトを強制的に参照として返すことができることを見たが,参照を得るもう1つの方法として,NETNewあるいはMakeNETObjectを呼び出す方法がある.これらの関数は常にオブジェクト参照を返すからである.以下でStringオブジェクトを明示的に作成する.

以下は文字列参照をWolfram言語文字列へ変換する.

オーバーロードされた演算子 」のセクションでMakeNETObject関数を紹介するが,この関数は,NETNewを使う場合よりも簡単に,Wolfram言語の文字列,数字,配列から.NETオブジェクトを構築することができる.

NETObjectToExpressionも列挙およびコレクション(ICollectionインターフェースを実装するオブジェクト)のように通常参照で返されるオブジェクトの型をその値の表現に変換する.列挙の型については上の「 Enum 」セクションで説明されている.コレクションはWolfram言語上ではリストとして有効に操作されることができるが,配列と違ってコレクションは反復するのに高くつくことがあるため,.NET/Link はそれらを参照として残し,自動的にリストに変換するということはしない.リストが必要な場合は,NETObjectToExpressionを使うとよい.

以下はコレクションオブジェクトを作成する:

今度はこれに値を投入する.

NETObjectToExpressionはオブジェクト参照をリストに変換する.

オーバーロードされた演算子

.NET言語の中には,+,>等のオーバーロードされた演算子をクラスに定義することができるものもある..NET言語では演算子のオーバーロードをサポートすることは必要ではない. C#とC++ではサポートすることができるが,Visual Basic .NETではできない.数多くのオーバーロードされた演算子を定義するクラスの例としてSystem.TimeSpanがある.例えば,これは+演算子を定義して,2つのTimeSpanオブジェクトを数字であるかのように足すことができる.以下はC#コードでの例である.

.NET言語でオーバーロードされた演算子をサポートすることは必須ではないので,これらの演算子を定義するクラスは常に同じ操作を別の方法でも行うことができるようにしておくべきである.一般的にこれはメソッドの呼出しを通して行われる.TimeSpanクラスは,オーバーロードされた演算子をサポートしないVisual Basic .NETのような言語で使えるAdd()メソッドを提供する.

.NET/Link はWolfram言語構文でのオーバーロードされた演算子はサポートしないので,同じ操作を行うメソッドを探す必要がある.以下は2つのTimeSpanオブジェクトを足すためのAdd()メソッドである.

クラスの作成者が.NETのガイドラインを無視して,オーバーロードされた演算子としての操作と同じ操作を行う別のメソッドを提供しなかったような場合でも,その操作をWolfram言語から呼び出すことは可能である.これはC#およびC++でのオーバーロードされた演算子は,XXXが操作の名前であるop_XXXのような名前を持つ特別な静的メソッドを通して内部で作成されるからである.クラスの作成者がこれらのメソッドを直接書くのではなく,コンパイラが作成するのである.しかし,これらのメソッドは他の任意のメソッドと同様にWolfram言語から直接呼び出すことができる.以下はTimeSpanクラスの中にあるあいまいな名前付きのメソッドすべてである.

TimeSpanクラスの中にAdd()メソッドがないような場合にでも,op_Addition()メソッドを呼び出して一緒に混合させることができる.Wolfram言語から.NETの名前を呼び出す場合は常にアンダースコアはWolfram言語シンボルの中では適切な文字ではないので,アンダースコアの文字をUにマップしなくてはならないことに注意されたい.

キャスティング

はじめに

.NETプログラムにはよくキャストが含まれる.キャストでは1つの型のオブジェクトが別の型のものに変換される.典型的な例は,プログラマーがおそらくObjectを返すためにタイプされたメソッドの呼出しの結果得られるような型Objectの変数を持ち,その型からのメソッドが変数を呼び出せるように変数を生成された型にキャストしたいと考えるような場合である.これはコレクションクラスを扱っているときによく起る.というのは,コレクションクラスは任意の型のオブジェクトを持つことができて,そのメソッドはObject以上に特定のものを返すためにタイプするのではないからである.以下はIList.Itemプロパティのシグナチャであり,これは特定の指数でオブジェクトをリストから抽出する.

C#ではItemプロパティはクラスのインデクサなので,上記の珍しい構文で書かれるか,あるいはVisual BasicでのようにItemという名前のプロパティとして書かれるかすることができる. 例えば,文字列をIListに入れて,その後Itemプロパティを使って1つ文字列を抽出するような場合には,その文字列を文字列変数に割り当てられるように文字列にキャストする必要がある.

Visual Basic .NETではキャストはCType()関数を使って行われ,Option Strictが設定される場合にのみ必要である.

この型のキャストは継承階層を下向きに(親の型から派生型へ)キャストしているためダウンキャストと呼ばれる.このようなダウンキャストはおそらくキャスティングの中では(intからbyteへの変換のように,1つの型の数字を別の型のものに変換するために使用されるキャスティングを除いては)最もよくある形のキャスティングである.

.NET/Link で複製しようと思っているようなC#およびVisual Basic .NETのプログラムの中全体にダウンキャストが散らばっているのを見かけるが,.NET/Link ではダウンキャストが重要になることはほとんど全くない.これは参照の型同士の間でのキャスティングはおもにコンパイルのときに行われる操作だからである.上記のサンプルコードでは,プログラマーはコンパイラにたった今ArrayListから抽出したオブジェクトは文字列であることは分かっていて,コンパイラにそのようにプログラマーがオブジェクトを取り扱えるようにして欲しいと伝えている.しかし,.NET/Link ではオブジェクトは常にその真のランタイムの型としてWolfram言語に返されるので,Itemプロパティを呼び出すときに,型Stringのオブジェクトを受け取るのである. ItemプロパティがObjectを返すためにタイプされているということは全く意味を持たない.実際,すべてのオブジェクトはその真のランタイムの型を持っていて,継承階層の下のレベルにはオブジェクトをキャストする型が存在していないので, .NET/Link ではダウンキャストは意味をなさなく不可能でさえある.

この前置きの目的は,.NETプログラムで見かけるキャストの大部分が .NET/Link では無意味であるということをはっきりさせることにある.キャストは,数的な型の間の変換(これはもしも必要になったとしても,Wolfram言語では別の方法で容易に行うことが可能である),あるいはObjectのような一般的な型からもっと特定の型へのダウンキャスト(これはコンパイラのために行われることであり,.NET/Link ではコンパイルの段階はないので意味がない)のいずれかである.

しかし .NET/Link 内でキャストが必要になる場所もある.その1つがCOMオブジェクトを使用する場合である.これは「COMをWolfram言語から呼び出す」セクションで触れるので,ここでは扱わない.その他の .NET/Link でキャストが必要な場合はすべてアップキャストである.ここでは,オブジェクトは親のクラスかインターフェースにキャストされている.アップキャストが必要なのは次の3つの場合である.

親クラスから隠れたメンバを呼び出す

子クラスは,newキーワード(C#)あるいはShadowsキーワード(Visual Basic .NET)を使って同じ名前を持つメンバを宣言することによって,その親クラスのメンバを隠すことができる.次のクラスを考えてみよう.

Childクラスのインスタンスを持っていて,Foo()Parent実装を呼び出したいとすると,ChildインスタンスをParentクラスにキャストすることによってこれを行うことができる.

この動作は,Foo()メソッドがvirtualと宣言されたか,それともParentクラスにないと宣言されたかにかかわらず同じである.newキーワード(Visual Basic .NETではShadows)は,もしも忘れるとコンパイラが警告を生成するが,厳密には必要ではない.

.NET/Link を使ってFoo()Parentクラス実装を呼び出すためには,CastNETObjectを使ってオブジェクトをParentクラスにC#コードでなされたのと全く同じ方法でキャストするとよい.

もちろん2つの参照は同じオブジェクトを参照する.

明示的インターフェースの実装

クラスが2つのインターフェースを実装し,そのそれぞれが同じ名前のメソッドを持つ場合,それぞれのインターフェースメンバに別々の実装を与えることを選ぶことも可能である.これは明示的インターフェースの実装と呼ばれる.この方法は一般に2つのインターフェースからのメソッドが概念的に全く異なっていて,両方のインターフェースのコントラクトを満足するような単独の実装を提供することができない場合にのみ使用される.以下は.NET SDKのドキュメントからの例を簡約したものである. BoxクラスはIEnglishDimensionsおよびIMetricDimensionsを実装し,この両方がLength()メソッドを持つ.もちろん両方のインターフェースに使えるような単独のLength()の実装はない.

これはこれらのメソッドを呼び出す方法である.Length()メソッドを呼び出す前に,オブジェクトをIMetricDimensionsあるいはIEnglishDimensionsのインターフェースにキャストしなくてはならない.

以下は同じことを .NET/Link を使って行った場合である.

以下は実例である.Arrayクラス(これは.NETのすべての配列に対する親クラスである)はIListインターフェースからのメソッドに明示的インターフェースの実装を使う.Arrayクラス等のクラスがそのメソッドのいくつかに明示的インターフェースの実装を使う場合には,ドキュメントの中でこれははっきり述べられているべきである.IListインターフェースからのメソッドの1つにContains()がある.ArrayIListを実装するにもかかわらず,Contains()を直接配列オブジェクトについて呼び出すことはできない.

IListにキャストするとうまくいく.

privateクラスとpublicインターフェース

.NET/Link でアップキャストが必要な最後のケースは,インターフェースを返すようにタイプされたメソッドがあって,そのメソッドの実装がインターフェースを実装する非publicのクラスのオブジェクトを返す場合に起り得る.これは完全に合法的であるが,.NET/Link. にとっては問題となり得る状況である.そのすべてのpublicメンバを獲得するために非publicクラスをリフレクトする(.NET/Link はすべてのメソッドをリフレクションを通して呼び出す)と,publicの親クラスのいくつかによって実装されているメソッドしか見ることができない.クラスがpublicインターフェースを実装するからと言って,それらのメソッドをそのクラスのインスタンスに対して呼び出すことができるということにはならないのである.クラス自体がpublicでない場合,そのメソッドがpublicであっても,publicの親クラスあるいはインターフェースとしてタイプされたクラスのインスタンスに対してのみメソッドを呼び出すことができるのである.

これらの説明は分かりにくいかも知れないので,次の例を考えてみよう.IFooを呼ばれるインターフェースとIFooを実装する内部クラス(内部クラスは同じアセンブリ内の別の型に対してのみ可視である)を想定する.

今度は,InternalIFooImplクラスのインスタンスを返すようなIFooを返すメソッドの型を持つクラスがいくつか他にあるとする.

Foo()メソッドは非publicクラスとしてタイプされているので,このメソッドをfooオブジェクトに対して呼び出してもうまくはいかない.

上のエラーメッセージはあまり正しいものではない. Foo()という名前のpublicメソッドがInternalIFooImplクラスにあることはあるのだが,クラス自体がpublicではないので,リフレクションを通して見ることができないのである.このようなことはC#あるいはVisual Basic .NETでは決して起らない.CreateIFoo()の結果を持つ変数はpublicインターフェースIFooとしてタイプされるからである.プログラマーはInternalIFooImplクラスを見る必要も,それについて知る必要もまったくない.コードは以下のようになる.

しかし .NET/Link では,オブジェクトはデフォルトでその真のランタイムの型として見られるので,非publicクラスとしてタイプされるオブジェクトのインスタンスが起る結果となる.これを解決するには,C#とVisual Basic .NETで行われていることを行わなくてはならない.つまり,オブジェクトをIFooインターフェースにアップキャストするのである.

上の例の「ファクトリ」デザインパターンは比較的よくあるものである.特別なオブジェクト作成メソッドは,あるインターフェースとしてだけタイプされるオブジェクトを返す.これでライブラリのデザイナはインターフェースのみをドキュメント化して,privateクラスの実装詳細を隠すことができる.ライブラリのクライアントはインターフェースのみに書き,実装クラスの実際の名前のような詳細から完全に切り離された状態で置かれる.つまり,非publicクラスをインターフェースの型にアップキャストする必要が .NET/Link プログラマーにとってまったくないというわけではないことが分かる.しかし実際には,非publicクラスがpublicの親クラスから少なくともそのメソッドのいくつかの実装を継承していることが多い.そのような場合には,それらのメソッドが見付けられ,キャストしなくても非publicの子クラスに対して呼び出されることが可能である.

インデクサ

.NET クラスの中には,配列と同じ方法でクラスのインスタンスにアクセスできるようにする特別のメンバを定義するものがある.この特別なメンバは,C#の用語ではインデクサ,Visual Basic .NET の用語ではデフォルトのパラメータ化されたプロパティと呼ばれる.以下はC#およびVisual Basic .NETのメンバ例のスケルトン定義である.

インデクサはクラスが配列ではない場合でもそうであるかのように動作できるようにする..NET コレクションクラス(ICollectionインターフェースを実装するクラス)の大部分は,要素を設定して単純な配列のような構文を使って取り出すことができるように,インデクサをサポートする.クラスが上の定義のどれかのような定義を持つ場合は,「i 番目の要素」に以下のようなコードを使ってアクセスすることができる.

クラスがインデクサを定義する場合は,Wolfram言語で関数用の角カッコを使ってインデクサを呼び出すことができる.

上のコードにはメソッド名もプロパティ名もない.オブジェクト自体の「引数」が関数であるかのように存在しているだけである.Partベースの構文(つまりobj[[0]]を使う構文)の方が,配列アクセスの際にWolfram言語で使う同等のものであるので,.NET/Link のインデクサを呼び出すのにふさわしいが,この構文は哲学的理由と技術的理由から拒否されたのだという論議をすることができるかも知れない.

以下はBitArrayクラスを使ってインデクサを呼び出す別の例である.このクラスは大変コンパクトな方法で保持されているTrue/Falseの値の集合である.このクラスがインデクサを定義して配列のように扱えるようにする.

以下ではインデクサを呼び出して第2の要素を得る.(これがゼロベースの指数だからである.)

C#でクラスを書いてそれにインデクサを与えると,コンパイラがItemという名前のpublicプロパティを作成してくれる.これはパラメータ化されたプロパティである.つまり,メソッドの呼出しのような引数を取る.インデクサ構文はItemプロパティを呼び出すための省略表現にすぎない.Visual Basic .NETで書く場合には,慣例ではデフォルトのパラメータ化されたプロパティは通例Itemと名付けられるべきであるとされているが,これは必要条件ではない.デフォルトのパラメータ化されたプロパティにどのような名前を付けたとしても,インデクサのスタイルの構文を使わずに,希望するならば,Wolfram言語から直接プロパティを呼び出すこともできる.

例外

例外が処理される方法

.NET/Link は.NETの例外を自動的に処理する.捕獲されなかった例外が任意の呼出し中に.NETに投げられる場合は,Wolframシステムでメッセージが返される.以下は実数を整数としてフォーマットしようとする例である.

例外が投げられると,呼出しの結果は$Failedになる.

.NETコードがデバッグの情報を含めてコンパイルされると,例外の結果として得られるWolframシステムメッセージは,それぞれのファイルに厳密な行番号を入れて,例外が起った点までをたどる完全なスタックを示す.

GetNETException

関数GetNETExceptionを使ってWolfram言語から.NETへの最終の呼出しに投げられた例外のExceptionオブジェクトを得ることができる.例外が投げられない場合はNullを返す.ほとんどのプログラマーはこの関数を使う必要がないが,プログラム中の特別な例外を処理する機能を実装するためにこれを使うことが可能である.以下ではInt32.Parse()への前回の呼出しで例外が投げられている.発生する例外のほとんどは「内部例外」の 標準.NETデザインパターンを使って投げられた実際の例外をラップする特別のCallNETExceptionオブジェクトでラップされて戻ってくることが分かる.

実際の例外が投げられるようにするためには,InnerExceptionプロパティを調べる必要がある.

この例では,.NET リフレクションシステムが例外を別の例外でラップしているので,「本当の」例外を見るためには,もう1レベル深く掘る必要がある.

カスタム例外の処理

非常に上級のプログラマーの中には,例外を処理したり報告したりするための自分のシステムを実装したいと考える方がいらっしゃるかも知れない.例えば,.NET 言語で使用されるのと同じプログラミングスタイルを使って,Wolfram言語コード内の.NET 例外をWolfram言語のThrowおよびCatchの関数で処理したい場合もあるかも知れない.さらに簡単な例としては,特定のコードブロック内の例外メッセージを黙らせたい場合が考えられる.

Wolfram言語におけるカスタムの.NET例外処理を実装するためには,シンボル$NETExceptionHandlerを使うとよい.$NETExceptionHandlerの値は,3つの引数を渡される関数として取り扱われる.この引数とは,メッセージに関連したシンボル(これは通常シンボルNETである),メッセージタグ(これは典型的には文字列netexcptnである),メッセージに関連したテキストの記述的文字列の3つである.

通常は$NETExceptionHandlerBlock内に設定して,メッセージを黙らせる次の例にあるように, $NETExceptionHandlerの影響がきっちりと定義されたコードのセグメントに限られるようにする.

ハンドラ関数内でGetNETExceptionを使うと,投げられた実際の.NET例外を得ることができる.以下はその例である.

$NETExceptionHandlerBlockの外に設定することは避けた方がよい.$NETExceptionHandlerの値がクリアされず,ユーザがどうして自分の思ったとおりに例外が処理されないのか分からず首をかしげるような状態をうっかりと作成してしまうことは確実だからである.

ネストされた型

.NETの型の中には別の型の宣言が型の中にネストされているものがある.以下はC#での例である.

.NETでは,+の文字は内側のクラスの名前をその外側のクラスから離すために,型の名前に使われる.上の例では,Innerクラスの実際の型の名前はSomeNamespace.Outer+Innerである.この名前をLoadNETTypeで使わなければならない.

また型の名前は,NETNewでも使われる.以下はInnerクラスのインスタンスの構築方法を示している.

+の文字は型の名前にのみ現れる.ネストされた型をコードで参照する場合は,標準スコープ解決演算子(C#およびVisual Basic .NETではピリオド)を使って内側のクラスを外側のクラスから離すとよい.

ほとんどの.NET言語で,ネストした型についての上の構文を使うことができるが,実際の型の名前は+の文字を使って,内側の型を外側の型から離すことに注意されたい..NET/Link では,型の名前を文字列として入力する場合は必ず+表記を使う必要がある.

ネストされた型のオブジェクトに対するインスタンスメソッドは,通常の方法で呼び出す.

以下は静的メンバをどのようにして呼び出すかを示している.C#およびVisual Basic .NETの場合と同じように,+の文字が消えてスコープ解決演算子(Wolfram言語では`)に取って代られる.

上記の行についてもう1つ注意しなくてはならない点は,Inner`StaticInnerFoo[]でのように,内側のクラス名を単に使ってネストされたクラスの静的メンバを参照することはできないということである. 他の.NET言語においてもこれを行うことはできないので,これは当然予期されることである.必ず外側のクラス名を内側のクラス名の前に付ける必要がある.

以下は実例である.System.Environmentクラスは,SpecialFolderという名前のネストされたenumを持つ.このenumは,Windowsのオペレーティングシステム内の特別な場所を指定する定数(ProgramFilesRecentSystemStartMenu等を含む値を持つ)を含む.以下はユーザのFavoritesフォルダへのパスの決定方法である. System.EnvironmentクラスとSystem.Environment.SpecialFolderのenum(enumのメンバは静的)から静的メンバを呼び出す必要があるので,まずこれらの2つの型をロードする.型の名前に+が付いていることに注意されたい.

GetFolderPath()メソッドはSpecialFolder列挙のメンバを取り,適切なパスを文字列として得る.SpecialFolderのenumのFavoritesメンバをどのように参照しているかに注意されたい.

要約すると,ネストされた型について注意しなくてはならないことのうちで最も重要な点は,文字列内でネストされた型の名前を参照しなくてはならない場合に,+の文字を使って内側の型から外側の型を切り離すということである.コードで型を参照する場合は,使い慣れた「」を使って外側と内側の名前を切り離す.

MakeNETObject

.NETオブジェクトを作成するときに最もよく使われる方法は,NETNewを通してコンストラクタを呼び出す方法である.しかし,.NETオブジェクトに変換したいWolfram言語式があるのだが,クラスが便利なコンストラクタを持っていないという場合もある.よくある例として,Wolfram言語リストから配列オブジェクトを作成したい場合がある.NETNewを通して配列コンストラクタを呼び出すことは可能だが,コンストラクタを通して値を持つ配列を初期化することはできない.以下の例ではNETNewで配列オブジェクトを作成し,それを手動で埋める.

MakeNETObjectを使うと,これをもっと簡単に行うことができる.

MakeNETObject[val]Wolfram言語式 val(数字,文字列,リスト等)を表すために適切な型のオブジェクトを構築する
MakeNETObject[val,type]指定の型のオブジェクトを構築する

MakeNETObject

MakeNETObjectを呼び出さなくてはならないことはほとんどない.例えば,配列を取るメソッドを呼び出す場合に,単にWolfram言語リストを渡せば,.NET/Link が.NET配列を作成してくれる.しかし,Wolfram言語からのデータを投入しなくてはならない.NETオブジェクトを明示的に作成したいが,便利なコンストラクタがないという場合がある. MakeNETObjectが有用である状況の例は次のようなメソッドである.このメソッドは引数として渡されるリストを逆にする.これは逆にされたリストを返すのではなく,その場で逆にするだけである.

このメソッドはWolfram言語リストで呼び出すことができるが,逆にされたリストを取り返すことはできない.この問題を回避するには,初期リスト値を投入された配列オブジェクトを作成してオブジェクト参照を渡し,その内部データを逆にしてからオブジェクト参照をWolfram言語リストに変換し直すという方法がある.

その他にMakeNETObjectが有用となるのは,オーバーロードされたメソッドの正しいシグナチャを選ぶ際に .NET/Link を少し助ける必要があるような場合である.次のようなメソッドの2つのオーバーロードを見てみよう.

Wolfram言語から整数でFoo()を呼び出すと,longパラメータを持つオーバーロードが呼び出される.これは,.NET/Link が一般にそれぞれのスロットに最も範囲の広い可能な型を持つメソッドを呼び出そうとする(しかし,特に複雑な場合には公式の保証は何もない)からである.byteバージョンを呼び出したい場合は,Byteの型の.NETオブジェクトを作成することによってこれを行うことができる.これは,.NET/Link が常に入ってくる引数の型に厳密にマッチするようなメソッドシグナチャを優先するからである.

MakeNETObjectはほとんど使用されることがない関数である.Wolfram言語の文字列,配列等から明示的に.NETオブジェクトを構築する必要はない. 単にこれらのオブジェクトを.NETメソッドに渡すだけで,.NET/Link がこの操作を自動的にやってくれる.上に述べたようないくつかの特別な状況ではこの関数が有用になる.

複素数

.NETの数の型(例えば,byteintdouble)は,Wolfram言語に整数および実数として返され,整数と実数は.NETに引数として送られるときに適切な型に変換される.複素数の場合はどうだろうか.Wolfram言語のComplexの型に直接マップされた複素数を表すような.NETの型があると,Wolfram言語と.NETの間で複素数が渡したり渡されたりしたときに自動的に変換されるので便利である..NETには複素数用の標準の型がないので,.NET/Link でこのマッピングに参加させたい型に名前を付けることができる.

SetComplexType["classname"]Wolfram言語の複素数にマップされるようにクラスを設定する
GetComplexType[]複素数に現在使用されているクラスを返す

複素数用の型を設定する.

以下のようなプロパティを持つ限り,どのようなクラスあるいはストラクトを使ってもよい.

.NETコンソールウィンドウ

.NET/Link は.NETの「コンソール」ウィンドウを表示する便利な方法を提供する.標準のConsole.OutおよびConsole.Errorのストリームに書かれた出力はすべてこのウィンドウに導かれる.診断情報をコンソールに書く.NETコードを呼び出している場合は,この出力をプログラムの起動中に見ることができる. ほとんどの .NET/Link の機能と同様に,コンソールウィンドウはWolfram言語からでも.NETプログラム(.NETコードからのその使用については「Wolfram言語を.NETから呼び出す」に記載されている)からでも簡単に使用することができる.Wolfram言語からこれを使用する場合は,ShowNETConsole関数を呼び出すとよい.

ShowNETConsole[].NETコンソールウィンドウを表示してConsole.OutおよびConsole.Errorに書かれる出力を獲得し始める
ShowNETConsole["stream"].NETコンソールウィンドウを表示し,指定されたストリーム(Console.Outについては"stdout"で,Console.Errorについては"stderr")に書かれる出力を獲得し始める
ShowNETConsole[None]出力の獲得をすべて止める

コンソールウィンドウを示す.

出力の獲得はShowNETConsoleを呼び出すときにだけ始まる.ウィンドウが最初に現れるときには,このウィンドウには以前にConsole.OutあるいはConsole.Errorに書かれたかも知れないようなコンテンツは全く入っていない. ウィンドウがすでに開かれているときにShowNETConsoleを呼び出すと,ウィンドウが最前面に出てくる.

次の例ではWolfram言語から出力をいくつか書く.上記のShowNETConsole[]を実行すると,ウィンドウにHello from .NETが出力される.

このようにWolfram言語コードを使ってウィンドウに書くことを実演してみることは便利ではあるが,これは通常Wolfram言語コードの代りに,コンソールに診断情報を書く.NET コードを使って行われることが多い.

.NET/Link を使うアプリケーションを配布する

このチュートリアルでは,Wolfram言語のアドオンを作成している .NET/Link 開発者に関係がある問題についていくつか触れる.

.NET/Link はアプリケーション開発者が.NETで一部の実装を行っているアプリケーションを流通させることが容易になるように設計されている.アプリケーションのディレクトリを正しく構築していれば,そのアプリケーションのユーザはWolframシステムアプリケーションの任意の標準位置にそれをコピーするだけで,それをインストールすることができる.特に .NET/Link は,ユーザが特別な操作を行ったり,.NETランタイムを再起動したりしなくても,.NETアセンブリを見付けることができる.

Wolframシステムアプリケーションは,通常Wolframシステムがそのディレクトリを見付けられるようないくつかの標準位置のうちのいずれかにインストールして,単独の(サブディレクトリ付きの)ディレクトリとして配備される.これらの標準位置は,$InstallationDirectory\AddOns\Applications$BaseDirectory\Applications$UserBaseDirectory\Applicationsのように書くことができる.ここで$InstallationDirectory$BaseDirectory$UserBaseDirectoryは,これらの組込みのWolfram言語シンボルで与えられる場所を参照する.

.NET/Link アプリケーションには,.NET アセンブリあるいはレガシーWindows DLL(これは「Wolfram言語からDLLを呼び出す 」セクションで触れるように,.NETから呼び出すことができる)が含まれることがある.Wolframシステムアプリケーションが .NET/Link を使い,それ独自の.NETアセンブリを含む場合は,アプリケーションディレクトリ内に「assembly」サブディレクトリを作成するべきである.アプリケーションで必要なアセンブリならどれでも,この「assembly」サブディレクトリに置くことができる.レガシーWindows DLL(いわゆる「アンマネージドの」DLL)は,アプリケーションディレクトリのLibraries\Windowsサブディレクトリに置くとよい.

以下は .NET/Link を使うアプリケーションのディレクトリ構造の例である.

上のディレクトリ構造を使った場合でも,アプリケーションコードは明示的にそのアセンブリをロードする必要があることに注意されたい..NET Framework自体を生成するアセンブリ以外はすべて,「.NETのアセンブリと型をロードする」セクションで述べたように,使用する前に手動でロードされなければならない.アセンブリが正しい場所に置かれているということは,ファイル名かアセンブリ名だけが与えられたときに,LoadNETAssemblyがこれらのアセンブリを見付けることができるということを意味するにすぎない.

バージョン情報

.NET/Link ではバージョン情報を与える3つのシンボルを提供する.これらのシンボルは,Wolfram言語自体でそれに対応するものと同じ型の情報を提供する.ただし,これらのシンボルはNETLink`Information`コンテキストにあり,$ContextPathにはないので,そのフルネームで指定する必要がある.

NETLink`Information`$Version完全なバージョン情報を与える文字列
NETLink`Information`$VersionNumber現行のバージョン番号を与える実数
NETLink`Information`$ReleaseNumberリリース番号(完全なx.x.xのバージョン指定の最終桁)を与える整数
ShowNETConsole[]コンソールウィンドウが .NET/Link アセンブリのバージョン情報を示す

.NET/Link バージョン情報.

.NETコンソールウィンドウ」セクションで触れたShowNETConsole[]関数は,.NET/Link アセンブリファイルのバージョン番号を表示する.このバージョンは .NET/Link Wolfram言語コンポーネントのバージョンにマッチしなくてはならない.

ユーザインターフェースを作成する

はじめに

.NET/Link のアプリケーションの1つに,Wolfram言語プログラムのユーザインターフェースを書くということがある.そのようなインターフェースの例としては,計算の完了を監視するプログレスバー,画像やアニメーションを表示するウィンドウ,ユーザに入力を促したりなじみがない関数への正しい呼出しを作成することを助けたりするダイアログボックス,ユーザに分析の段階を説明するミニアプリケーション等がある.このようなタイプのユーザインターフェースは,ユーザがあるWolfram言語コードを呼び出したときに出てくる,Wolfram言語を背景で使う.NETプログラムのために書くインターフェースとは異なっている.これらのユーザインターフェースは,ノートブックフロントエンドに取って代るものではなく,それを拡大するだけのものである.この意味では,これらのインターフェースは,フロントエンドで作成することができるパレットやその他の特殊ノートブック要素の拡張のようなものである.

.NET/Link と一緒に使うWolfram言語は,ユーザインターフェースを作成する際に極めて強力で生産的な環境となる.ユーザインターフェースコードの複雑性は,.NET/Link 開発において1度にインタラクティブな行を1行ずつ書く性質に理想的に適合している.実行中にユーザインターフェースを構築,修正,実験してみることができるのである.

.NET/Link あるいは J/Link を使って,Wolfram言語プログラムのユーザインターフェースを構築することができる.J/Link はクロスプラットフォームなので,インターフェースをWolframシステムのすべてで実行することができるという点で有利である..NET/LinkJ/Link よりももっとしっかりとWindowsに統合されているので,ユーザがインターフェースをWindowsのマシンだけで使う必要がある場合には,.NET/Link がおそらく最適の選択である.

J/Link を使ってWolfram言語のユーザインターフェースを作成した場合には,この分野においては .NET/LinkJ/Link の間に大きな違いがあることに注意されたい..NET/Link は一般に J/Link よりも簡単である.これは.NETがJavaよりも優れているからではなく,.NET/Link は2世代目のデザインであるからである..NET/Link におけるデザインの単純化はいずれは J/Link にももたらされるものである.

Wolfram言語プログラムのユーザインターフェースを書こうと考えている方は,GUIKit のアドオンも選択の1つとして見てみるとよい.このアドオンは,Mathematica 5.1以降の Mathematica にバンドルされていて, 古いバージョンの Mathematica のユーザもダウンロードして手に入れることができる.GUIKitJ/Link の上に構築されているもので,インターフェースを作成する上で大変高レベルの手段を提供する.

モーダルな操作とモードレスの操作

ウィンドウあるいはボタン等の.NETのユーザインターフェース要素を表示するWolfram言語プログラムを書くことには, ノートブックフロントエンドだけを使ってカーネルと交信する典型的なWolframシステムセッションには存在しない特別な問題に関してある程度の知識が必要である.これらの特別な問題を理解するには,カーネルが入力を手に入れ,それを評価し,任意の出力を送り出す「メインループ」について基本的部分を少し考慮することが役に立つ.

Wolfram言語カーネルがフロントエンドから使用されると,カーネルはほとんどの時間をフロントエンドと交信する際に使うWSTPに入力が到着するのを待つことに費やす.このWSTPのリンクは$ParentLinkによって与えられるため,カーネルの注意を引く$ParentLinkである.$ParentLinkに入力が到着すると,入力は評価され,結果はすべてリンクに送り返され,カーネルはまた$ParentLinkにさらに入力が来るのを待つことに戻る. .NET/Link が使われていると,カーネルは.NETランタイムに接続するもう1つのWSTPのリンクを開くことになる..NETに呼出しをかけるコードを実行すると,カーネルは.NET に何かを送り,.NETからの戻り値を待つことをブロックする.カーネルが.NETからの戻り値を待つこの期間,.NETリンクはカーネルの注目を得る.これは,カーネルが.NETリンクに注目しているはこの期間中だけである.もっとこれを一般的な言い方で言うと,カーネルは特別に指示された場合にだけ入力が.NETから到着するのを耳を澄まして聞いているということである.その他の時間は,$ParentLinkにだけ耳を済ませている. $ParentLinkは通常ノートブックフロントエンドである.

ユーザが.NETウィンドウのボタンをクリックして,ボタンがWolfram言語に呼出しをかけるためのコードを実行しようとする場合に何が起るのかを考えてみよう..NET側はWolfram言語に何かを送り,その後結果を待つが,カーネルは.NETリンクではなくノートブックフロントエンドリンクにしか注目していないので,要求を決して受け取ることがない.カーネルに.NETリンクに到着する入力を探すように伝える何らかの方法が必要である..NET/Link は,カーネルが.NETリンクに注意を向けるように管理する2つの主な方法を提供し,それによってカーネルが.NET側で始められた評価への要求を進んで受けることを制御する.

これらの2つの方法を「モーダル」および「モードレス」と呼ぶことができる.Wolfram言語関数DoNETModalの使用で特徴付けられるモーダルなインタラクションでは,カーネルは.NET側がそれを解放するまで .NETリンクに向けられる.カーネルは完全な.NET側のスレーブで,その他の計算には使用できない.Wolfram言語関数DoNETModelessの使用で特徴付けられるモードレスのインタラクションでは,カーネルはノートブックフロントエンドと.NETの両方から到着する評価要求を受け入れられる状態に置かれ,これらの2つのプログラム間に等分に注意を払う.

ユーザインターフェース要素の普通の型は,モーダルなダイアログに似ている.つまり,これが一旦表示されると,Wolfram言語プログラムはユーザがウィンドウを解任するまで待ち続ける.典型的にこれは,ウィンドウが結果をWolfram言語に返すので,ウィンドウが閉じられるまでWolfram言語が継続することに意味を持たないからである.そのようなウィンドウの例としては,ユーザにある値を要求する簡単な入力ウィンドウがあり,値はOKボタンがクリックされるとWolfram言語に返される.

これらのウィンドウを表現するために用語「モーダル」が少し一般化されて使われていることを理解することが大切である.ユーザインターフェースで他に何かする前にこれらのウィンドウを解任しなければならないという点で,これらは従来の意味ではモーダルとは呼べないかも知れない.むしろ,カーネルはウィンドウが閉じられるまで何もできないという点で,これらのウィンドウはWolfram言語カーネルについてはモーダルであると言える.作成した.NETウィンドウは,スクリーン上の他の.NETウィンドウについてはモーダルではないかも知れないが,それが解任されるまでカーネルの注意を縛り付けておく.

ユーザインターフェース要素のもう1つの型は,モードレスのダイアログに似ている.つまり,これが表示された後で,それを作成したWolfram言語プログラムは終了し,ユーザがノートブックフロントエンド内で作業を続ける間ウィンドウを可視で使用できる状態のままにしておく.その例としては,ユーザがパッケージをスクロールリストから選んでWolfram言語にそれをロードすることを可能にするウィンドウがある.このウィンドウを作成,表示,そして返す .NET/Link プログラムを書く.ウィンドウは,ユーザがそのクローズボックスをクリックするまで開いて使用できる状態のままで置かれる.この間,ユーザはフロントエンド内で自由に作業し続け,都合のいいときにいつでもこの.NETウィンドウをもう1度使うために戻れる.

このようなウィンドウは,フロントエンド内の別のタイプのノートブックあるいはパレットのウィンドウとほとんど同じようなものである.一度に任意の数のフロントエンド,あるいは.NETのモードレスウィンドウを開いてアクティブにしておくことができる.つまり,これらはWolfram言語で計算を始めるのに使用することができ,それぞれが同じカーネルにおけるそれ自身の小さなインターフェースである..NETウィンドウが異なる点は,これはノートブックウィンドウよりももっと一般的で,重要なことにフロントエンドとは異なるアプリケーションの層に存在するということである.この最後の事実が実際.NETウィンドウをノートブックフロントエンドの延長というよりも第2のフロントエンドにしている.そのような第2フロントエンドを受け入れるためには,カーネルはノートブックフロントエンドあるいは.NETのいずれかから到着する評価要求を処理できる特別な場所に置かれる必要がある.

モーダルなウィンドウとモードレスのウィンドウの実装の仕方の例を示す前に,少し先に飛んで,.NETのユーザインターフェース要素がイベントをWolfram言語に交信するメカニズムについて説明する.

イベントを処理する

ユーザインターフェース要素は一般に,ボタン,スクロールバー,メニュー,テキストフィールド等使用されるときに特定のアクションをトリガする必要があるアクティブな要素を持つ..NETイベントモデルでは,構成要素がユーザのアクションに応えてイベントを引き起し,他の構成要素は,イベントをそのハンドラに接続する代表を供給することによって,これらのイベントに対して興味があることを示す. 代表の概念については.NET Frameworkのドキュメントで詳しく触れているが,イベントが起るときに呼び出されるWolfram言語関数を割り当てるのには大変簡単な構文を使うので,.NET/Link ユーザは代表についての詳細は一般に無視しても大丈夫である.

イベントハンドラ関数を割り当てるための .NET/Link での方法とC#およびVisual Basic .NETにおける方法を比較することは有益である.以下は,TextBoxKeyPressイベントにイベントハンドラを加えるためのC#およびVisual Basic .NETの構文を示す.

上の行のどちらかを実行した後で,myTextBoxコンポーネントに注意が向けられている間にキーが押されるといつでもMyKeyPressHandlerMethod()メソッドが呼び出される.イベントに代表を加えるのに+=演算子がオーバーロードされることから,C# 構文は少し暗号的である.

前述のコードではMyKeyPressHandlerMethod()の定義を示さない.このメソッドのシグナチャはKeyPressイベントに対応する代表と同じものでなければならない.C#コードに見られるように,代表の型はKeyEventHandlerであり,以下はその宣言である.

MyKeyPressHandlerMethod()は同じシグナチャを持たなくてはいけないので,以下のような形になる.

Wolfram言語でWolfram言語関数のmyKeyPressHandlerKeyPressイベントに割り当てると次のようになる.

これはVisual Basic .NETバージョンとほとんど全く同じ形になることに注意されたい.一旦上の行を実行すると, myTextBoxが入力フォーカスを持つ間にキーが押されるといつも,.NETはWolfram言語を呼び返し,myKeyPressHandler関数を実行する.Wolfram言語関数はKeyEventHandler の代表と同じ引数で呼び出され,同じ型の値を返すはず(ただし,ほとんどのイベントハンドラはvoidを返すので,戻り値は無視される)である.以下はそれがどのようになるかの例である.

AddEventHandler[obj@eventName,funcName]オブジェクト objeventName イベントを引き起すと呼び出されるWolfram言語関数を設定する
RemoveEventHandler[delegate]AddEventHandlerへの前回の呼出しで割り当てられたイベントハンドラを削除する

イベントの通知に応えて呼び出されるWolfram言語関数を割り当てる.

Wolfram言語関数への呼出しを通してアプリケーションのイベント論理を繋ぐ方が,.NETの従来のアプリケーションを書くことよりもずっと融通が利く.コンパイルされた.NET言語で書いたり,あるいはドラッグアンドドロップのGUI ビルダーを使ったりすると,イベント論理をハードコード化することになる.コンパイルする際に,すべてのクリック,スクロール,キーストロークがそれぞれ何を行うかを決めなくてはならない.しかし,.NET/Link を使う場合は,ランタイムにプログラムをどのように配線するかを決定する.単に数行のコードを入力することによって,オンザフライで動作を変更することすら可能である.

RemoveEventHandler関数を使ってイベントハンドラを削除することができる.AddEventHandlerを呼び出すと,NETObjectを返す.このオブジェクトは .NET/Link の内部でユーザ用に作成された代表である.このオブジェクトを保存して,後からRemoveEventHandlerに渡し,それが表すWolfram言語コールバックを削除することができる.これがAddEventHandlerの戻り値を使うことになるほとんど唯一の場合である.

イベントに応えて呼び出されるWolfram言語のイベントハンドラ関数は,自動的にNETBlockでラップされる.これはつまり,関数に引数として送られるオブジェクト,および関数の実行中に作成された任意の新しいオブジェクトは,関数が戻った後解放される.NETBlockあるいはReleaseNETObjectを手動で使う必要はない.ハンドラ関数からのオブジェクトを関数が戻った後もWolfram言語で持続したい場合は,KeepNETObjectを使って関数の呼出しをラップする目に見えないNETBlockを逃がす必要がある.以下は,後から検査するためにKeyCodeオブジェクトをリストに保持するmyKeyPressHandlerの修正されたバージョンである.

AddEventHandlerはその動作を制御する2つのオプションを取る.SendDelegateArgumentsではどの代表引数をどの順番にWolfram言語ハンドラ関数に送りたいかを指定することができる.デフォルトで .NET/Link は代表引数をすべて送るが,最適化という意味では,すべての引数を送らない方がよい場合もある.新しいNETObject式をWolfram言語で作成することは,比較的高くつき,ほとんどのイベント代表の引数はオブジェクトである.上のKeyPressイベントの例では,第1引数はTextBoxオブジェクトであるが,これはおそらくすでにWolfram言語内に存在するものであるので,それを送ることを避けても有意な最適化にはならない.しかし,KeyEventArgsオブジェクトは確実にWolfram言語にとって新しいオブジェクトであるので,必要ではない場合には送ることを避けた方がよいこともある.以下は,Wolfram言語のコールバック関数の第1引数だけを送るイベントハンドラを設定する例である.

これはmyKeyPressHandler3関数がどのようになるかの例である.

SendDelegateArgumentsオプションに指定できる値は,All(デフォルト),None,あるいは送りたい引数の指数を与える整数のリストである.

オプション名
デフォルト値
SendDelegateArgumentsAllどの代表引数をWolfram言語イベントハンドラ関数に送るか
CallsUnshareFalseイベントハンドラ関数が拡張関数UnshareKernelを呼び出すかどうか

AddEventHandlerのオプション.

CallsUnshareオプションは,DoNETModelessを使う代りにShareKernelおよびUnshareKernelの関数を使って,手動でカーネル共有を制御している上級プログラマー用のオプションである.共有関数については「カーネルとフロントエンドを手動で.NETと共有する」で触れる.Wolfram言語コールバック関数が UnshareKernelを呼び出す場合には,AddEventHandlerへの呼出しの中でCallsUnshareTrueに設定する必要がある.

AddEventHandlerはイベントが起ったときに呼び出されるWolfram言語関数を容易に割り当てることができる簡易関数である.その操作の一部として,AddEventHandlerはイベントに割り当てられ,そのアクションが指定のWolfram言語関数を呼び出すことにある,.NET代表オブジェクトを作成する.場合によっては,そのような代表オブジェクトを手動で作成したいけれども,それをイベントに添付したくはないこともある..NET/Link は,NETNewDelegate関数をこのために提供する.NETNewDelegateはそのアクションが指定されたWolfram言語関数を呼び出すことにある指定の型の代表を作成する.NETNewDelegateを使う主な目的は,.NETのPInvokeの設備を通して呼び出される外部C関数に供給される代表オブジェクトを作成することにある. この目的にこの関数はしばしばDefineNETDelegateと一緒に使用される.EnumWindows.nbの例題ファイルは,DefineNETDelegateおよびNETNewDelegateを使って,引数としてコールバック関数ポインタを取るC関数を呼び出す例を示す.

NETNewDelegate[type,funcName]そのアクションが指定のWolfram言語関数を呼び出すことにある,指定の代表の型の新しいインスタンスを作成する
DefineNETDelegate[name, returnType,{argType,...}]適切なシグナチャの.NET代表がすでに存在しない場合に新しい代表の型を作成する

Wolfram言語を呼び出す代表オブジェクトを作成する.

Wolfram言語イベントハンドラのコールバックの特定の仕方を見てきたので,今度はカーネルが.NETのユーザイベントからの呼出しを受け取れる特別の状態になければならないことを思い出していただきたい.次のセクションでは,これを行う2つの主な方法である関数DoNETModalおよびDoNETModelessについて触れる.

モーダルなウィンドウ

モーダルとモードレスの .NET/Link インターフェースの基本的な概念については,上のセクション「モーダルな操作とモードレスの操作」で触れている.以下は簡単なモーダルウィンドウの例である.ウィンドウは,クリックされるたびに背景色が新しいランダムな色に変わる簡単なFormオブジェクトである.

ただ単に新しいFormオブジェクトを作成するだけでは,それは可視にはならない.ShowNETWindow関数を使ってウィンドウを可視にし,それを他のウィンドウの前に持ってくる.後で使用されるDoNETModal関数は,フォームを可視にするが,インターフェースを実際モーダルに実行する前にインターフェースをいじっているときにはShowNETWindowが便利である.

この時点でスクリーンの中央に小さな枠のウィンドウが見えるはずである.それをドラッグしてスクリーンの端に持ってきて,それが現行のノートブックウィンドウを前面に持ってきたときにWolframシステムウィンドウで隠れないようにする.ユーザインターフェース開発に .NET/Link を使う大きな利点は,典型的なコンパイルされた.NET言語と比べて,.NET/Link ではインターフェースを実行している最中にいろいろ試してみることができるという点にある.今度は背景色を変えてみる.

フォームのClickイベントにWolfram言語ハンドラを加える.

以下はonClick関数の定義である.これは,フォームのBackColorプロパティをランダムな色に設定する.この関数はイベント引数を無視するが,これらの引数が何であるかは,Clickイベントのシグナチャから分かる.

この時点でフォームをクリックすると,ビープが鳴り,色は全く変更されない.Clickイベントが起ると,.NETはWolfram言語を呼び出してonClick関数を実行しようとするが,Wolfram言語は.NETリンク上の入力の有無に注意を払っていないので,この呼出しを行うことは安全ではない.Wolfram言語への呼出しが永久にハングアップしてしまう..NET/Link はカーネルの準備ができていないことを知っているので,呼出しを行うことを拒否し,代りにビープ警告を鳴らすのである.

ここで必要なのは,カーネルが.NETリンクから継続して読み込むような状態にカーネルを置くことである.これがつまりウィンドウを「モーダル」にするということで,カーネルはウィンドウが閉じられるまでは他のことは何もできない.このモーダルな状態を実装する関数がDoNETModalである.DoNETModalの第1引数は,トップレベルのウィンドウオブジェクト(.NET FrameworkではこれはSystem.Windows.Forms.Form,あるいはそれから継承する任意のクラス)である.

DoNETModal[form]指定された form ウィンドウが閉じられるまでカーネルの注意が.NETリンクだけに向けられた状態になるようにカーネルを置く
DoNETModal[form, returnValue]form をモーダルに実行し, returnValue の計算(これはウィンドウが破棄される前に実行される)の結果を返す

モーダルなウィンドウを実行する.

これですべての準備が整ったので,モーダルな状態に入ってウィンドウを使用することができる.

DoNETModalは.NET フォームウィンドウが閉じられるまで返らない.ウィンドウを何度がクリックして色が変化することを確かめ,その後タイトルバーのクローズボックスをクリックしてフォームを破壊しDoNETModalが返るように仕向ける.

テキストボックスからの値,あるいはフォームがOKあるいはCancelのボタンをクリックすることによって閉じられるかどうか等の情報をモーダルなダイアログボックスが閉じられるときにそこから得たい場合がよくある. DoNETModalが返るとき,フォームオブジェクトは破壊されるので,それに対してもはやメソッドを呼び出すことはできない.フォームが破壊される前にフォームから何らかの情報を得る必要がある場合は,オプショナルな第2引数であるDoNETModalを使うとよい.この引数はフォームが破壊される直前に実行される計算(計算はこのときまで評価されずに留め置かれる)を指定する.DoNETModalがこの計算の結果を返す.PackageHelper.nbの例題ファイルでは,フォームがOKあるいはCancelのボタンをクリックして閉じられたかどうかを決定するDoNETModalの第2引数の使い方を示す.

以下は,例全体が単独のプログラムにパッケージされた場合にどのようになるかを示している.

.NET Frameworkのドキュメントは,ShowDialog()メソッドを使ってどのようにモーダルなウィンドウを実装するかについて述べている.DoNETModalの代りにこの技術を使うことについてはいくつかの利点と不利点が挙げられる.1つの不利点は,.NETウィンドウはShowDialog()を使う場合にノートブックウィンドウの前に必ずしも来るとは限らないという点である.しかし,このウィンドウが他のウィンドウの前に出てこないのは,セッションで一番最初に表示されるウィンドウに限られるようである. ShowDialog()で表示されるウィンドウは,モーダルなウィンドウが閉じられるまで他の.NET ウィンドウが使えないという面において真にモーダルである.ShowDialog()メソッドは,DialogResult列挙の値の1つを返すので,どのようにウィンドウが閉じられるか(OKあるいはCancelのボタンがクリックされるかどうか)を決定することを容易にする.同じ結果をDoNETModalの第2引数を使って得ることもできる.

PackageHelper.nbの例題ファイルは,DoNETModalOKおよびCancelのボタンが実装された典型的なモーダルのダイアログを示す.

DoNETModalは,ウィンドウがスクリーン上に現れる位置を指定する1つのオプションであるFormStartPositionを取る.可能な値は,Center(デフォルト),Automatic(フォームはWindowsのデフォルトの位置を持つ), Manual(フォームは別で指定された,例えばフォームのLocationプロパティで設定された場所に現れる)のいずれかである.

オプション名
デフォルト値
FormStartPositionCenterスクリーン上でウィンドウが表示される位置

DoNETModalのオプション.

モードレスのウィンドウ

上のセクションではDoNETModal関数を使って,ウィンドウが閉じられるまでカーネルが使用中のままになるモーダルなウィンドウを表示,実行する方法について示した.モードレスと呼べるもう1つのタイプのウィンドウは,カーネルを完全に不通にすることなく,開いていて使える状態に置いておく.モーダルとモードレスの .NET/Link のインターフェースの基本的な概念については,上のセクション「モーダルな操作とモードレスの操作」に詳細が掲載されている.

.NET/LinkDoNETModeless関数を提供してウィンドウをモードレスに実行する.DoNETModelessの第1引数はトップレベルのウィンドウ(特に,Formあるいはそこから継承される任意のクラス)である.ウィンドウは可視化され,すべてのノートブックウィンドウの前面に持ってこられる.

DoNETModeless[form]指定された Form ウィンドウを表示してすぐに返し,ウィンドウをアクティブのままにしておく

モードレスのウィンドウ.

以下は前セクションからのSimpleModalの例で,モードレスウィンドウとして実装されている.

SimpleModeless[]を実行すると,ウィンドウは可視になり,その後すぐに返る.ウィンドウをクリックしてその背景色を変えることができるし,ノートブックフロントエンドを通して他の計算にカーネルを使い続けることもできる.

SimpleModelessSimpleModalのコードには,DoNETModalの代りにDoNETModelessを呼び出すという明らかな違いの他に,重要な違いがいくつかある.これらの違いは,フォームがSimpleModeless関数が返った後から実行されるという事実から来るものである.Moduleが終了したときに関数の定義がクリアされるので,onClick関数はModuleにローカルであってはならない.SimpleModelessが実行されるときにNETBlockを使って自動的にすべての作成された.NET オブジェクトが解放されるが,これはつまりonClickのコードの中で名前でfrmを参照してはならないということである.これはなぜならonClickが実行される頃(シンボルfrmModuleに対してローカルであるので,その値はどっちみちクリアされる)にはすでにNETBlockが終了しているからである.onClick関数の第1引数はイベントを起すオブジェクト,つまりFormオブジェクトなので,シンボルfrmの代りに第1引数を使ってそれを参照することができる.

DoNETModelessは,開発中に大変便利で,最終的なフォームではモーダルに実行されるウィンドウについてさえも役に立つ.DoNETModalはウィンドウが閉じられるまで返さないので,実行中にウィンドウのイベント論理やその他の面について修正を入れることはできない.DoNETModelessを使って,カーネルを不通にすることなくイベントコールバックを「ライブ」にすることができ,ウィンドウが表示されている間でもウィンドウを操作することができる.希望通りにウィンドウが動作することを確認したら,DoNETModalで実行する完全なプログラムをパッケージすることができる.

オプション名
デフォルト値
FormStartPositionCenterスクリーン上でウィンドウが表示される位置
ActivateWindowTrueウィンドウを可視にするかどうか
ShareFrontEndFalseカーネルに加えてフロントエンドも.NETで共有できるようにするかどうか

DoNETModelessのオプション.

DoNETModelessはいくつかのオプションを取る.FormStartPositionはスクリーン上でウィンドウが現れる位置を指定する.可能な値は,Center(デフォルト),Automatic(フォームはWindowsのデフォルトの位置にある), Manual(フォームは別の場所で指定された位置,例えばフォームのLocationプロパティを設定することによって指定された位置に現れる)のいずれかである.ActivateWindowはウィンドウを可視して最前面に持ってくるかどうかを制御する.モードレスの状態に入るためにDoNETModelessを呼び出してからのみウィンドウを可視にしたいという稀な場合にはActivateWindowFalseに設定する.

最後のオプションであるShareFrontEndは,.NETインターフェースがノートブックフロントエンドとインタラクトできるようにするために使用される.関数ShareKernelおよびShareFrontEndについては次のセクションで触れる.DoNETModelessを使っているプログラマーは一般にこのような低レベルの関数からはシールドされている.DoNETModelessは主にShareKernelおよびUnshareKernelへの呼出しをカプセル化する方法で,ウィンドウが最初に現れたときに共有し始め,ウィンドウが閉じられたときに終了する.モードレスのインターフェースにノートブックフロントエンドの中で何らかのアクション(テキストを出力したりグラフィックスが表示されるようにしたりする等)を起させたい場合は,DoNETModelessにカーネル共有だけでなくフロントエンドもオンにするように強制する必要がある.これはShareFrontEndオプションをTrueに設定することで行うことができる.

ShareFrontEndオプションが便利となる状況でよくあるのが,Print宣言をイベントハンドラ関数に挿入することによってプログラムをデバッグしたい場合である.モードレスのインターフェースでは,イベントハンドラ関数でトリガされるPrint出力とWolframシステムの警告メッセージは.NET側に送られるので,ノートブックには現れない.この情報を見たい場合は,ShareFrontEndTrueに設定すると最前面のノートブックウィンドウにこれが現れる.開発中にこのオプションを使うが,プログラムの最終バージョンでは必要ないという場合は,忘れずにオプションを削除しなくてはならない.フロントエンド共有を付けたり消したりするのは高くつくし,ウィンドウが最初に現れるのが遅れることがあるからである.

カーネルとフロントエンドを手動で.NETと共有する

注意: Mathematica 5.1以降では,カーネルは常に.NETリンクと共有される.これはつまり関数ShareKernelおよびUnshareKernelは必要なく,実際何も行わないということである.Mathematica 5.1以降の Mathematica のみで実行する必要があるプログラムを書いている場合は,ShareKernelあるいはUnshareKernelを呼び出す必要は全くない.プログラムがすべてのバージョンの Mathematica で使えなければならない場合は,以下に述べるようにこれらの関数を使う必要がある.

前セクションで触れたDoNETModalおよびDoNETModelessの関数は,.NETから始まるイベントからの呼出しを受け入れる状態にカーネルを置く方法である.ほとんどのプログラマーは.NETウィンドウを表示し実行するためだけにこの2つの関数を使う必要がある.DoNETModelessはカーネルが.NET あるいはノートブックフロントエンドからの入力を受け入れるようにするということを思い起こしていただきたい.実際,カーネルはフロントエンドと.NETの間で「共有」される.状況によってはカーネル共有を始めたいが,DoNETModelessがそれには適切ではないという場合もあるかもしれない.その場合,ShareKernel関数を呼び出すことによって,カーネル共有を直接制御することができる.

ShareKernelJ/Link で紹介されたもので,JLink`コンテキストで定義される.NETLink`をロードするとJLink`がロードされるので,パッケージの中でShareKernelあるいはその関連関数を使っているのでない限り,異なるコンテキストについて心配する必要がない.BeginPackageを呼び出すと,Wolfram言語は使用中のパッケージのコードにBeginPackage宣言の中で明示的に指定したコンテキストだけを使用できるようにして,これらのパッケージのその他のコンテキストを使えるようにはしない.これはつまり,パッケージ内で使用したいシンボルそれぞれに対して常にBeginPackage宣言の中でそのシンボルのコンテキストを明示的に含む必要があるということである.以下は .NET/Link を使い,またShareKernelを直接呼び出すパッケージの概略である.

J/Link ユーザガイド」でShareKernelおよびShareFrontEndについて詳細に触れているので,完全な情報はそれらのセクションを参照されたい.ShareKernelあるいはShareFrontEndを.NETで使う際に心に留めておく必要があるのは,リンクを.NETに引数として供給しなくてはならないということである.さもなければ,デフォルトでJavaリンクが使用される.

常にShareKernelからの結果を保存し,その後でUnshareKernelに渡さなくてはならない.

上で述べたように,DoNETModelessShareKernelを呼び出して共有を開始し,ウィンドウが閉じられたときにUnshareKernelが呼び出されるようにする.J/Link では,プログラマーはモードレスなウィンドウを作成するために直接ShareKernelおよびUnshareKernelを呼び出す必要がある.DoNETModelessのように自動的に共有をオンにしたりオフにしたりする特別の関数があるとずっと楽である.

直接ShareKernelを呼び出す必要があるプログラムの例が「COMイベントを処理する」に掲載されている. そのプログラムはWolfram言語のイベントハンドラをInternet Explorerアプリケーションで引き起されたCOMイベントのために設定する. DoNETModelessに渡すトップレベルの.NETFormウィンドウはないので,明示的に共有を管理する必要がある.

Wolfram言語グラフィックスとタイプセット式を表示する

.NET/Link には標準PictureBoxクラスの特別のサブクラスが含まれている.このサブクラスはWolfram.NETLink.UI.MathPictureBoxと呼ばれ,Wolfram言語のグラフィックスあるいはタイプセット式を.NET ウィンドウで表示することを容易にしてくれる.例題ファイルSimpleAnimationWindow.nbにその使い方が示されている.このクラスについての完全なドキュメントは .NET/Link APIドキュメントを参照されたい.

.NETウィンドウを最前面に持ってくる

.NETウィンドウをWolfram言語プログラムで作成している場合,おそらくそのウィンドウがユーザが作業をしているノートブックの前に出てくるようにして,そのウィンドウの存在が明らかになるようにしたいであろう.関数DoNETModalおよびDoNETModelessは自動的にフォームを可視化し,それを最前面に持ってくる.Formクラスの Show()あるいはActivate()メソッドもこれと同じことを行えそうであるが,.NETウィンドウはノートブックフロントエンドとは違うアプリケーションに存在しているので,これらのメソッドは必ずしもうまくいくとは限らない.

.NET/Link はWolfram言語関数であるShowNETWindowを提供して,.NETウィンドウを可視化し,他のウィンドウの前に現れるようにするのに必要なステップをすべて行う.DoNETModalあるいはDoNETModelessを使っている場合は,ShowNETWindowは自動的に呼び出されるので,呼び出す必要はない.しかし, DoNETModelessを使っていても,そのウィンドウが最初に表示された後にユーザが他のウィンドウをその前面に持ってきた場合には,ウィンドウをもう1度最前面に持ってくるのにShowNETWindowが役に立つことがある.

ShowNETWindow[form]指定の.NET form ウィンドウを可視化し,それをノートブックウィンドウを含めたその他のウィンドウすべての前に持ってくるようにする

.NETウィンドウを最前面に持ってくる.

DoNETModalおよびDoNETModelessと同様に,ShowNETWindowはスクリーン上でウィンドウが現れる位置を指定するFormStartPositionオプションを取る.可能な値は,Center(デフォルト),Automatic(フォームはWindowsのデフォルトの位置にある),Manual(フォームはそれ以外で指定された位置,例えばフォームのLocationプロパティを設定することによって指定された位置に現れる)のいずれかである.

例題ファイル

以下のGUIの例題プログラムが .NET/Link に含まれている.

Circumcircle.nb

PackageHelper.nb

SimpleAnimationWindow.nb

RealTimeAlgebra.nb

AsteroidsGame.nb

Wolfram言語から使用するために独自の.NETの型を書く

はじめに

このドキュメントでは既存の.NETの型をどのようにしてロードして使うかということについて見てきた.これでWolfram言語プログラマーは.NETの型の世界全体に即座にアクセスできる.しかし、既存の型だけでは足りなくて,独自の型を書く必要がある場合もある.

.NET/Link は基本的に.NETとWolfram言語の間の境界線を消し去って,任意の型の式を渡したり受け取ったりすることと,.NETオブジェクトをWolfram言語内で意味のある形で使用することを可能にしてくれる.これはつまり,Wolfram言語から呼び出す独自の.NETの型を書く場合に,何も特別な操作を行う必要がないということである.型を.NETからのみ使いたい場合と全く同じようにコードを書いて,好きな.NET言語を使うことができる.

場合によっては,Wolfram言語とのインタラクションにもっと直接コントロールを及ぼしたい場合もあるかも知れない.例えば,メソッドが実際に返すものとは異なる結果をWolfram言語に送るメソッドを希望する場合があるかも知れない.あるいは,メソッドがただ単に何かを返すだけでなく,Wolfram言語で何かを出力したり特定の条件下でメッセージを表示したりといった何らかの副作用を引き起すようにしたいかも知れない.メソッドが返る前にWolfram言語と拡張された「対話」を行って,Wolfram言語内で複数の計算を呼び出したり,その結果を読み込んだりといったことを行うことさえ可能である..NETで何らかのイベントの結果が引き起されるときにWolfram言語で呼び出すコードを書きたいと思うこともあるかも知れない.

これらのことを全く行う予定がないのであれば,このチュートリアルを無視してもらっても大丈夫である..NET/Link の核心は,WSTPを通して行うWolfram言語とのインタラクションについて心配することを不要にするということである.Wolfram言語から使用する.NETの型を書きたいと考えるプログラマーのほとんどは,Wolfram言語あるいは .NET/Link のことを考えずに,単に.NETの型を書く.もっとコントロールが欲しい,あるいは .NET/Link を使って行えることについてもっと知りたいと思っているプログラマーの方は,この先を読み進まれたい.

.NET/Link で使用する独自の型を作成する際に注意しなくてはならないのは,LoadNETAssemblyを使って型を含むアセンブリをロードする必要があるということである..NET/Link でアセンブリに含まれる型を使う前に必ずそのアセンブリをロードしなくてはならないが,このことは,.NET/Link が.NET Frameworkの一部であるアセンブリについてはそれが必要になったときに自動的にロードするので,忘れられがちである.

結果をWolfram言語に手動で返す

Wolfram言語から呼び出された.NETのメソッドあるいはプロパティにデフォルトの動作は,メソッドあるいはプロパティ自体が返すものをそのままWolfram言語に返すということである.しかし,それ以外のものを返したいと思うような場合もある.例えば,ある状況では整数を返し,別の状況ではシンボルを返したいと考えるかも知れない.あるいは,メソッドが.NETから呼び出された場合とWolfram言語から呼び出された場合で違うものを返すようにしたいことがあるかも知れない.このような場合に,メソッドが返す前にWolfram言語に手動で結果を送る必要がある.

Wolfram言語から呼び出したいファイルを読み込むクラスを書いているとしよう.標準クラスのSystem.IO.StreaInlineCodeeaderとほとんどまったく同じ動作を行いたいので,作成するクラスはそのサブクラスとなる.唯一変更したい部分は,もっとWolfram言語らしい動作をいくつか提供するようにしたいということである.1つの例としてRead()メソッドがファイルの終りに到着したときに-1ではなく,むしろWolfram言語の組込みのファイル読込み関数が返すシンボルEndOfFileを返すようにしたい場合がある.

ファイルが最後に到達すると,i-1になり,何かを手動でWolfram言語に返したいとする.最初に行わなくてはいけないことは,Wolfram言語と交信するために使えるIKernelLinkオブジェクトを得ることである.これは静的なプロパティStdLink.Linkを呼び出して得る.インストールできる WSTPプログラムをCで書いたことがあるなら,ここで選択している名前に見覚えがあるであろう.Cプログラムはstdlinkという名前でWolfram言語へ返すリンクを持っている大域的な変数を持つ..NET/Link はこのリンクオブジェクトに関連するメソッドをいくつか持つStdLinkクラスを持つ.

次にLinknullを返すかどうかを検証する.Wolfram言語からメソッドが呼び出されている場合には,これは決してnullにはならないので,このテストを使ってメソッドがWolfram言語から呼び出されているのか,それとも標準の.NETプログラムの一部として呼び出されているのかを調べることができる.このようにして,Wolfram言語カーネルがどこにもない場合に,通常の方法で.NETから使えるメソッドを持つことができる.

一旦カーネルに返るリンクが存在していることを確かめたら,まず .NET/Link に結果を自分でWolfram言語に送り返すので,自動的にメソッドの戻り値を送らないようにということを伝える.これは,IKernelLinkオブジェクトに対してBeginManual()メソッドを呼び出すことによって行える.

結果をWolfram言語に送り返す前にBeginManual()を呼び出さなくてはならない.これを行わないと,リンクは同期しなくなって,今度 .NET/Link の呼出しをWolfram言語から行った場合におそらくハングアップされてしまう.2度以上BeginManual()を呼び出しても問題ないので,自分のメソッドがBeginManual()をすでに呼び出した別のメソッドから呼び出されるかも知れないということを心配する必要はない.

例題のプログラムに戻って次にBeginManual()の後で行うことは,必要な「put」形式の呼出しを行って結果をWolfram言語に送り返すようにするということ(この場合は単独のPutSymbol()だけ) である.すべてのメソッドの呼出しをラップする内部 .NET/Link コードがPutSymbol()の呼出し中に起り得る任意のWSTPエラーからのクリーンアップと回復を処理する.手動で結果を置いているときに起るMathLinkException例外に対して何も行う必要はない.メソッドの呼出しは自動的に$FailedをWolfram言語に返す.

Wolfram言語による評価をリクエストする

これまで.NETメソッドが大変簡単なインタラクションをWolfram言語と行う場合だけを見てきた.このメソッドは呼び出されて結果を自動あるいは手動で返す.しかし,もっと複雑なインタラクションをWolfram言語と持ちたいと考えるような状況も多くある.メッセージあるいは何らかのPrint出力がWolfram言語で現れるようにしたい,あるいはWolfram言語が何かを評価してその答を返すようにしたいかも知れない.これはメソッドの最後にWolfram言語に何を返したいかということとは全く異なる問題である.つまりメソッドが最終結果を手動で返すかどうかにかかわらず,メソッドのボディで評価を要求することができる.

ある意味で,このタイプのインタラクションをWolfram言語で行うということは,Wolfram言語の立場を逆転させる,しばらくの間「マスター」と「スレーブ」の役割を逆にするということである.Wolfram言語が.NET内に呼出しをかけると,.NETコードがスレーブとして働き,計算を行ってからコントロールをWolfram言語に返す.しかし.NETメソッドの最中に,Wolfram言語に呼出しをもう1度かけて,一時的にそれを.NET側の計算スレーブにすることができる.このため,基本的に「.NETからWolfram言語を呼び出す」で触れた問題とすべて同じものに出会うことが予想され,.NETプログラマーが見るように完全な .NET/Link APIを理解する必要が出てくる.

IMathLinkおよびIKernelLinkのインターフェースの完全な取扱いについては,「.NETからWolfram言語を呼び出す」で触れる.ここでは,「インストールされた」メソッドで使用することを特に意図したIKernelLinkインターフェースの特別なメソッドのいくつかについて触れる.そのうちの1つ,BeginManual()メソッドについてはすでに上で触れている.このセクションでは,Message()Print()Evaluate()のメソッドについて触れる.

Wolframシステムメッセージを.NETメソッドから発行したり,何らかのPrint出力を引き起したりするタスクは,大変頻繁に行われることであるので,IKernelLinkインターフェースはこれらの操作のために特別のメソッドを持つ.メソッドMessage()はWolframシステムメッセージを発行するためのすべてのステップを行う.

Print()メソッドはWolfram言語のPrint関数を呼び出すのに必要なすべてのステップを行う.

以下は両方を使用する例題メソッドである.以下のメッセージはWolfram言語で定義されるものとする.

以下はC#コードである.

Print()およびMessage()は,必要なコードをWolfram言語に送り,結果(常にシンボルNullとなる)をリンクから読み込むことも行う.

以下はFoo()を呼び出すと何が起るかを示す.

.NETからの浮動小数点結果がNaN (Not-a-Number,数ではない)であるときには,自動的にIndeterminateにWolfram言語へ返させることに注意されたい.

メソッドのPrint()およびMessage()は簡易関数で,自分のメソッドが結果を返す前にWolfram言語に即座の評価を送るというもっと一般的な考えの2つの特別な場合のための関数である.これを行うための一般的な手段は,Wolfram言語にEvaluatePacketで送るものをすべてラップするという方法で,これは最終結果ではなく,むしろ評価して結果を.NETに送り返すべき何かであるということをカーネルに示す方法である.明示的にEvaluatePacketの頭部を送る,あるいはEvaluatePacketを使うIKernelLinkのメソッドの1つを使うことができる.これらのメソッドは,Evaluate()EvaluateToInputForm()EvaluateToOutputForm()EvaluateToImage()EvaluateToTypeset()である.これらの詳細については .NET/Link APIドキュメントを参照されたい.

以下は簡単な例である.

例外を投げる

メソッドが投げる例外は .NET/Link で巧みに処理されるので,結果として例外を表現するメッセージをWolframシステムで出力する.これについては「例外」で触れている.計算をWolfram言語に送っている場合は,前セクションで述べたように,例外が予期しないところでコードに割り込まないようにすることが必要である.つまり,Wolfram言語とのトランザクションを始めたら,それを必ず完了しなければならない.さもなければ,リンクが同期しないままになり,その後で.NETへ行う呼出しがおそらくハングアップしてしまう.

メソッドを割込み可能にする

完了するのに時間がかかりそうなメソッドを書いている場合は,これをWolfram言語から割込み可能なものにすることを考慮すべきである.C WSTPプログラムでは,WSAbortという名前の大域変数がこの目的のために提供されている..NET/Link プログラムでは,WasInterruptedプロパティをIKernelLinkインターフェースで呼び出すとよい.

以下は長い計算を行う例題メソッドである.このメソッドは,ユーザが計算を放棄しようとしているかについて(評価メニューの評価を放棄のコマンドを使って)100個の反復をすべてチェックする.

このメソッドは,ユーザが放棄しようとしていることを検知した場合には0を返すが,この値がWolfram言語によって見られることは決してない.これは,.NET/Link がコードの中に放棄を検知するかどうかにかかわらず,放棄されるメソッド,プロパティ,コンストラクタの呼出しのいずれもをAbort[]に返させるからである.このため,放棄を検知し,ユーザの要求に従いたいと考えた場合には,何らかの値をすぐに返すだけでよい..NET/LinkAbort[]を返すと,ちょうどAbort[]がWolfram言語コードに埋め込まれているかのように,ユーザの計算全体が放棄されてしまう.これはつまり,Wolfram言語に放棄を伝えなおすことの詳細について心配する必要はないということである.放棄の要求を検知した場合は,時期尚早に返すことだけを行えばよく,残りについては何もしなくても処理される.

.NET/Link は割込み要求と放棄要求の区別を行わないので,どちらもWasInterruptedTrueを返させる.Wolfram言語には計算を中断する場合と放棄する場合とでは別々のコマンドがあることを思い出していただきたい. 「Abort(放棄)」操作(WindowsではAlt+. )は,計算全体をできるだけ早く終了して$Abortedを返す. 「Interrupt(中断,割込み)」操作(WindowsではAlt+,)は,さらに選択を行うためのダイアログボックスを表示する..NETメソッドの実行中にこの「Interrupt(割込み)」ダイアログボックスが引き起された場合には,通常のWolfram言語コードの実行中に出てくるボックスのボタンとは異なるボタンがこのボックスには入っている.オプションの1つがSend Abort to Linked Program(リンクされたプログラムに放棄を送る)で,もう1つがSend Interrupt to Linked Program(リンクされたプログラムに割込みを送る)である.どちらを選んでも,.NET メソッドに対する効果は同じである.つまり,WasInterruptedtrueを返させ,呼出しが完了するとAbort[]を返させる.3つ目のボタンはKill Linked Program(リンクされたプログラムを止める)で,.NETランタイムを終了させる.割込みが可能ではない.NETメソッドを呼び出す場合は,このように.NETランタイムを止めることがメソッドの呼出しを終了させる唯一の方法である.Windowsのタスクマネージャを使っても .NETランタイムを止めることができる。

ときには.NETメソッドに放棄を検知させ,Wolfram言語の計算全体を放棄する以外の操作を行いたい場合もある.例えば,その点までの結果を止めたり返したりするループを作りたい場合があるかもしれない.ただし,この操作は通常お勧めする操作ではないことに注意されたい.ユーザは,放棄要求を出した場合にプログラムが放棄され,$Abortedが返されることを期待する.しかし,特にコードが大きなコミュニティで使われることを意図していないような場合には,ただ単に計算を放棄してしまう代りに,自分の.NETコードに何らかの情報を交信するための「メッセージ」として放棄を使うことが便利なこともある.この考え方は,放棄が全体に伝播して計算全体が放棄されてしまわないように,放棄を検知して吸収してしまう関数であるWolfram言語のCheckAbort関数に似ている..NET コードで放棄を「吸収」して .NET/LinkAbort[]を返さないようにするためには,WasInterruptedプロパティをfalseにリセットするだけでよい.

以下はその例である.

独自のイベントハンドラのコードを書く

イベントを取り扱う」では,ボタンをクリックする等の.NETで引き起されたイベントに対する反応としてWolfram言語に呼出しを引き起すことについて紹介した.AddEventHandler関数は,Wolfram言語でイベントハンドラを設定するための簡単な手段を提供する.もちろん,AddEventHandlerを使わなければならないということではない.任意の.NET言語で独自の代表を作成して,イベントを処理したりコードに直接Wolfram言語への呼出しを挿入したりすることができる.この方法を選ぶ場合は,Wolfram言語に呼び出すイベントハンドラのコードを書く際に必ず守らなければならない大変重要な規則がある.Wolfram言語に計算を送る前に必ずRequestTransaction()を呼び出さなくてはならないということである.RequestTransaction()は,Wolfram言語が.NETからの呼出しを引き受ける状態にない場合に,例外を投げる.希望するならこの例外を捕まえてもよい.あるいは,これを無視して,.NET/Link が捕まえて警告ビープを出すようにしてもよい.言い換えれば,カーネルと .NET/Link の準備ができていないときにカーネルを呼び出そうと試みて,.NET/Link の内部に干渉することを防ぐ.

正確には,RequestTransaction()は以下の条件の1つが満たされない限り例外を投げるということである.

自分の.NETクラスをデバッグする

自分の好きなデバッガを使ってWolfram言語から呼び出された.NETコードをデバッグすることができる.唯一の問題は,これを行うためには一般にデバッガ内部で.NETプログラムを起動しなくてはならないということである.起動しなくてはならない.NETプログラムはInstallableNET.exeで,これは通常InstallNETを呼び出すと起動されるプログラムである.このプログラムはWolfram.NETLink.dllアセンブリファイルのすぐ隣のNETLinkディレクトリにある.

Visual Studio .NETを使っていて,.NET/Link で使っているクラスライブラリプロジェクトをデバッグしたい場合は,厳密なステップは使用している言語によって変わってくる.プロジェクトを選択し,ProjectメニューからPropertiesを選ぶ.Configuration PropertiesセクションのDebuggingパネルで,スタートアップのアプリケーションをInstallableNET.exeプログラムに設定する.コマンドラインの引数を-linkmode listen -linkname fooのようなものに設定する.その後デバッガを起動する.InstallableNETプログラムが起動され,Wolfram言語が接続されるのを待つ.Wolframシステムセッションの中で以下を実行する.

この操作は,InstallNETLinkObjectを引数として取ることができ,.NET自体を起動しようとしないので,うまくいく.これでWSTP接続を手動で.NETとWolfram言語の間に設立して,その後でそのリンクをInstallNETに与え,Wolfram言語と.NETの側をお互いがインタラクトできるように準備する残りの作業を行わせることができる.

DLLをWolfram言語から呼び出す

はじめに

このセクションでは,.NET/Link を使ってDLL関数をWolfram言語から呼び出す方法について触れる.これは従来のWindows DLLで,一般にC言語ライブラリ(ただし,他にもこのようなDLLを作成できる機能を持つ言語は多くある)である..NETの用語では,このような型のDLLは,.NETランタイム内で実行されないので,「アンマネージド」と呼ばれる.アンマネージドの関数を呼び出すタスクは,.NETとは全く関係ないように見えるが,.NET/Link はそのような関数を呼び出すために.NETの既存の施設を活用することができる.言い換えれば,.NETがDLLを呼び出せるので,Wolfram言語が.NETを呼び出せて,容易にWolfram言語でDLLを呼び出すことができるようになるのである.

外部のC関数を容易にWolfram言語から呼び出せるということは,Windowsプログラマーは,外部関数をラップして直接WSTP交信を処理するために,他のいわゆる「テンプレート」のWSTPプログラムを書く必要がほとんど全くないということである.別のチュートリアルでは,.NET/Link が.NETコードを呼び出すための特別のプログラミングの必要性をどのようにして排除するかを示した.このチュートリアルでは,.NET/Link がどのようにしてレガシーDLLを呼び出すこれらのステップも排除するかについて見ていく.

多くのプログラミング言語で,単に1行のコードを使ってDLL 関数を「宣言する」ことによって,これらの関数を呼び出すことができる.以下は,いくつかの言語によるそのような宣言の例である.関数はGetTickCount()であり,これはkernel32.dllで定義されるWindows APIの一部である.

Wolfram言語関数のDefineDLLFunctionは上記の宣言に似ていて,DLLの名前,関数の名前,戻り値と引数の型を指定する.

DefineDLLFunctionは関数を返す.シンボルにこれを割り当てた後で,そのシンボルを関数の名前として使うとよい.

DefineDLLFunctionには4つの引数がある.第1引数はDLL内の関数の名前である.第2引数はDLLの名前で,DLLに完全パス名あるいは単にそのファイル名を与えることができる.(「DLLの見付け方」でどのようにしてDLLが .NET/Link で見付けられるかについて詳しく触れている.)第3引数は文字列として与えられる戻り型である.第4引数は引数の型のリストである.関数がGetTickCount()のように零個の引数を取る場合は,空のリストを指定する.型の指定は文字列であり,.NET/Link はさまざまな方法で型の指定をサポートする.型の指定については,「引数と戻り値を指定する」で詳しく触れる.

DefineDLLFunction["funcName","dllName",returnType,{argType,...}]指定のDLLから指定の関数を呼び出すのにふさわしいWolfram言語関数を作成する
DefineDLLFunction["declaration"]C#構文で与えられた完全な外部関数宣言からWolfram言語関数を作成する

DLL関数を定義する.

DefineDLLFunctionはいくつかのオプションをサポートしている.まず最初のCallingConventionは, DLL関数が標準慣例とは異なる呼出し慣例を使う場合に使用する必要がある関数である.ここで標準慣例とは,Windows CE以外のバージョンのWindowsにおけるstdcall のことである.まれに関数はcdeclの呼出し慣例を使うこともある.呼出しメソッドがC++クラスの場合は,thiscall慣例を使うことができる.これらの値についての詳細は,System.Runtime.InteropServices.CallingConvention列挙についての.NET Frameworkドキュメントを参照されたい.ほとんどの場合,CallingConventionオプションはデフォルト設定のまま置いておかれる.

MarshalStringsAsオプションについては,セクション「文字列 」で,ReferencedAssembliesオプションについては,セクション「特別の属性を必要とする宣言」で触れている.

オプション名
デフォルト値
CallingConventionAutomaticDLL関数で予期される呼出し慣例(可能な値は,"StdCall","CDecl","ThisCall",Automaticである)
MarshalStringsAs
"ANSI"
文字列の引数 (char*, string,String) をどのようにしてDLL関数にそしてDLL関数からマーシャルされるべきか(可能な値は,"ANSI","Unicode",Automaticである)
ReferencedAssembliesAutomatic宣言で参照されるアセンブリの名前のリスト

DefineDLLFunctionのオプション.

DLLの見付け方

DefineDLLFunctionの第2引数は,関数が存在するDLLの名前である.DLLへの完全なパス名を指定することができるし,あるいは単にファイル名を与えて .NET/Link の自動検索機能がそれを見付けてくれることに頼ることもできる.DLLがシステムのPATHに置かれている場合,あるいはWolframシステムアプリケーションディレクトリ内の特別なサブディレクトリに置かれている場合は,そのファイル名だけを使って見付けることができる.このアプリケーションディレクトリの自動検索のおかげで,ユーザにアプリケーションディレクトリ外の別の場所にDLLファイルをインストールさせなくても,2つ以上のDLLを含むWolframシステムアプリケーションを配布できるようになる.

Wolframシステムアプリケーションは一般に単独のディレクトリとして(サブディレクトリと一緒に)配備され,Wolframシステムが見付けられる標準の場所のいずれかにインストールされる.これらの標準の場所は,$InstallationDirectory\AddOns\Applications,$BaseDirectory\Applications$UserBaseDirectory\Applicationsとして書くことができる.ここで$InstallationDirectory$BaseDirectory$UserBaseDirectoryはこれらの組込みのWolfram言語シンボルで与えられる場所を参照する.Wolframシステムアプリケーションに .NET/Link, を通して呼び出されることを意図したDLLが含まれる場合は,アプリケーションディレクトリをこれらの標準の場所のいずれかにインストールする必要があり,DLLはアプリケーションディレクトリのLibraries\Windowsサブディレクトリに置かれなければならない.「.NET/Link を使うアプリケーションを配布する」ではアプリケーションディレクトリのレイアウトについてさらに詳しく触れる.

DefineDLLFunctionを呼び出すときにはDLLを見付けようという努力はなされない.これは関数が最初に呼び出されたときにだけ起ることである.つまり,.NET/Link がDLLあるいはその中にある指定の関数を見付けることができない場合は,関数が呼び出されるときだけエラーメッセージが表示され,関数が定義されるときには表示されない.

引数と戻り値を指定する

はじめに

戻り値と引数の型を特定する場合は,intdoublevoid等の文字列を使う.自分が最も使いやすい言語(C, C#,Visual Basic .NETのいずれか)の構文に従う型の名前を使ってよい.また,WORDBOOLLPSTR等Windows APIで使われる型の名前の多くを使うこともできる.ほとんどの場合,C言語の関数プロトタイプから作業をすることになり,そのプロトタイプからの名前を直接使うのが最も便利である.例えば,Standard Cライブラリのmath.hヘッダファイルからfloor()関数の以下の宣言を使うとしよう.

Wolfram言語からこの特定の数学関数を呼び出したいことはほとんどあり得ないが,これは,誰もが持っているDLLからの簡単な例として役立つ.Windowsでは,CのランタイムライブラリはDLLのmsvcrt.dllの中にある.以下はDefineDLLFunctionを使ってfloor()関数を呼び出すWolfram言語関数を作成する1つの方法である.

DefineDLLFunctionでは,Cの型の名前を直接使うことができる.これはヘッダファイルからC言語のプロトタイプを見るときには便利である.以下は同じ定義を作る同等の方法である.

C#あるいはVisual Basic .NETの構文を型の名前に使うことは,これらの言語の1つのサンプルコードのいくつかから外部関数宣言をコピーしている場合には便利である.実際,DefineDLLFunctionを使う最も簡単な方法は,C#あるいはVisual Basic .NETのサンプルコードの外部関数用の既存の宣言を見付けて,その宣言で使われている型の名前を単にコピーするという方法である.以下ではfloor()のための宣言がこれらの言語でどのようになるのかを示している.

Visual Basic 6のDeclare Function文を正しい型の名前のガイドとして使うこともできるが,VB 6とVB .NETとの間にはいくつかの重要な違いがあることに注意されたい.まずVB 6では,パラメータはデフォルトでByRef(これらはVB .NETではデフォルトでByValである)であるので,VB 6 宣言でのDoubleのような型の名前は,ByRef Doubleと変換されるべきである.また,VB 6のIntegerは,VB .NETのShortに等しく,VB 6のLongは,VB .NETのIntegerに等しい.VB .NETに適切な型の名前を使う必要がある.

以下のサブセクションでは使用できる型の名前についてさらに詳しく見ていく.

プリミティブ型

以下の表は,どの型の名前をプリミティブ型(例えば,整数,実数,ブーリアン等)に使うことが正しく,どの型をそれらのプリミティブ型がWolfram言語にマップするかについて示している.

外部関数宣言の型
Wolfram言語の型
C言語の名前:Integer
char, int, short,     long (and unsigned versions)
C#の名前:
byte, sbyte, char, short,     int (and unsigned versions)
Visual Basic .NETの名前:
Short, Integer,     Long (these are all ByVal)
.NET Frameworkの名前:
Byte, SByte, Char, Int16, UInt16, Int32, UInt32, Int64, UInt64
Win32 APIの名前:
BOOL, BYTE, SHORT, INT, UINT, LONG, WORD, DWORD, LPARAM, WPARAM
float, double, Single, DoubleReal
bool, BooleanTrue あるいは False
void, Void戻り値に使われる場合はNull;ゼロ引数の関数には {} を引数の型のリストとして使う

DefineDLLFunctionのプリミティブ型に対する正しい型の指定.

上の型の名前を使うことは簡単であるはずである.longは標準Windows C long(4 バイト)を意味し,8バイトであるC# longは意味しないことに注意されたい.またWindows APIのBOOL型は,TrueおよびFalseではなくむしろ整数(0および非零)にマップされる.プリミティブ型のへのポインタとプリミティブ型の配列については,後のサブセクションで触れる.

文字列

文字列を取って返すDLL関数を呼び出す際に気を付けなくてはならない細かいことがいくつかある.これらは, DLL関数が文字列をどのように(ANSIスタイル,1バイト,ヌル終端の文字列,あるいはUnicode,2バイト,ヌル終端の文字列として)表示することを期待するかということにかかわっている. データがシステムの境界上を移動するにつれてそれを1つの表記法から別のものに変換するプロセスはマーシャリングと呼ばれる.ほとんどのDLL関数ではこのよくあるC文字列の形式を処理するように書かれているので,アンマネージドコードを呼び出す際の.NETのデフォルトの動作は,文字列を1バイトでヌル終端の文字列としてマーシャルするということである.デフォルト以外の動作が必要な場合は,DefineDLLFunctionMarshalStringsAsオプションを使うとよい.

以下は,異なる型の文字列を操作するWindows Cランタイムライブラリからの2つのDLL関数の例である.それぞれが文字列を小文字に変換する.ここで,_strlwr()関数はANSIの文字列を取り,_wcslwr()関数はワイド文字の文字列を取る.

以下は,これらの関数の両方に対するDefineDLLFunctionの呼出しである._wcslwr()関数はワイド文字の文字列を取って返すので,文字列のデフォルトのマーシャリングをオーバーライドする必要がある.

どちらの関数も1バイトにフィットする文字を持つ文字列に対しては同じように動作する.

予想されるように,strlwr関数は2バイトを必要とする文字を持つ文字列に対しては不成功に終る.π の文字は1バイトに切り捨てられる(この例では,切捨ては文字列が.NETからDLLに渡される際に起るが,DLLから出る際にも起る)ことに注意されたい.

ワイド文字のバージョンを呼び出す場合はうまくいく.

パラメータあるいは戻り型として2つ以上の文字列を持ち,文字列が異なる方法でマーシャルされなくてはならない場合には,MarshalStringsAsオプションは関数のすべての文字列に適用されるので,この関数を使うことはできない.代りにDefineDLLFunctionの特別の「完全宣言」形式を「特別の属性を必要とする宣言」で説明するように使うことができる.

外部関数宣言の型
Wolfram言語の型
C言語の名前:String
char*
C#の名前:
string
Visual Basic .NETの名前:
String
.NET Frameworkの名前:
String
Win32 APIの名前:
LPSTR, LPCSTR

DefineDLLFunctionの文字列に対する正しい型の指定.これらはすべて等しい.

_strlwr()および_wcslwr()に対するDefineDLLFunctionでは,C#構文である型の名前string を使って文字列を示した.上の表で示すように,以下はまったく同等の宣言である.

ここまで関数の[in]パラメータとして使われる文字列だけを見てきた.つまり,文字データが関数の中に送られている場合だけを見てきた.char*を取るように入力される関数の中には,文字列を[out]パラメータとして使うものもある.つまり,これは実際には関数によって書き込まれるバッファである.文字列を[out]パラメータとして使う関数は一般に,割り当てた文字列のバッファの長さを与える余分な引数で渡すことを必要とするか,あるいはこれらの関数がバッファに書き込むことになる文字の最大数をドキュメント化する.文字列にデータを書き込む関数の例は,馴染み深いStandard Cライブラリ関数のsprintf()である.

bufferの引数は,関数が書き込む文字列である.これは[out]パラメータであり,データの文字列をこの関数に上書きするために渡すことはできる(ただし文字列は書かれたデータが文字列の長さを超過しないだけの長さを持っていなくてはならない)が,修正した文字列をもとに戻すことはできない..NETランタイムでは[out]文字列パラメータで作業するための特別の方法をサポートする.これは,System.Text.StringBuilderクラスのインスタンスを使用するという方法である.StringBuilderは文字のバッファとしてアンマネージド関数にマーシャルされる.関数が戻った後で,StringBuilderオブジェクトがバッファに書き込まれたデータを保持する.StringBuffer.ToString()メソッドを使ってデータを文字列として抽出することができる.

これがsprintf()関数を使ってどのように行われるかを見てみよう.この関数は変数引数カウントを取るが,DefineDLLFunctionはこれを処理できないので,書式文字列(3つの完全引数)に続く1つの整数引数の場合について特別にバージョンを定義する.

上のDefineDLLFunctionへの呼出しで1つ注意しなくてはならないことは,const限定子を任意の引数スロットについて指定することができるということである.これは .NET/Link の目的には関係がないので,.NET/Link には無視されるが,宣言をもっと自己文書化するのに役立つと考える場合や,ただやみくもにC関数のプロトタイプをコピーしている場合には,この限定子を使ってもよい.

sprintfを呼び出すためには,まずその中に書き込まれるかもしれないデータがすべて入るのに十分な大きさのバッファを持つStringBuilderオブジェクトを作成する.この例では小さな文字列を使うので,20バイトで十分である.

戻り値はバッファに書き込まれる文字の数である.文字列を見るためには,ToString()を呼び出す.

配列とポインタ

ポインタあるいは配列を取ったり返したりする関数を取り扱っている場合は,もう少し注意を払って,呼び出している関数でパラメータがどのように取り扱われているかを必ず理解する必要がある.例えば,型int*のパラメータがある場合,このパラメータは以下のいずれかである.

関数ポインタ

DLL関数の中には,コールバック関数ポインタを引数として取るものがある..NETは関数ポインタを代表にマップするので,関数ポインタ引数に適切な型の代表オブジェクトを渡す.「イベントを処理する」ではNETNewDelegate関数とそれがDLL呼出しの中で関数ポインタの代表オブジェクトを作成するためにおもに使われることが紹介されている. 関数ポインタについて適切なシグナチャが付いた既存の.NETの代表の型がないということがよくあるが,DefineNETDelegate関数を使って適切なシグナチャ付きの.NETの代表の型を作成することができる. EnumWindows.nbの例題ファイルは,DefineNETDelegateおよびNETNewDelegateを使ってコールバック関数ポインタを引数として取るDLL関数を呼び出すことを示す.

特別の属性を必要とする宣言

.NETランタイムは,関数がどのように呼び出されて引数がどのようにマーシャルされるかを精密に制御するために数多くの属性をサポートする.DefineDLLFunctionCallingConventionおよびMarshalStringsAsのオプションは,これらの側面にある程度のコントロールを与えるが,使用できる属性すべてを十分にサポートするものではない.以下は,Windows APIからのMoveFile()関数の複雑なC#宣言に関する.NET Frameworkのドキュメントからの例である.この宣言は,わざとかなり複雑なものになっているが,可能な属性のいくつかを示している.

DefineDLLFunctionのオプションでできる以上の属性を指定する必要がある場合は,完全な宣言をC#構文の文字列として指定できる別の形式を使うとよい.

このバージョンの .NET/Link では,C#構文だけがサポートされていて, Visual Basic .NETはされていない.

この型の完全な宣言が必要となる関数のもう1つの例は,別々のマーシャリング慣例が必要な2つの文字列引数を持つ関数である.

以下はどのようにしてこれをWolfram言語で定義するかを示す.

DLL宣言がSystemアセンブリにはない型を使う場合には,ReferencedAssembliesオプションを使ってそのアセンブリを指定する必要がある.これはVisual Studioプロジェクトでアセンブリに参照を加えるのに似ている.以下はこのオプションを使った例である.RectangleクラスはSystem.Drawingアセンブリにあるので,これを明示的に参照アセンブリとして指定しない限りエラーが出る.

例題ファイル

.NET/Link に含まれている以下の例題ファイルは,CスタイルのDLLをWolfram言語から呼び出すことを示す.

BZip2Compression.nb

WindowsAPI.nb

EnumWindows.nb

COMをWolfram言語から呼び出す

はじめに

.NETランタイムには,COM(コンポーネントオブジェクトモデル.ActiveXとも呼ばれる)との相互運用性をサポートする数多くの機能が含まれている..NETの登場によって,COM/ActiveXは公けには「レガシー」テクノロジーとなったが,今もまだ使われている数多くのCOMオブジェクトとライブラリがあり,COMはまだWindowsのプログラミングの世界で重要な位置を占めている.COMオブジェクトは簡単に.NETから呼び出すことができるので,Wolfram言語から .NET/Link を通して呼び出すことも簡単にできる.

COMプログラミングは複雑(Microsoftがこれを.NETに取って代らせた理由の1つはここにあることは間違いない)であり,これを読んでおられる皆さんがCOMについての基本的な知識を持っているものとここでは想定する.COMと.NETの相互運用性の問題についてはずいぶん詳しく.NET SDKのドキュメントで取り上げられている.

.NETからCOMへの相互運用性の中心的要素は,ランタイム呼出し可能ラッパー (Runtime Callable Wrapper: RCW) と呼ばれる特別のプロキシオブジェクトである.COMオブジェクトを作成してそれを.NET環境にインポートしたい場合はいつでも,.NETランタイムが.NET環境内のCOMオブジェクトを表すRCWオブジェクトを作成する. RCWは.NETからCOMへの呼出しの仲立ちをし,引数をまとめて値を.NETとCOMの世界の間で行き来させる. RCWオブジェクトの例はこの後のセクションに掲載されている.

.NET/Link はCOMオブジェクトをWolfram言語から呼び出す2つの主要な方法を提供する.最初の方法はいわゆるCOMオートメーション(遅延結合)を使う方法である.これは,準備が不要であるため便利であるが,後で触れるようにさまざまな理由から理想的な方法であるとは言えない.好まれる2つ目の方法は,呼び出したいCOMオブジェクトに対して相互運用アセンブリを作成,あるいは獲得するという方法である.相互運用アセンブリは,COMライブラリをラップしてライブラリの型とインターフェースをネイティブの.NETの型ように見せる特別の.NETアセンブリである.COMオブジェクトを呼び出すこれらの2つのメソッドについて次の2つのセクションで触れる.

オートメーション(遅延結合)を使う

COMインターフェースは,基本的には関数ポインタの表にすぎない.これはC++プログラマーが使用するのには理想的であるが,COMオブジェクトを使用するために言語をスクリプトする方法が必要である.しかし,この言語にはコンパイルの段階がなく,C++ヘッダファイルへのアクセスがない.この問題を解決するのが,IDispatchと呼ばれる特別のCOMインターフェースである.IDispatchを実装するCOMオブジェクトは,オブジェクトのユーザがランタイムに使用できるメソッドとプロパティを見付けて,それらを呼び出せるようにする.IDispatchは,COMにおいて .NETの「リフレクション」機能に匹敵するものである.COMオブジェクトをそのIDispatchインターフェースを通して使用することは,しばしば遅延結合,オートメーション,ディスパッチ等と呼ばれる.ここではこれをオートメーションを呼ぶ.

すべてのCOMオブジェクトがオートメーションをサポートするわけではないが,できる限り幅広い種類のプログラミング言語と環境で使えるようにしたいCOMオブジェクトの多くがこれをサポートしている.Visual Basic 6ではCOMオブジェクトをオートメーションあるいは初期結合(後で触れる)を通して使うことができる.しかしVBScriptを含めたほとんどのスクリプト言語では,オートメーションしか使えない..NET/Link ではオートメーションも初期結合も使える.初期結合の方が好まれる方法で,これについては,次のセクションで触れる.ここではオートメーションに焦点を当てる..NET/Link でオートメーションを通してCOMオブジェクトを使うことは,Visual BasicあるいはVBScriptでオートメーションを使うのとほとんど全く同じである.自分が興味を持つCOMオブジェクトを使用するサンプルコードをこれらの言語のいずれかで見付けたら,一般にそのコードを一字一句違えずWolfram言語に変換することができる.

COMライブラリの例として,このセクションではMicrosoft Speech APIを使用する.MicrosoftはいずれそのAPIのすべてを純粋な.NET実装に移動させるであろうが,COMオブジェクトとしてしか使用できないものもまだ多くあり,Speech APIはその1例である.(MicrosoftにはSpeech Application SDKと呼ばれる.NETベースの言語ツールがある.これは,テレフォニアプリケーションを作成しているASP .NETの開発者用のものであり,古いCOMベースのSpeech APIとは違うことに注意されたい.後者が次の例では使われている.)単に入出力を読めばよいので,このセクションを理解するのにSpeech APIをインストールする必要はないが,入力を再評価したい場合,あるいは自分で実際に操作してみたい場合は,Speech APIを http://www.microsoft.com/speech/download/sdk51/ からダウンロードするとよい.CreateCOMObjectを呼び出す行がうまくいかない場合は,Speech APIがインストールされていないということである.

オートメーションを通したコントロールのためのCOMオブジェクトを作成する基本関数はCreateCOMObjectである.この関数は,Visual BasicのCreateObject()関数に似ている.CreateCOMObjectの引数は,COM coclassのProgIDあるいはCLSIDを提供する文字列である.ProgIDがExcel.Application等の人間が読み取れる文字列であるのに対し,CLSIDは"{000208d5-0000-0000-c000-000000000046}"等の16進数字列である.COMオブジェクトのCLSIDあるいはProgIDは,そのドキュメントから,あるいはもっとよい方法としては,それを使うVisual Basicのサンプルコードから得ることができる.

CreateCOMObject["ProgID"]指定のProgID(例えば,Excel.Application)を持つCOMオブジェクトを作成する
CreateCOMObject["CLSID"]指定のCLSID(例えば,{000208d5-0000-0000-c000-000000000046})を持つCOMオブジェクトを作成する
GetActiveCOMObject["ProgID"]指定のProgID(例えば,Excel.Application)を持つすでにアクティブなCOMオブジェクトへの参照を得る
GetActiveCOMObject["CLSID"]指定のCLSID (例えば,{000208d5-0000-0000-c000-000000000046})を持つすでにアクティブなCOMオブジェクトへの参照を得る

COMオブジェクトを得る.

以下はSpVoice COMオブジェクトのインスタンスを作成する.

voiceオブジェクトは,これまで見てきた.NETオブジェクトは異なっている.このオブジェクトは,「はじめに」で触れたオブジェクトのクラスであるランタイム呼出し可能ラッパー (RCW) である.これは.NETの世界のCOMオブジェクトを表すプロキシオブジェクトであると考えることができる.ほとんどの.NET オブジェクトでは,オブジェクトのOutputForm表記のカッコの中の文字列は,オブジェクトの.NETの型の名前を与える.voiceオブジェクトでは異なっていて,文字列はオブジェクト(SpeechLib.ISpeechVoice)でサポートされるデフォルトのCOMインターフェースの名前を示す.以下は,オブジェクトの実際の型の名前である.

もう気付いている方もいらっしゃると思うが,System.__ComObjectはRCWクラスの名前である..NET オブジェクトがWolfram言語で<<NETObject[System.__ComObject]>> と表されているのを見ても,これは任意のCOMオブジェクトであり得るために,何もオブジェクトについて何も語らないので,あまり情報としては役に立たない..NET/Link がRCWオブジェクトをWolfram言語に返すときには,.NET/Link はオブジェクトがサポートするデフォルトのCOMインターフェースの名前を見付けようとする.これが成功すると,.NET/Link はオブジェクトを<<NETObject[COMInterface[Default.COM.Interface]]>>として報告する.覚えておかなければならないことは,これはCOMインターフェースの名前であり,.NETあるいは .NET/Link には何の意味も持たないということである.これは単にこの.NETオブジェクトが表すCOMオブジェクトについての情報を何か知らせようとするために表示されるだけである..NET/Link がデフォルトのCOMインターフェースの名前を見付けるためには,オブジェクトはCOMのタイプライブラリを通して十分に詳しい型の情報を提供する必要がある.ほとんどのCOMオブジェクトはこの機能を持っているので,COMのインターフェース名でフォーマットされたRCWオブジェクトをよく見かけることになる.しかし,デフォルトのインターフェース名の検索に失敗する場合もあり,その場合COM オブジェクトは<<NETObject[System.__ComObject]>>としてだけフォーマットされる.

オートメーションを通してCOMオブジェクトを使うことの1つの問題は,NETTypeInfoを使ってCOMのメソッドおよびプロパティについての情報を得ることができないということである.

COMオブジェクトについてメソッドやプロパティを呼び出すことはできるが,これらのメソッドを直接.NETから見ることはできない.メソッドとプロパティ,およびその引数についての情報は,COMオブジェクトのドキュメントを見る必要がある. Visual BasicのサンプルコードはしばしばCOMオブジェクトを使って見付けることができ,これをWolfram言語から .NET/Link を通して使うことは,ほとんど同じように見える.

ISpeechVoiceのCOMインターフェースは,Volumeと呼ばれるプロパティを含む.

Speak()メソッドはテキストの文字列を示す.以下は,SpeechLibのタイプライブラリのIDL ファイルからのSpeak()メソッドに対する宣言である.

ベテランのCOMプログラマーならこの宣言の要素に見覚えがあるであろう.第1引数はBSTRで,これはCOMの世界での文字列である.このような引数は.NETからの文字列と一緒に,つまりWolfram言語からの文字列と一緒に呼び出される.第2引数はオプショナルという印が付いていて,デフォルトの値は0である.これはつまりSpeak()メソッドは第2引数なしでも呼び出せるということである.

第2引数は型SpeechVoiceSpeakFlagsであるとしてリストされており,この型はどのようにテキストが話されるかを制御する定数を含むCOM列挙である.オートメーションを通してCOMオブジェクトを使う難点は,COM列挙にアクセスできる方法が全くないということである.第2引数を使うためには,enumの正しい値に対応する整数値を供給する必要がある.この情報はドキュメントから,あるいはOLE Viewのようなツールを使ってタイプライブラリそのものから得ることができる.OLE ViewはMicrosoft Visual Studioにバンドルされている.非同期的に意見を話すためには,つまりテキストが話し終える前にSpeak()メソッドが返すためには,1の値を持つフラグSVSFlagsAsyncを使う.

オートメーションを使うことの難点

上では,.NET/Link でどのようにCOMオブジェクトをそのIDispatchインターフェースを通して使うことができるかということについて見た.これはなにも準備がいらないという点では優れているが,いくつかの難点もある.

相互運用アセンブリ(初期結合)を使う

前セクションでは, .NET/Link でCOMオブジェクトをそのIDispatchインターフェースを通して使うことについて触れた.この方法はしばしば遅延結合あるいはオートメーションと呼ばれる.この方法にはいくつかの使用上の難点があるが,幸い.NET は,COMオブジェクトを初期結合と呼ばれるもっと洗練されて効率的な方法で呼び出すことをサポートする.これはCOMオブジェクトがC++で使われる方法に似ていて,メソッドの発送はIDispatchではなくvtableインターフェースを通して起る..NETで初期結合を使うためには,まずいわゆる相互運用アセンブリを作成するか見付けるかする必要がある.相互運用アセンブリは,COMのタイプライブラリの型およびメソッドを表すメタデータを含んでいる特別のアセンブリである.一旦相互運用アセンブリがあると,他のどのような.NET アセンブリとも同じようにロードして使用することができて,それを表すCOMの型はクライアントにとってネイティブの.NETの型のように見える.

相互運用アセンブリは,tlbimp.exe(タイプライブラリインポータ)と呼ばれるツールを使って作成することができる.これは.NET Framework SDK,およびVisual Studio .NETに含まれている.tlbimpの実行の仕方については多くのドキュメントが出ており,後で1つの例を見る.また,プログラム的に相互運用アセンブリを作成することも可能で,.NET/LinkLoadCOMTypeLibrary関数をこの目的に提供する.LoadCOMTypeLibraryは,COMのタイプライブラリを取り,それから相互運用アセンブリを作成し,このアセンブリを .NET/Link にロードする.これをLoadNETAssemblyに似たものと考えることができるが,代りにCOMのタイプライブラリへのパスを取るという点で異なる.

LoadCOMTypeLibrary[typeLibPath]特定のタイプライブラリから相互運用アセンブリを作成し,これをロードする

COMのタイプライブラリをロードする.

上では,COMベースのMicrosoft Speech APIをオートメーションを通して使ったが,もっとよい方法は,初期結合が行えるようにタイプライブラリをロードするという方法である.

LoadCOMTypeLibraryで作成された相互運用アセンブリは,ネームスペースと型のネーミングについてのデフォルトの規則で作成され,NETTypeInfoをアセンブリに使ってどの型が使用できるかを見るのに便利である.COMのタイプライブラリの各coclassについて,クラスは"Class"という言葉が加えられたcoclassの名前が付いた相互運用アセンブリで作成される.先ほどSpVoiceと呼ばれるcoclassがあることを見たので,相互運用アセンブリでSpVoiceClassと呼ばれる.NETクラスが見付かることが予想できる.このアセンブリには多くの型があり,以下はそのクラスだけを示している.

NETTypeInfoは相互運用アセンブリを探索する上で大変便利である.上でオートメーションを使ってCOMオブジェクトを作成したときにCreateCOMObject関数を使用した.ここではCOMのcoclassを表す.NETクラスがあるので,代りにNETNewを呼び出すことができる.

このオブジェクトはその他の.NETオブジェクトと同じように使用できる.以下はSpeak()メソッドである.なぜか第2引数のオプショナルの性質は相互運用アセンブリでは保持されないので,2つの引数でSpeak()を呼び出す必要がある.

オートメーションを使用したときには,第2引数はSpeechVoiceSpeakFlagsと呼ばれるCOM列挙であることをさきほど見た.しかし,オートメーションを使用している場合には列挙にアクセスする方法は全くなく,このため整数値を渡さなければならなかった.これに対して相互運用アセンブリでは,コードをもっと読みやすくするために使える.NET 列挙がある.

相互運用アセンブリを使う大きな利点の1つは,NETTypeInfoを使ってオブジェクトがサポートするメソッドおよびプロパティについての情報を得ることができるということである.以下は,voiceオブジェクトのプロパティである.

一旦相互運用アセンブリがタイプライブラリのためにロードされると,CreateCOMObjectをCOMのcoclassの名前で呼び出す場合に,以前のような生のRCWではなく,そのCOMのcoclassに対応するクラスのネイティブの.NETオブジェクトを返される.これはつまりCreateCOMObjectおよびNETNewは,COMのcoclassのインスタンスを作成するための同等の方法になるということである.

オプション名
デフォルト値
SaveAssemblyAsNone作成したいアセンブリファイルへの完全パス名
SafeArrayAsArrayFalseSAFEARRAYの型をSystem.Arrayとしてマーシャルするかどうか

LoadCOMTypeLibraryのオプション.

LoadCOMTypeLibraryは,アセンブリ作成のプロセスを制御する2つのオプションを取る.1つはSaveAssemblyAsで,これで作成したアセンブリを保存したいファイル名を指定することができる.LoadCOMTypeLibraryは,実行するのに時間がかかるので,アセンブリをファイルに保存して,それを将来LoadNETAssemblyを使ってロードするすると便利である.これでLoadCOMTypeLibrarytlbimp.exeツールを実行することとプログラム的には同等となり,生成されたアセンブリをその中で書き出すことができる.LoadCOMTypeLibraryの2つ目のオプションはSafeArrayAsArrayで,これはすべてのCOMのSAFEARRAY配列を入力された一次元マネージド配列としてよりもむしろSystem.Arrayクラスとしてインポートするかどうかを指定する.デフォルトはFalseである.この上級オプションの詳細については,System.Runtime.InteropServices.TypeLibImporterFlags列挙についての.NET Frameworkドキュメントを参照されたい.生成されたアセンブリに対してもっとコントロールが必要な場合は,次のセクションで触れるtlbimp.exeツールを使うとよい.

COMオブジェクトをオートメーションを通して使用することについて上で触れた際に,RCWの役割について述べた. 生のRCWのクラス名はSystem.__ComObjectであることもここで見た.相互運用アセンブリを使っているときでも,すべてのCOMオブジェクトは.NETでRCWとして表されることをここで思い出すことが大切である.以下では,SpVoiceClass__ComObjectに由来するものであることが分かる.

以下は,継承関係を証明するもう1つの方法である.

tlbimp.exeを使って相互運用アセンブリを作成する

上でも述べたように,.NET Framework SDKにはtlbimp.exe(タイプライブラリインポータ)と呼ばれるツールが含まれており,COMのタイプライブラリから相互運用アセンブリを作成する.このツールを使って相互運用アセンブリを作成し,LoadCOMTypeLibrary関数の代りにLoadNETAssemblyを使ってそれをロードすることができる. tlbimpプログラムにはどのようにアセンブリが生成されるかということを制御する多くのオプションが含まれているので,このレベルのコントロールが必要な場合は,これを間違いなく手動で実行するようにすべきである..NET Framework SDKのドキュメントには,tlbimpの使い方が詳しく述べられているが,以下にSpeechLibのタイプライブラリ用の相互運用アセンブリを作成するのにtlbimpをどのように使えばよいかの例を示す.

一旦アセンブリが作成されたら,他のアセンブリと同じようにそれをロードするとよい.

プライマリ相互運用アセンブリ

プライマリ相互運用アセンブリ (PIA) は,ベンダーの署名付きで,グローバルアセンブリキャッシュ (GAC)に置くことができるように強い名前を付けられた特別の相互運用アセンブリである.PIAは,COMのタイプライブラリのベンダーがそのタイプライブラリに対して理想的なインターフェースを表す公式の相互運用アセンブリを作成するであろうという考えのもとに作られたものである..NETランタイムは,PIAを特別で幸運なアセンブリであると認識し,関連するPIAがインストールされているcoclassにCreateCOMObjectを呼び出すときに自動的にPIAをロードする.COMライブラリを使用する場合は,ベンダーがそれ用にPIAを作成しているかどうかをチェックして,もししていたら,それをインストールすべきである.

PIAのよい例は,MicrosoftがOffice XPスイートに合うように作成したセットで,これはオートメーションのリッチオブジェクトモデルを公表している.WordあるいはExcelのようなOffice XPコンポーネントをWolfram言語から制御しようと考えている人なら誰でも,Office PIAをhttp://msdn.microsoft.com/library/default.asp?url=/downloads/list/office.asp から取得すべきである.これらのPIAはデフォルトでOffice 2003と一緒にインストールするようになっているが,Office XPとおそらくもっと古いバージョンのOfficeで使うためにもこれをインストールすることができる.

ExcelPieChart.nbの例題ファイルは,ExcelをWolfram言語から .NET/Link を使って呼び出す方法を示す.Office PIAをインストールしているしていないにかかわらずこの例を使うことができるが,PIAがあると,強く定型化された.NETオブジェクトを生のRCWオブジェクトの代りに使って作業できるという利点がある.PIAがお使いのマシンにインストールされていて,CreateCOMObjectがExcelのインスタンスを起動するために呼び出される場合は,PIAが自動的に<<NETObject[COMInterface[Excel._Application]]>>のような生のRCWではなく.NET の型をExcel の相互運用アセンブリから返す.

COMリソースを解放する

RCWオブジェクトの.NETにおける役割の1つに,それをラップするCOMオブジェクトのライフサイクルを管理するということがある.COM オブジェクトは,RCWオブジェクトが.NETのガベージコレクタによって解放されたときに破棄される.しばしばCOMオブジェクトのライフタイムに対するこのレベルのコントロールは問題ない.しかし, .NETガベージコレクタはまれにしか実行されない場合があり,一般に.NETのメモリスペース(マネージドヒープ)がいっぱいになったときにのみ実行される.RCWオブジェクトはマネージドヒープでは小さいフットプリントしか残さないが,アンマネージドCOMの世界では大変大きなオブジェクト(Excelのインスタンスのように)にくっつく場合もある.COMの使用状況によっては,数多くの解放されていなく未使用のRCW オブジェクトが多大な量のメモリと他のリソースをCOMの世界で使い続けている場合でも,.NETのガベージコレクタが実行されないこともある.このため,オブジェクトによって捕らえられたCOMのリソースを解放することを強制するような関数を持つことが大切である.その関数はReleaseCOMObjectである.

ReleaseCOMObject[obj]特定のCOMオブジェクトで所有されるCOMリソースを解放する

COMリソースを解放する.

.NETのCOMオブジェクトはすべてそれぞれ単独のユニークなRCWによって表される.2つの異なる手段によって同じCOMオブジェクトへの参照を獲得した場合には,同じRCWを毎回得ることになる.この唯一のRCWがCOMオブジェクトの参照カウントを保持する.この参照カウントはCOM内部のもので,.NETオブジェクトの参照カウントと混同しないようにしなければならない.ReleaseCOMObjectを呼び出すことが実際にCOMリソースを即時に解放することを強制することには繋がらない.ReleaseCOMObjectはこの内部のCOMの参照カウントをデクリメントするだけである.しばしばこのカウントはただの1であり,ReleaseCOMObjectはこれをゼロにしてから,リソースを解放する.ReleaseCOMObjectは新しい参照カウントを返すので,ゼロになったかどうかをチェックすることができる.

以下は同じCOMオブジェクトに複数の参照を得る例である.次の行がExcelの新しいインスタンスを起動する.可視にはならないが,プロセスのタスクマネージャのリストでそれを見ることができる.

今度はExcelのその同じインスタンスに対してもう2つ参照を得る.GetActiveCOMObject関数はCreateCOMObjectに似ているが,前者は新しいオブジェクトを作成する代りにすでにアクティブなオブジェクトを得るという点で異なる.これはCOM APIとVisual Basic 6のGetActiveObject()関数に似ている.Pauseがここで必要なのは,COMがGetActiveObject()への呼出しの間で明らかに1息つく必要があるからである.

ReleaseCOMObject[excel1]を今呼び出す場合,excel2およびexcel3のオブジェクトを通してExcel への未解決の参照が他にもあるので,おそらくExcelが終了してしまうと困るであろう.ReleaseCOMObjectを呼び出すことでExcelのインスタンスに対するCOMの参照カウントが最終的にゼロになるまでどのようにデクリメントするかに注意していただきたい.ゼロになって初めてExcel のプロセスが終了する.

これでReleaseCOMObjectを使ってタイムリーなCOMリソースの解放を強制する方法を見てきたことになる..NETのガベージコレクタによっていずれはこの解放が実行されるので,厳密にこれを行う必要は決してないが,ガベージコレクタは十分タイムリーにこれを実行しないことが多い.ReleaseCOMObjectを使う代りの方法としては,NETBlockあるいはReleaseNETObjectを確実に使用して,作成する.NETオブジェクトが解放されるようにするという方法がある.それから手動で.NETガベージコレクタを強制的に実行することができる.この方法は,たくさんのCOMオブジェクトを作成するコードを持っているような場合には特に便利である.それらすべてを追跡し,それぞれに対して ReleaseCOMObjectを呼び出すよりも,NETBlockを使って確実にオブジェクトがすべてゼロの.NET参照カウントを終了時に持っているようにしてから,ガベージコレクタを呼び出す方が容易である.以下はこれがどのようになるかのアウトラインである.

COMオブジェクトをキャストする

CastNETObject関数については「キャスティング」で触れている.この関数は普通の.NETオブジェクトにはめったに使われないが,COMオブジェクトに対して特別の任務を持っている.次の例では,使用中のマシンにMicrosoft Office XPのプライマリ相互運用アセンブリがインストールされている必要がある.次の行は,可視にはならないが, Excelアプリケーションの新しいインスタンスを作成する.

今度は新しいワークブックを作成する.

ApplicationClassのクラスは,ActiveSheetと呼ばれ,今作成したワークブックのワークシートを返すプロパティを持つ.

ActiveSheetからの結果は生のRCWオブジェクトであり,強く定型化された.NET オブジェクトではない.(生のRCWオブジェクトはクラスSystem.__ComObjectのオブジェクトであり,.NET/Link はそれをサポートするデフォルトのCOMインターフェースの名前でフォーマットしようとすることを思い出していただきたい.)Microsoft.Office.Interop.Excel.WorksheetClassクラスがワークシートを表すExcel の相互運用アセンブリにあるのに,それではどうしてActiveSheetの結果がそのクラスのインスタンスではないのであろうか.その答を見るために,ActiveSheetプロパティの宣言を見てみよう.

このプロパティはWorksheetClassではなくobjectだけを返すように入力される.なぜならアクティブなシートは表またはワークシートであり得て,これらは異なるクラスであるからである.相互運用アセンブリでは,objectを返すようにタイプされたメソッドあるいはプロパティは生のRCWを返す.これは.NET内のCOMオブジェクトは通常の.NETオブジェクトとは異なるものであるということを思い起こさせる.任意のオブジェクトがCOMから.NETへマーシャルされると,それは必ず生のRCWとして到着する.相互運用アセンブリからの型の情報の助けを得て,.NETランタイムは生の RCWを特定のマネージドの型にキャストすることができる.例えば,メソッドがクラスXを返すようにタイプされ,メソッドがCOM オブジェクトを(RCWとして)返す場合は,.NETはRCWをメソッドから返す前に型Xにキャストする.メソッドがActiveSheetプロパティのようにobjectだけを返すようにタイプされる場合は,.NETがオブジェクトをキャストするために使える型の情報がないので,生のRCWを得ることになってしまうのである.

ActiveSheetで返されたオブジェクトを使うことができるが,型の情報がないので,それを遅延結合を通して呼び出すことになる.初期結合を通して呼び出せる強く定型化されたオブジェクトを作成したい場合は,C#あるいはVisual Basic .NETで行うのと全く同じ操作を行う,つまりオブジェクトを希望の型にキャストする.一旦希望するマネージドの型にキャストしたら,遅延結合の代りに相互運用アセンブリを使用することから得られる利点をまた持つことができる.この例では,アクティブシートがワークシートであることが分かっているので,それをWorksheetClassにキャストすることができる.

この説明が分かりにくい場合は,全く同じことがC# あるいはVisual Basic .NETで行われるということを思い出すとよい.以下はC#を使った場合である.

COMオブジェクトが指定のマネージドの型にキャストできない場合は,CastNETObjectはメッセージを発行し,$Failedを返す.

キャスティング」では,オブジェクトは常にその真のランタイムの型を持っているので,.NET/Link でダウンキャストを行う必要は全くない,つまり継承階層中にダウンキャストできる低い階層の型が全くないと述べた.この規則は,COMオブジェクトをキャストすることについては当てはまらない.なぜなら,ある意味ですべてのCOMオブジェクトのランタイムの型は,生のRCWのクラスである__ComObjectにすぎないからである.型の情報があると,.NET ランタイムは自動的にもっと派生したマネージドの型にダウンキャストすることができる.プロパティあるいはメソッドがobjectだけを返すようにタイプされると,正しい型が分かっていることを条件としてオブジェクトを自分でダウンキャストすることができる.

COMイベントを処理する

.NETランタイムはCOMイベントをどのように.NETイベントにマップするかを知っている.つまり,COMオブジェクトで引き起されたイベントに応えるということは,.NET オブジェクトで引き起されたイベントに応えることとちょうど同じようなものであるということである.「相互運用アセンブリ」を使ってWolfram言語コードでのCOMイベントを処理する必要がある.ここでは遅延結合を使うことはできない.

以下の例はInternet Explorerで引き起されたCOM イベントの処理方法を示している.Internet Explorerは HTMLウィンドウのコンテンツのためのリッチオブジェクトモデルをサポートする.これはドキュメントオブジェクトモデルと呼ばれ,mshtmlのCOMライブラリを通してクライアントに公表されている.Microsoftは.NET Frameworkと一緒にmshtmlのプライマリ相互運用アセンブリをバンドルして,これを.NETプログラムから使いやすいようにしている.おそらく将来Internet Explorerおよびmshtmlの ネイティブの.NETバージョンができることと思うが,今の時点では,その他多くのCOMベースのMicrosoftテクノロジーと同様に,これらを相互運用アセンブリを通して.NETから使う.以下に開発された例では,Internet ExplorerウィンドウのWebページが表示され,ユーザがページの要素上でマウスを動かすのに合わせて要素がそのフォントの大きさをランダムに変える.

mshtmlのCOM要素とその関連のプライマリ相互運用アセンブリは,Internet Explorerウィンドウのコンテンツを管理するだけである.Internet Explorerアプリケーションは,生のCOMオブジェクトに対して普通の方法で作成されなければならない別のCOMオブジェクトである.

このオブジェクトがCOMInterface[...] でフォーマットされているという事実は,これが生のRCWであり,オートメーションを通してしかインタラクトできないということを示している.これは,少ないプロパティしか必要ではないので,問題ない.COMオブジェクトのドキュメントは,オンラインあるいはVisual Studioのヘルプシステムにある MSDNライブラリで探してみるとよい.

まず,URLにナビゲートする.

今度はブラウザウィンドウを可視にして,ページが完全にロードされるまでループさせる.

Internet Explorerアプリケーションではなく,それを含むドキュメントオブジェクトとインタラクトしたいので,そのオブジェクトを得る.

このオブジェクトは,強く定型化された.NETオブジェクトであって,生のRCWではない.ドキュメントオブジェクトモデルに対してプライマリ相互運用アセンブリがあるので,そのようなCOMオブジェクトが.NETにインポートされるときはいつも,このオブジェクトはドキュメントのCOMのcoclassにマップされる.NETクラスに自動的にラップされることができる.これで強く定型化されたオブジェクトを相互運用アセンブリから使えるようになる.

オブジェクトによって引き起されたCOMイベントは,相互運用アセンブリの.NETイベントにマップされる..NETクラスが実装する [source] COMインターフェースすべてで,それぞれのメソッドの.NETイベントのメンバを見ることができる.NETTypeInfoを使って,HTMLDocumentClassクラスによって引き起されたイベントを見ることができる.このクラスはずいぶんたくさんあるので,ここでは必要なものだけを示す.

2つの異なるインターフェースから継承された2つのonmouseoverイベントがある.HTMLDocumentEvents2_Event_onmouseoverという名前で,引数を供給するイベントの方を使用する.mshtmlのプライマリ相互運用アセンブリについては個別のドキュメントが存在しないので,COMバージョンのドキュメントを使用して,適切な変換を頭の中でする必要があるが,この変換は通常かなり単純である.mshtml要素についての完全ドキュメントは以下を参照されたい. http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/mshtml/reference/reference.asp.

イベントを処理する」ではAddEventHandler関数を使って,イベントが.NETで引き起されたときに呼び出されるWolfram言語関数を割り当てる方法について述べた.COMイベントについても,これらは相互運用アセンブリによって.NETイベントのように見えるようにされているので,同じ関数を使用する.いつも通り,.NETの名前をシンボルとして使用するときには_の文字をUに変える必要がある.

今度はonMouseOver関数を定義する.上のNETTypeInfoの呼出しでは,イベントの引数がmshtml.IHTMLEventObjというインターフェースの型のものであることを示した.これはIHTMLEventObj COMインターフェースのマネージドの同等のものであり,そのインターフェースのドキュメントから,ランダムなフォントの大きさを指定する <FONT>要素のそれぞれをすべてラップする次の簡単な関数を作成することができる.

実際にマウスをWebページ上で動かしてみる前に,もう1つ処理しなければならないことがある.「カーネルとフロントエンドを手動で.NETと共有する」では,ShareKernel関数を使って.NETから到着する呼出しをカーネルが受け入れられる状態にカーネルを置く方法について触れた.通常はDoNETModeless関数を使って自動的に共有の状態に入り,そこから出ることができるため,ShareKernel.NET/Link ではほとんど必要とされない.しかし,DoNETModelessはトップレベルのウィンドウをその引数として必要とする.ここではそのようなウィンドウはなく,Internet Explorerのインスタンスがあるだけである.したがって,ShareKernelを使って手動で共有の状態に入る必要がある.いつもと同じように,ShareKernelからの結果を保存して,後でUnshareKernelに渡せるようにする.

今度はInternet Explorerウィンドウを最前面に持ってきて,その上でマウスを(マウスボタンを押さずに)動かしてみる.最初のフォントの効果が現れるのに1,2秒かかるかも知れない.

使い終わった後で片付けるのを忘れないようにする.

これは取るに足らない例であるが,Wolfram言語を使ってブラウザウィンドウとインタラクトするもっと役に立って洗練された方法が数多くあることは想像できるであろう.

ActiveXコントロールを表示する

COMオブジェクトの多くは,ツールバー,グリッドボックス,あるいはその他のウィンドウ要素等の視覚的な表示を持っている.ActiveXコントロールという用語はCOMオブジェクトの同意語にすぎないが,可視のCOMオブジェクトは通常ActiveXコントロールとして参照される.ActiveXコントロールを.NETプログラムで表示したい場合は,そのコントロール用に特別の型の相互運用アセンブリを作成しなければならない.このアセンブリは,aximp.exeツール(ActiveXインポータ)で作成され,このツールは上で触れたtlbimpツールに似ているが,ActiveXコントロールが.NETウィンドウにホストされなくてはならないという点で異なる.コントロールが .NETウィンドウ内でホストされる場合は,System.Windows.Forms.Controlから継承する特別のラッパークラスが必要である.aximpツールがこのラッパークラスを作成する.

aximpについての完全ドキュメントが.NET Framework SDKにあるが,以下に簡単な例を挙げる..NETウィンドウでMicrosoft Calendar Control(おそらくすでにこのコントロールはマシンにインストールされている)を使いたいとしよう.このコントロールのタイプライブラリはマシンのファイル d:\OfficeXP\Office10\mscal.ocxにあるとする.(このような情報を得る1つの方法は,Visual StudioにバンドルされているOLE Viewツールを使ってコントロールを見付けるという方法である.)次のコマンドラインがaximpを実行する.(もちろんこれが書かれているように動作するためには,aximp.exeが自分のPATH上になければならない.)

上のaximpの起動は,現行のディレクトリであるMSACAL.dllおよびAxMSACAL.dll(「MSACAL」はコントロールのタイプライブラリの定義にあるライブラリのMSACAL 文から来ている)に2つのアセンブリを作成する.MSACAL.dllアセンブリは,ICalendarインターフェース,およびCalendarClassクラスに対するマネージドの型をその他のものと一緒に含む.これは,tlbimpツールを使って作成される相互運用アセンブリと同じものである.2つ目のアセンブリであるAxMSACAL.dllは,Calendarコントロールを.NETウィンドウでホストできる.NET コントロールにする特別のラッパークラスを含む.このラッパークラスは,「Ax」にCOMのcoclassの名前(Calendar)が続く慣例に従って名付けられ,AxCalendarと呼ばれる.ildasm.exeツール(中間言語ディスアセンブラ)は,作成されたアセンブリを調べるのに大変便利である.ildasm.exeプログラムは,.NET Framework SDKとバンドルされ,aximp.exeおよびtlbimp.exeと同じディレクトリに属する.

AxMSACAL.dllアセンブリをロードすると,MSACAL.dllアセンブリもロードされる.というのは,AxMSACAL.dllは後者に依存していて同じディレクトリに属するからである.

今度はAxCalendarラッパークラスのインスタンスを作成する.このクラスはCalendar COMオブジェクトのすべてのメソッドとプロパティを持ち,System.Windows.Forms.Controlクラスからも継承するので,その他の任意の.NETコントロールと同じように使用することができる.

以下でコントロールをホストして表示する.NETフォームを作成する.最後のDoNETModal関数はフォームを表示し,そのフォームが閉じられたときにカレンダーのValueプロパティを返す.このプロパティはユーザが選択した日付を保持する.

.NETランタイムは便利なことにValueプロパティの結果をDateTimeオブジェクトにマップする.これは簡単に操作して,選択された日付を文字列として得ることができる.

今度はカレンダーオブジェクトを解放して片付ける.AxCalendarオブジェクトは,COMオブジェクトではなく,COMオブジェクトへの参照を保持するだけの純粋な.NETクラスであるので,このAxCalendarオブジェクトに対してReleaseCOMObjectは呼び出さない.実際のCOMオブジェクトは,クラスMSACAL.CalendarClassのものであるが,そのクラスのインスタンスを直接作成することは決してなく,AxCalendarラッパーだけを作成する.

例題ファイル

.NET/Link に含まれている以下の例題プログラムは,COM要素をWolfram言語から呼び出すことを例示する.

ExcelPieChart.nb