プログラミング

SQLiteの.NET対応版「System.Data.SQLite」の配布場所に注意しよう

.NETでSQLiteを使いたくなるときがあると思います。
そのとき、グーグルさんで「SQLite NET」などと検索しますと、
System.Data.SQLite」という、アドレスが「http://sqlite.phxsoftware.com/」のページが出てきます。
「あ!ここがSQLiteの.NET版の配布場所なんだ~」
と思うのは大きな間違いです。
ここに置いてあるのは、バージョン「
1.0.66.0」までのやつです。
.NET3.5で使うのはいいのかもしれませんが、
.NET4で使うと「データベースエクスプローラー」でテーブルなどが見れません。
プログラム中で使うと設定をやらないとエラーが起きるっぽいです。
実は、グーグルさんで「SQLite」と検索して出てくる、
SQLite自体の公式ページ「SQLite Home Page」という、
アドレスが「http://www.sqlite.org/」のページから辿れるダウンロードのリンクに、
.NET版のSQLiteが置いてあります。
こちらは、バージョン「
1.0.77.0」などと最新版です。

・・ただ、このバージョンも「データベースエクスプローラー」でテーブルが見れません。
1.0.66.0」では、まかりなりにも「データベースエクスプローラー」への対応をしようとしていますが、
最新のバージョンではインストーラーに含まれていないようです。

次のバージョンでは、インストーラーに含むようなことが書いてあるっぽいので、待ってみます。
コードからSQLiteに接続してテーブルを取得したりすることはできます。

ということで、.NET4でSQLiteがエラーになる方に向けてこの情報をお送りしました。

C#で、GraphicsのDrawImageメソッドがOutOfMemoryExceptionを出す

今、C#でプログラムを書いています。
そのプログラムでは、読み込んだ画像をマウスで拡大したり縮小したり移動したり出来ます。
が、画像の表示に使用しているGraphicsクラスのDrawImageメソッドが
OutOfMemoryExceptionを吐き出しました。
物理メモリは余ってるし、使っているBitmapGraphicsもちゃんとDisposeしてます。
しばらく悩んで解決したのでメモしておきます。

まず、具体的な処理を説明すると・・・

画像の表示はPictureBoxを使って行っています。
PictureBoxImageプロパティに読み込んだBitmapなりを入れると表示してくれるものです。

最初に、PictureBoxImageプロパティに、
PictureBoxと同じ大きさのBitmapを入れておきます。
絵は入ってなくて白いままです。
読み込んだオリジナル画像は別のBitmapとして保持しておきます。

マウスのドラッグで位置や拡大率を変更できるようにします。
で、位置や拡大率を変更したときに次の処理を必ず行います。

GraphicsクラスのFromImageメソッドに、
PictureBoxImageプロパティを指定して、Graphicsのインスタンスを取得します。

で、このGraphicsDrawImageメソッドに、オリジナル画像のBitmapと、
マウスのドラッグで変更した位置や、拡大・縮小後の画像のサイズを指定します。
拡大縮小後のサイズは、拡大率×オリジナル画像のサイズで計算しています。

すると、PictureBoxImageプロパティに、
オリジナル画像を指定した位置、サイズで移動・縮小・拡大したものが書き込まれます。

PictureBoxは、Imageプロパティをそのまま表示するので、
オリジナル画像を指定した位置、サイズで移動・縮小・拡大したものが表示されるわけです。

最後に使用したGraphicsDisposeして終わります。
これで世界は平和になったのだ・・もとい、画像の拡大縮小ができるようになりました。

・・・で、あればよかったのですが。

どうも拡大・縮小に利用したGraphicsDrawImageメソッドが、
OutOfMemoryExceptionを吐き出してしまうときがあるのです。
最初に言ったとおり物理メモリは余ってますので、メモリ不足って意味ではないようです。

ではなんだろうと思って調べました。
どうやらGraphicsDrawImageメソッドは、
書き込むBitmapが適切な形式じゃなかったらOutOfMemoryExceptionを出すとかなんとか。

でも、読み込んだオリジナル画像は特殊な形式でもないし、
ちゃんとBitmapとして読み込めてるしなぁ。

