.Netにおいて重要になった例外。Managed DirectXでも、例外を利用しています。今回は今までのコードについて例外という観点から色々と見ていきましょう。
とりあえず、Direct3Dに関する例外一覧です。
Microsoft.DirectX.Direct3D.DirectXException //DirectX全般の基底例外クラス。 Direct3D.Direct3DXException //D3DXの基底例外クラス。 CannotAttributeSortException //最適化テクニックとして属性のソートはサポートされていません CannotModifyIndexBufferException //インデックスバッファを変更できません DuplicateNamedFragmentException //名前付きフラグメントが重複しています。 InvalidDataException //データが無効です。 InvalidMeshException //メッシュが無効です。 LoadedMeshHasNoDataException //メッシュにデータが含まれていません。 SkinningNotSupportedException //スキニングががサポートされていない。 TooManyInfluencesException //指定された影響が多すぎる。 Direct3D.GraphicsException //グラフィックスの基底例外クラス。 ConflictingRenderStateException //現在設定されているレンダーステートは同時に使う事は出来ません ConflictingTextureFilterException //現在のテクスチャフィルタを同時に使う事は出来ません ConflictingTexturePaletteException //現在のテクスチャを同時に使う事は出来ません DeviceLostException //デバイスはは消失しているが、現在はリセットできません。 //したがってレンダリングは不可能です。 DeviceNotResetException //デバイスは消失しているのが、現在リセットできます。 DriverInternalErrorException //内部ドライバエラー。通常、このエラーを受け取った場合には、 //アプリケーションはシャットダウンする必要があります。 DriverInvalidCallException //現在のメソッド呼び出しが無効である事をドライバが報告します。 InvalidCallException //メソッド呼び出しが無効です。 //たとえばメソッドのパラメータが無効である場合があります。 InvalidDeviceException //要求されたデバイスの種類が無効です。 MoreDataException //指定されたバッファサイズで保持できる以上のデータが存在する。 NotAvailableException //このデバイスは、問い合わされたテクニックをサポートしない。 NotFoundException //要求された項目が見つからなかった。 OutOfVideoMemoryException //Direct3Dが処理を行うのに十分なディスプレイメモリがない。 TooManyOperationsException //デバイスがサポートしている数より多くテクスチャフィルタリング処理を、 //アプリケーションが要求している。 UnsupportedAlphaArgumentException //アルファチャンネルに指定されているブレンディング引数を、 //デバイスがサポートしていない UnsupportedAlphaOperationException //アルファチャンネルに対して指定されているテクスチャブレンディング処理を、 //デバイスがサポートしていない。 UnsupportedColorArgumentException //色値に対して指定されているテクスチャブレンディング引数を //デバイスがサポートしてない。 UnsupportedColorOperationException //色値に対して指定されているテクスチャブレンディング処理を、 //デバイスがサポートしてない。 UnsupportedFactorValueException //デバイスが指定されたテクスチャ係数値をサポートしていない。 UnsupportedTextureFilterException //デバイスが指定されたテクスチャフィルタをサポートしてない。 WasStillDrawingException //デバイス描画中であった。 WrongTextureFormatException //テクスチャサーフェイスのピクセルフォーマットが無効である。
いくつか代表的なものを取り上げてみましょう。
DeviceLostException //デバイスはは消失しているが、現在はリセットできません。 //したがってレンダリングは不可能です。 DeviceNotResetException //デバイスは消失しているのが、現在リセットできます。 DriverInternalErrorException //内部ドライバエラー。通常、このエラーを受け取った場合には、 //アプリケーションはシャットダウンする必要があります。 DriverInvalidCallException //現在のメソッド呼び出しが無効である事をドライバが報告します。 InvalidCallException //メソッド呼び出しが無効です。 //たとえばメソッドのパラメータが無効である場合があります。 InvalidDeviceException //要求されたデバイスの種類が無効です。 //要求された項目が見つからなかった。 OutOfVideoMemoryException //Direct3Dが処理を行うのに十分なディスプレイメモリがない。
特に「DriverInvalidCallException」は色々なメソッドから返される例外です。
まず第一に、継承元の例外でキャッチしない。という事です。Direct3Dの例外の中には「DriverInternalErrorException」のように。速やかに終了すべき例外もあります。そのため、例外は必要最小限だけをキャッチするようにしましょう。
二つ目に、例外の伝播はboolなどでなく例外ですること。ただし、その例外の原因を取り除いた場合はその限りではありません。これは、例外を無視してしまわないようにするための方法です。無視して構わない例外であれば、そのようにしても良いです。
次が、上にしたがって作られたコードの例です。
using System; using System.Drawing; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Project2 { /// <summary> /// md3d2 の概要の説明です。 /// </summary> public class md3d2:Form { public md3d2():base() { this.MinimumSize=new Size(80,60); this.ClientSize = new Size(300,300); this.Text ="Direct3D-My"; } /// <summary> /// Direct3Dのデバイス /// </summary> private Device device_; /// <summary> /// 表示のための設定 /// </summary> private PresentParameters presentParam_; /// <summary> /// 立方体メッシュ /// </summary> private Mesh teaMesh_; /// <summary> /// Direct3Dの初期化を行います。 /// </summary> public void DXInitialize() { presentParam_ = new PresentParameters(); presentParam_.Windowed =true; presentParam_.SwapEffect = SwapEffect.Discard; presentParam_.AutoDepthStencilFormat =DepthFormat.D16; presentParam_.EnableAutoDepthStencil=true; try { MessageBox.Show("A"); device_ = new Device(0,DeviceType.Hardware,this ,CreateFlags.HardwareVertexProcessing,presentParam_); } catch(InvalidCallException) { try { MessageBox.Show("A"); device_ = new Device(0,DeviceType.Hardware,this ,CreateFlags.SoftwareVertexProcessing,presentParam_); } catch(InvalidCallException) { try{ MessageBox.Show("A"); device_ = new Device(0,DeviceType.Reference,this ,CreateFlags.SoftwareVertexProcessing, presentParam_); } catch(InvalidCallException ex) { MessageBox.Show("C"); throw new FatalException( "Direct3Dのデバイスを作成できませんでした。",ex); } } } device_.RenderState.ZBufferEnable = true; device_.RenderState.ZBufferWriteEnable = true; try { creatMesh(); } catch(OutOfMemoryException ex) { throw new FatalException("メッシュの作成に失敗しました。",ex); } } /// <summary> /// メッシュを作成する /// </summary> private void creatMesh() { teaMesh_ = Mesh.Teapot(device_); } /// <summary> /// 描画設定 /// </summary> private void renderSetting() { device_.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, (float)this.ClientSize.Width / (float)this.ClientSize.Height, 0.0f, 15.0f); //カメラの設定を行う device_.Transform.View = Matrix.LookAtLH( new Vector3( 4.0f, 0.0f, 0.0f ), new Vector3( 0.0f, 0.0f, 0.0f ), new Vector3( 0.0f, 1.0f, 0.0f ) ); //回転 device_.Transform.World = Matrix.RotationY(Environment.TickCount/1500f); device_.RenderState.Lighting=false; //ワイヤーフレームモードに device_.RenderState.FillMode = FillMode.WireFrame; //背面カリングなし device_.RenderState.CullMode = Cull.None; } public void Render() { if(device_==null)return; if(this.WindowState ==FormWindowState.Minimized)return; renderSetting(); device_.Clear(ClearFlags.Target|ClearFlags.ZBuffer, Color.Blue,1.0f,0); device_.Clear(ClearFlags.Target, Color.Blue,1.0f,0); device_.BeginScene(); teaMesh_.DrawSubset(0); device_.EndScene(); try { device_.Present(); } catch(DeviceLostException) { resetDevice(); } } /// <summary> /// デバイスのリセットを行う /// </summary> private void resetDevice() { int result; if(!device_.CheckCooperativeLevel(out result)) { if(result ==(int)ResultCode.DeviceLost) { //ちょっと待つ System.Threading.Thread.Sleep(10); } else if(result ==(int)ResultCode.DeviceNotReset) { device_.Reset(presentParam_); } } } protected override void Dispose(bool disposing) { if(teaMesh_ !=null) { teaMesh_.Dispose(); } if(device_ != null) { device_.Dispose(); } base.Dispose (disposing); } } /// <summary> /// エントリクラス /// </summary> class Program { public static void Main() { try { using(md3d2 dxform =new md3d2()) { try { dxform.DXInitialize(); } catch(FatalException ex) { MessageBox.Show("Diret3Dの初期化に失敗しました。\r\n" + ex.ToString(),"初期化の失敗",MessageBoxButtons.OK, MessageBoxIcon.Error); return; } dxform.Show(); while(dxform.Created) { dxform.Render(); Application.DoEvents(); } } } catch(DriverInternalErrorException ex) { MessageBox.Show("ドライバ内部でエラーが発生しました。\r\n" + ex.ToString(),"不明な失敗",MessageBoxButtons.OK, MessageBoxIcon.Error); } } } /// <summary> /// 致命的なエラーを表す。打つ手がないため、速やかに終了する事。 /// </summary> class FatalException:Exception { public FatalException():base(){} public FatalException(string message):base(message){} public FatalException(string message,Exception innerException) :base(message,innerException){} } }
それでは説明していきましょう。
今回、独自の例外として「FatalException」というものを定義しました。これは、それ以上実行する意味がない場合や、実行にたどり着けなかった場合にスローされる例外です。
今回のコードは、ティーポッドをワイヤーフレームで表示するプログラムです。そのため、それをなしえない場合には「FatalException」となります。
「new device()」はいくつかの例外を発生させます。
サンプルコードでは、「InvalidCallException」の場合のみ「Hardware-HardwareVertexProcessing」「Hardware-SoftwareVertexProcessing」「Reference-SoftwareVertexProcessing」の順番で作成を試みています。これは、前提条件として「十分なメモリを持ち、不測の事態が起きない環境を想定」しているためです。この前提の中でデバイスの作成に失敗した時(つまり「InvalidCallException」がスローされた時)に限り例外の回復を試みています。
また、これらすべてに失敗した時には「前提条件の中で致命的な状態にったった」と判断され「FatalException」がスローされます。
ここで重要なのは、どのような場合には例外の復旧が可能かという事です。今回に限っては他の三つの例外についても「Reference-SoftwareVertexProcessing」をためす価値はあります。ですが、そこまでの面倒は見きれないという事で、それらは切り捨てています。
特にDeviceLostExceptionは安定した環境でも起こりえると思います。初期化途中でビデオメモリとの接続が切れる事は結構あると思います。
メッシュの作成では次の例外が発生します。
サンプルでは「OutOfVideoMemoryException」以外の例外を考えていません。Meshの作成時にメモリが足りなくなる事は考えられる事態です。また、この場合にはもっとメモリ使用量の少ない方法に切り替える事も考えられます(サンプルではすぐに終了します)。
ですが、その他の例外が出る場合はバグであると考えられます。そのため、この例外はキャッチしません。根本的なバグの発見に役立つ情報を止めないようにするためにも大きな範囲の例外をキャッチするのは控えるようにしましょう。
また、その二つの例外がバグ以外の原因ででた場合にもキャッチする利益はありません。対処できないからです。
エントリポイント内では一番外側で、「DriverInternalErrorException」をキャッチしています。これ以外には自作した「FatalException」しかキャッチしていません。
まず、DriverInternalErrorExceptionをキャッチする理由です。この例外はキャッチした時点で狩猟のコードを書く事になります。例外の復旧を試みる意味はありません。そのため、明示的に終了のコードを書いています。
次に「FatalException」これも、終了する事を求められる例外です。そのため、明示的にキャッチしています。
そのほかの例外については「キャッチする意味がない(復旧出来ない、キャッチしても再スローを行うだけ)」「発生してはいけない例外である」「前提条件に反する」の三つに当てはまるためキャッチしていません。
ただし、このコードはテスト用プログラムの例外構造としてあるものです。公開する場合には一番外側にもう一段例外キャッチを設けてログをとるなりの対応はすべきだと思います。
ただ、その対応は例外に対する原則に反するものではありません。そこでキャッチするのには意味があるからです。例外はエラーではないですが、それが野放しのままユーザーにさらされるのはバグであると思います。のバグを減らすために例外をキャッチするというわけです。
この例外処理の仕方は、自分がプログラムを公開する時にはそうするだろうというものをまとめただけに過ぎません。その人独自の考え方もあるでしょうし、これがベストだとも限らないからです(でも、そうだと良いなと思って書いてはいます)。
例外の良い点は、無視できない点とメソッドをまたぐ点だと思います。是非有効利用して下さい。