じゃあ、もう1つの引数である位置や画像のサイズはどうだろう?
ってことでやったらBingo!でした。

実は、位置や画像サイズの指定は、RectangleFという構造体を使っていました。
RectangleFは、矩形の位置やサイズをfloat型で保持するものです。

スクリーン座標は1280x1024とかの整数値なので、int型で指定すればいいのですが、
縮小・拡大の計算の関係でサイズはfloat型の結果になります。
キャストが面倒なのでそのまま入れられるRectangleFに入れたのですが、これがいけなかった。

OutOfMemoryExceptionを吐き出すときにRectangleFの中身を見たところ、
サイズが0.000012345といったすごく細かくて小さいになっていました。

え?これが原因?普通に考えれば0とかになるよね?
と思ったけど、ずばりこれが原因でした。

矩形の位置やサイズをint型で保持するRectangle構造体で指定するようにしたところ、
OutOfMemoryExceptionが出なくなりました。

ちゃんちゃん。

その部分なソース↓
        /// <summary>
        /// オリジナル画像を拡大・縮小したものをpictureBoxにセットする
        /// </summary>
        private void SetPasteImage()
        {
            // pictureBox.Imageプロパティ←pictureBoxのサイズのBitmapが入っている
            // originalImage←オリジナル画像のBitmap
           
            // オリジナル画像が読み込まれていなかった場合なにもしない
            if (originalImage == null)
            {
                return;
            }

            // 位置とサイズの計算
            Rectangle rect = new Rectangle(imageX, imageY, (int)(scaleX * originalImage.Width), (int)(scaleY * originalImage.Height));

            // pictureBoxのImageから、Graphicsを取得
            Graphics g = Graphics.FromImage(picPasteImage.Image);

            // pictureBoxのImageをクリア
            g.Clear(Color.White);

            // pictureBoxのImageにオリジナル画像を拡大縮小して書き込み
            g.DrawImage(originalImage, rect);

            // Graphicsを破棄
            g.Dispose();

            // pictureBoxの表示を更新(Imageに画像が書き込まれているので表示も変わる)
            picPasteImage.Invalidate();
        }

application_ja.propertiesに日本語を入力するとき

日本語は\uFFFFという形式で書かなくてはいけない。

普通はnative2asciiというプログラムを使う。

が、面倒なのでeclipseでは、PropertiesEditor プラグインというものがあって、それを使ってファイルを開くと、編集してるときは日本語に変換され、書き込むときに\uFFFFという形式にしてくれる。

メモでした。

slim3でテストのデータが残ってしまった

最近slim3をやっています。

そこで、詰まったのでメモ。


JUnitで単体テストをやるのですが、なんかテストうまくいかないな、とテストをデバッグで動かして途中で止めたら、データが残ってしまいました。普通はテストが終わるとデータが消えてくれます。

テストの最初に「データが空であること」というのを入れていたので、すべてのテストが通らなくなってしまった・・。


解決策。

まず、ローカル環境の普通の実行のデータストアは、

「war/WEB-INF/appengine-generated/local_db.bin」

に入っています。

普通の実行の際のデータを消したい場合は、そのlocal_db.binを消して作り直せばデータ消えます。

自分の場合は、■ボタンでサーバーをとめてから、また実行させてます。

これはホットリローディング効かないのかな?


テスト実行の場合

「test/WEB-INF/appengine-generated/local_db.bin」

にはいっているとあったので、それを削除・再作成。

したのだが、やはりテストが通らない。


うーん、と悩んで、

「build/test-classes/WEB-INF/appengine-generated/local_db.bin」

というのがあったのでこれを削除した。

すると、データが消えて、テストが通った!やったー!

どうやら、このファイルにキャッシュ?かなんかされるみたいな。

このファイルは勝手に作成されるみたいで、削除だけでよかった。


メモ終わり。




コピーしてやってみた

前の記事(Singleton的にやってみた)で、Singleton的にクラスを作ったが、別にクラス1つにつき1つのインスタンスに制限しなくてもいいし、1つに制限すると逆に面倒くさいことになりそう。
重要なのは生成時に毎回ミノを構成するブロックを作成処理が動かないことだ。
ってことで今度はコピーする方法でやってみる。

□Minoクラス(抽象クラス)
・IDプロパティ(抽象)
・ミノを構成するブロック情報変数
・「ミノを構成するブロック情報」を生成するメソッド(抽象)
・位置情報
・方向番号
・落下とか回転とか移動の処理
・コンストラクタ
「ミノを構成するブロック情報」を生成するメソッドを呼んで、
それをミノを構成するブロック情報変数に代入する
・浅いコピー処理(シャローコピー)
Minoクラスの浅いコピー処理を実行する。

↓(継承)

□Mino0~Mino6(棒ミノや四角ミノの種類ごとに1つ)
・IDプロパティの実装
棒ミノを表すmino0クラスなら、return 0;を返す 等

・「ミノを構成するブロック情報」を生成するメソッドの実装
棒ミノを表すmino0クラスなら、
方向0・・・ (-1,0 0,0 1,0 2,0)
方向1・・・ (0,-1 0,0 0,1 0,2)
方向2・・・ (-2,0 -1,0 0,0 1,0)
方向3・・・ (0,-2 0,-1 0,0 0,1)
といったようなデータを返す


□TetrominoGeneratorRandomクラス
・Mino0~Mino6までのインスタンスを保持しておく変数

・初期化
Mino0~Mino6までのインスタンスを生成する

・GetNewMino()
ランダムにMino0~Mino6までのインスタンスの浅いコピー(シャローコピー)を返す。



Minoとそれを継承したMino0~Mino6は、前の記事(XNAテトリスで悩んでること)とほぼ同じだ。
ブロックの保持とミノの回転・移動・落下などの各種処理を行っている。
コンストラクタでブロックを生成するメソッドを呼び出して、実際の生成は継承したクラスのメソッドが行っている。

前と違うところは浅いコピー(シャローコピー)を行うメソッドを追加したところだ。
インスタンスのコピーとは、インスタンスを新しく生成して、フィールドの値を複製することだ。
その手法に、浅いコピー(シャローコピー)と深いコピー(ディープコピー)がある。

浅いコピーは、値型のフィールドはそのままコピーされるが、参照型のフィールドは参照だけがコピーされる。つまり中身のコピーを行わない。これは、コピー先でも同じ参照型のインスタンスを使うわけだ。

例えば、Aというクラスの中にBというクラスの参照型のフィールドがあったとする。
AのインスタンスA'の中にBのインスタンスB’があるとき、
インスタンスA'の浅いコピーを行うと、新しくインスタンスA''ができるが、その中のBのインスタンスはA'の中にあったB'の参照がそのままコピーされる。B''が新しく生成されてコピーされるわけではない。
A''の中のB'をいろいろいじると、A'のなかのB’も変わってしまうわけだ。同じものだから。

深いコピーは、値型のフィールドはそのままコピーされ、参照型のフィールドもインスタンスが新しく生成されて、中身がコピーされる。
先ほどの浅いコピーの例で言えば、A''の中に新しくB’のコピーB''が生成される。
A''の中のB''をいじっても、A’の中のB'は変更されない。


浅いコピーはTetrominoGeneratorRandomクラスで利用している。
このクラスは、GetNewMinoメソッドを呼び出すとランダムでMino0~Mino6までのインスタンスを返してくれる生成クラスだ。

このクラスでは、初期化時にあらかじめMino0~Mino6までのインスタンスを生成して保持している。
Mino0~Mino6は生成時にミノを構成するブロックの情報も作成する。
GetNewMinoメソッドでは、この保持されたMino0~Mino6のインスタンスの浅いコピーを返している。

なぜこのようなことをするかというと、ミノを構成するブロック情報の生成をやりたくないからだ。
浅いコピーなら参照型のフィールドは参照だけがコピーされる。
ミノを構成するブロック情報は参照型なので、この参照がコピーされるわけだ。

つまり最初にMino0~Mino6を生成したときにだけブロック情報が構築され、あとはコピーコピーでやっていけるので、コストが安いというわけ。
ついでに参照だけコピーなのでメモリ消費も少なくてすむ。

浅いコピー処理を作ったのにはもう1つ理由がある。
テトリスの落下地点を表示するガイドミノの表示のためだ。
ガイドミノの位置は、現在落ちているミノの落下地点にあるので、Minoクラスの落下処理がそのまま使える。
現在落ちているミノを落下させるわけにはいかないので、コピーしたものを落下させて、それを表示させている。


コードを簡単に張っておく。
 TetrominoGeneratorRandomクラス

       private Mino[] MinoInstances = 
        {
            new Mino0(),
            new Mino1(),
            new Mino2(),
            new Mino3(),
            new Mino4(),
            new Mino5(),
            new Mino6(),
        };
  
      public Mino GetNewMino()
        {   //ランダム
            int minoNumber = randomForTetromino.Next(MinoInstances.Length);
     // 浅いコピーしたものを返す
            Mino newMino = MinoInstances[minoNumber].ShallowCopy();

            return newMino;
        }

Minoクラス
        public abstract int ID { get; }
        public IList<IList<MinoBlock>> MinoBlocks { get; private set; }

        private int _minoAngleNumber = 0;
        public int MinoAngleNumber
        {
            get
            {
                return _minoAngleNumber;
            }
            private set
            {
                _minoAngleNumber = value;

                if (_minoAngleNumber < 0)
                {
                    _minoAngleNumber = MinoBlocks.Count - 1;
                }
                else
                {
                    _minoAngleNumber %= MinoBlocks.Count;
                }
            }
        }

        private Point _location;
        public Point Location
        {
            get
            {
                return _location;
            }
            set
            {
                _location = value;
            }
        }
        
        public TetrisField TetrisField { get; set; }

        private float fallSpeedSum = 0;
        public float FallSpeed { get; set; }

        public bool Finished { get; private set; }

        public Mino()
        {
            MinoBlocks = CreateMinoBlocks();
        }

       // ミノを構成するブロック情報の保持変数
        protected abstract IList<IList<MinoBlock>> CreateMinoBlocks();
        public IList<MinoBlock> CurrentMinoBlock
        {
            get
            {
                if (MinoBlocks.Count >= 1)
                {
                    return MinoBlocks[_minoAngleNumber];
                }
                else
                {
                    return null;
                }
            }
        }
   // 浅いコピー
        public Mino ShallowCopy()
        {
            return (Mino)MemberwiseClone();
        }

        // 以下回転とか移動とか

Mino0クラス

        public override int ID
        {
            get { return 0; }
        }
        protected override IList<IList<MinoBlock>> CreateMinoBlocks()
        {
            IList<IList<MinoBlock>> minoBlocks = new List<IList<MinoBlock>> 
            { 
                new List<MinoBlock> 
                { 
                    new MinoBlock(new Point(0, 0), 0),
                    new MinoBlock(new Point(-1, 0), 0),
                    new MinoBlock(new Point(1, 0), 0),
                    new MinoBlock(new Point(2, 0), 0)
                },
                new List<MinoBlock> 
                { 
                    new MinoBlock(new Point(0, 0), 0),
                    new MinoBlock(new Point(0, -1), 0),
                    new MinoBlock(new Point(0, 1), 0),
                    new MinoBlock(new Point(0, 2), 0)
                },
                new List<MinoBlock> 
                { 
                    new MinoBlock(new Point(0, 0), 0),
                    new MinoBlock(new Point(-1, 0), 0),
                    new MinoBlock(new Point(-2, 0), 0),
                    new MinoBlock(new Point(1, 0), 0)
                },
                new List<MinoBlock> 
                { 
                    new MinoBlock(new Point(0, 0), 0),
                    new MinoBlock(new Point(0, -1), 0),
                    new MinoBlock(new Point(0, -2), 0),
                    new MinoBlock(new Point(0, 1), 0) 
                },
            };

            return minoBlocks;
        }


以上。

記事検索
livedoor プロフィール
QRコード
QRコード

トップに戻る