NUnit で DBUnit もどきを使う

データベースアクセスがある場合は、DBUnit を使うのがベストなんでしょうが、ひとまず、初期データの投入だけできればいいや、って気持ちで作ったのがこれです。

public class DBTest
{
    public static void DataSetup( IDataTable tbl, SqlConnection cn )
    {
        Type RowType = tbl.GetRowType();
        string TableName = tbl.GetTableName();


        PropertyInfo[] pis = typeof(XProductRow).GetProperties();

        string sql_columns = "(";
        string sql_values = "(";
        foreach (PropertyInfo pi in pis)
        {
            sql_columns += pi.Name + ",";
            sql_values += "@" + pi.Name + ",";
        }
        // 最後のカンマを削除
        sql_columns = sql_columns.Substring(0, sql_columns.Length - 1);
        sql_values = sql_values.Substring(0, sql_values.Length - 1);
        string sql = "insert into " + TableName+ " "
            + sql_columns + ") values "
            + sql_values + ")";


        SqlCommand cmd = new SqlCommand(sql, cn);
        foreach (PropertyInfo pi in pis)
        {
            SqlParameter param = new SqlParameter();
            param.ParameterName = pi.Name;
            cmd.Parameters.Add(param);
        }

        cmd.Connection.Open();
        foreach ( object item in tbl.GetList())
        {
            foreach( PropertyInfo pi in pis ) {
                cmd.Parameters[pi.Name].Value = pi.GetValue(item,null);
            }
            cmd.ExecuteNonQuery();
        }
        cmd.Connection.Close();
    }
}

例のごとく、リフレクションを使ってプロパティ名=テーブルのカラム名と想定して、INSERT 文を作成します。Visual Studio で作成される型付 DataSet を使っても良いのですが、データ投入をするたびに、別の DataAdapter を呼び出さないといけないし、なんかいまいちなので、ってのと、テストデータの投入なので INSERT 文と、テーブルの中身を DELETE する部分だけが欲しい訳で、型付 DataSet だと冗長なんですよね。

/// <summary>
/// リフレクションが簡単になるためのインターフェース
/// </summary>
public interface IDataTable
{
    string GetTableName();
    Type GetRowType();
    System.Collections.IList GetList();
}

/// <summary>
/// 商品クラス(カラム)
/// </summary>
public class XProductRow
{
    public string id { get; set; }
    public string name { get; set; }
    public int price { get; set; }
    public int cateid { get; set; }
}
/// <summary>
/// 商品クラス(テーブル)
/// </summary>
public class XProduct : IDataTable
{
    public XProduct() 
    {
        Rows = new List<XProductRow>();
    }
    public List<XProductRow> Rows { get; set; }
    public Type GetRowType() { return typeof(XProductRow); }
    public System.Collections.IList GetList() { return this.Rows; }
    public string GetTableName() { return "XProduct"; }
    public XProductRow NewRow() { return new XProductRow(); }
}

ざっくりと、POJO タイプの DataRow と、単なるコレクションを集めただけの DataTable を作っておきます。

そうして、データ投入をする場合は、

private void button1_Click(object sender, EventArgs e)
{
    // 自動でinsert文を作る
    SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=mvcdb;Integrated Security=True;MultipleActiveResultSets=True");

    XProduct table = new XProduct();
    XProductRow it = table.NewRow();
    it.id = "A8000";
    it.name = "新商品XXX";
    it.price = 2000;
    it.cateid = 2;
    table.Rows.Add(it);

    it = table.NewRow();
    it.id = "A8001";
    it.name = "新商品YYY";
    it.price = 2000;
    it.cateid = 2;
    table.Rows.Add(it);

    DBTest.DataSetup(table, cn);
}

こんな風に、DataRow/DataTable にデータを詰めておいて、一気にセットアップできます。

C# v3.0 の場合は、こんな風に初期化しながらということもできます。

table.Rows.Add(
    new XProductRow
    {
        id = "A8002",
        name = "新商品ZZZ",
        price = 20000,
        cateid = 1
    });

まあ、これで良いかなぁと。

カテゴリー: C#, xUnit | NUnit で DBUnit もどきを使う はコメントを受け付けていません

コマンドラインで NUnit を使う

VB2010 Express + NUnit 2.5 で、 初めてのTDD Step by Step
http://www.tdd-net.jp/vb2010ee-nunit25-tdd-stepbystep.html
NUnit V2 Test Framework
https://launchpad.net/nunitv2

暫く、NUnit は使っていなかったのですが(主に Visual Studio 付属の Test Framework を使っていたので)、ちょっとメモ書き程度に。

GUI で使う場合には、上記のリンクから辿るとして、私の場合はコマンドラインの動作を少し。コマンドラインでビルドをして、コマンドラインでテストをする、というシビアなw環境で動かしているので、まずは、テスト環境のディレクトリ構成を決めます。

+ 諸々のソースコードがあるところ
+ test/ テストコード
+ test/make.cmd

な感じで test ディレクトリを作ります。

先日、あえなく MSBuild で挫折したので、挫折したままバッチファイルを作ります。

@echo off
if "%1" == "test" goto TEST

copy ..\*.dll .
set NUNIT=nunit.framework.dll
csc /t:library /out:test.datasets.dll /r:%NUNIT% /r:sample.datasets.dll datatable.test.cs

goto END

:TEST
nunit-console test.datasets.dll

goto END

:END

ビルドをするときは、*.dll を親ディレクトリからコピーして、コンパイル。
コンパイルするときに必要なのは、nunit.framework.dll だけです。これは、テスト実行時にも必要なので、test フォルダにコピーしておきます。

テストをする場合は【make test】で動作させます。いちいち nunit-console.exe と打つのも面倒だし、テストの自動化としては make した後に test も実行というスタイルがいいですよね。昔、cppunit で makefile を作っていたときもこうやっていました。

このコンソールバージョン、何が便利かというと、

  • 再帰テストしたいときに、いちいち Visual Studio を立ち上げないですむ
  • 視覚的には GUI がいいんでしょうが、色々なテストをいっぺんに行うときは、コマンドラインが楽

という利点があります。まぁ、DOS プロンプトとかターミナルに慣れている私だからなのかもしれませんが。受け入れテスト的な形で NUnit を使う場合は、コマンドライン版のほうが便利ですよ。

ちなみ、実行結果はこんな感じです。

C:\masuda\demo\test>make test
NUnit version 2.5.9.0
Copyright (C) 2002-2010 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.

Runtime Environment -
   OS Version: Microsoft Windows NT 5.1.2600 Service Pack 3
  CLR Version: 2.0.50727.3615 ( Net 2.0 )

ProcessModel: Default    DomainUsage: Single
Execution Runtime: Default
..
Tests run: 2, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0.09375 seconds
  Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0

copyright を消したい場合は /nologo スイッチをつけると OK です。

カテゴリー: 開発, C#, xUnit | コマンドラインで NUnit を使う はコメントを受け付けていません

型なしDataTableから型付きDataTableにコピーする方法

最近だと、LINQ to SQL や LINQ to Entities があるので、DataSet/DataTable はあまり使わないのですが、ADO.NET と云えば、DataAdapter と DataSet の組み合わせでした。
その頃は、データベースのテーブルを型付で取ってこれる、型付きDataSetの存在が結構大きかったのです。

どういうことかというと、DataTable を直接扱ってしまった場合、

foreach ( DataRow row in dt.Rows ) {
	int id = (int)row["id"];
	...
}

のように、DataRow から値を取ってくる場合は、列名を指定しないと駄目かつキャストをしなければならず、という2重苦が待っています。これが、文字列なので、ええ、ちょっと間違うとえらいことになってしまうのです。PHP だと、こんな風に書くのが普通なので、特に気にしないのですが、ASP.NET で規模の大きいデータベースだと、ちょっとデバッグが大変なことに、っていう具合です。これ、実行時のエラーでしか取れないので、難しいんですよね。

なので、C# の厳密な型、というのを利用して、

foreach ( MyDataRow row in dt.Rows ) {
	int id = row.id ;
	...
}

のように、プロパティで値を安全に取得できるようにするのが、型付の意味なのです。

で、この型付DataSetですが、Visual Studio 上で作成すると膨大な自動生成のソースコードがあって整理が大変ッ!!! ということもあり、さらに、自前で型付DataTableを使いたいときは、あのデザイナでちまちま列を作らなきゃならない、という問題がありまして。なんとなく避けてしまって、とりあえず、DataSet や DataTable のまま扱っているのが普通なのではないかなぁと。

そこで、本題ですが、これをコンバーターのクラスを用意して、型なしから型付にコピーできるようにしてしまおう、というものです。で、MyDataTable, MyDataRow の作成はなるべく手間をかけたくないという訳で。

それを実現してみたのが次のコードです。

/// <summary>
/// リフレクションを簡単にするための準備
/// </summary>
public interface IDataTable  
{
    Type GetRowType();
    object CreateRow();
}
/// <summary>
/// 型付DataTable用のテンプレート
/// </summary>
/// <typeparam name="DRType"></typeparam>
public class DTTemplate<DRType> : IDataTable, IListSource
{
    /// <summary>
    /// DataRow の型を返す
    /// </summary>
    /// <returns></returns>
    public Type GetRowType() {
        return typeof(DRType);
    }
    /// <summary>
    /// DataRow のリスト
    /// </summary>
    protected List<DRType> _rows = new List<DRType>();
    public List<DRType> Rows {
        get { return _rows; }
    }

    /// <summary>
    /// 新しい DataRow を作成
    /// </summary>
    /// <returns></returns>
    public DRType NewRow() {
        return (DRType)Activator.CreateInstance(typeof(DRType));
    }
    /// <summary>
    /// 新しい DataRow を作成(object型)
    /// </summary>
    /// <returns></returns>
    public object CreateRow() {
        return NewRow();
    }
    
    // インターフェースの実装
    /// <summary>
    /// 内部リストから IList を使う
    /// </summary>
    public bool  ContainsListCollection
    {
        get { return true; }
    }
    /// <summary>
    /// IListのコレクションを返す
    /// </summary>
    /// <returns></returns>
    public System.Collections.IList GetList()
    {
        return this.Rows;
    }
}

/// <summary>
/// いわゆる型付DataRow
/// </summary>
public class ProductRow  
{
    protected string _id;
    protected string _name;
    protected int _price;

    // プロパティアクセスは、C# 3.0 ならば、
    // public string id { get; set; }
    // で書ける.
    // 今回は v2.0 なので、この形式で。
    public string id
    {
        get { return _id; }
        set { _id = value; }
    }
    public string name
    {
        get { return _name; }
        set { _name = value; }
    }
    public int price
    {
        get { return _price; }
        set { _price = value; }
    }
}
/// <summary>
/// 型付DataSet
/// </summary>
public class Prodcut : DTTemplate<ProductRow>
{
}
/// <summary>
/// 形無しDataSetを型付DataSetにコンバートするクラス
/// </summary>
public class DataBind
{
    public static void Conv(DataTable src, IDataTable dest)
    {
        Type rowType = dest.GetRowType();
        System.Collections.IList rows = ((IListSource)dest).GetList();
        foreach (DataRow row in src.Rows)
        {
            object item = dest.CreateRow();
            // リフレクションを使って、列ごとにコピーする
            foreach (DataColumn column in src.Columns)
            {
                // この部分はキャッシュすると高速化する
                string key = column.ColumnName;
                PropertyInfo pi = rowType.GetProperty(key);
                if (pi != null)
                {
                    // object型 -> 元の型のキャストでエラーになるため、
                    // 一度元の型に ChangeType してから代入する。
                    pi.SetValue(item, 
                        Convert.ChangeType(row[key], pi.PropertyType), 
                        null);
                }
            }
            rows.Add(item);
        }
    }
}

いやぁ、リフレクションを使っているんですが、結構実現するのは長いコードになってしまいました。型付の ProductRow は、v2.0 なのでget/set を別に書く必要がありますが、v3.0 以降ならば、次のように短く書けます。

public class ProductRow  
{
    public string id { get; set; }
    public string name { get; set; }
    public int price { get; set; }
}

そして、型付DataTable に関しては、こんな風に更に短く。

public class Prodcut : DTTemplate<ProductRow> {}

実質、1行で書くことができます。
コンバート関数は、DataBind.Conv() だけで使えるので、非常に簡単。

データベースに接続して、DataGrid に表示するコードがこのように短く書けます。

private void button2_Click(object sender, EventArgs e)
{
    // データベース接続
    SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=mvcdb;Integrated Security=True;MultipleActiveResultSets=True");
    cn.Open();
    DataTable dt = new DataTable();
    SqlDataAdapter da = new SqlDataAdapter(
        "SELECT * FROM TProduct", cn);
    da.Fill(dt);
    cn.Close();

    Prodcut product =new Prodcut();
    DataBind.Conv(dt, product);

    dataGridView1.DataSource = product;

}

まあ、単純に DataGridView の DataSource プロパティを使ってバインドする場合は、DataTable のままでいいんですけどね。列名なんかで条件分岐をするときなんか、dt.id のようなプロパティで指定できると間違いが少なくなりますよねぇ。という話でした。

ええと、業務で使うコードは、もうちょっと高速化していきます。といいますか、DataBind.Conv のように static 関数にせず、実は ProductRow の行テーブルへのマッピング方式にしています。ま、これは別の機会に紹介します。値を加工して ProductRow に詰め込むようなことをするために、ProductRow の内部メソッドとして用意しています。

カテゴリー: C# | 型なしDataTableから型付きDataTableにコピーする方法 はコメントを受け付けていません

アリスはプラグインで強化する(Assembly.LoadFrom を使う)

オブジェクト指向の肝で、それは【継承】を使うのが良いのか、それとも【委譲】にしたほうがいいのか、という話があります。結論を言えば、ケースバイケースなのですが、どうしても委譲でしか解決できないものもあります。

プラグインのパターンがそうで、とあるクラスの機能を強化しようとする場合、とあるクラスに手を加えずに強化する方法が【委譲】のパターン、インターフェースを作っておいて、それを動かすというパターンになります。

さて、C++ の場合は、プラグイン作りは、インターフェースとなる関数を定義しておいて、外部のDLLで定義しておけば良いので、結構簡単にできます。結構簡単に、と言うのは DLL 作りに慣れていればの話であって、非常に敷居の高いものでもあります。DLL の import/export の対象となる関数の型の変換やスタックの使い方の問題があって、ややこしいのですね。

これを統一化させるために、COM がある訳ですが、いちいち登録が必要なのと、COM を扱うのが相当手間(少なくとも生のC++でやるのは手間です)なので、避けたいプラグインです。VB6 あたりだと CreateObject 関数で作成すればよいのですが、このあたりは variant 型を扱える言語だからですね~、とかなんとか。

と、C# ではどうやるの?と思って調べて作ってみたのが以下です。
ネタ元は、先日買った「プログラミング .NET Framework 第3版」なのです。

まずはメイン関数です。

using System;
using System.Reflection;
using Sample;

public class Program 
{
	public static void Main(string[] args )
	{
		Console.WriteLine("plugin test");
		
		Type per = null;
		// 動的に DLL をロードする
		Assembly asse = Assembly.LoadFrom("Person.dll");
		foreach ( Type t in asse.GetExportedTypes() ) {

			Console.WriteLine("class: {0}", t.ToString());
			// 内部で公開されているクラスで IPerson なものを探す
			if ( t.IsClass && typeof(IPerson).IsAssignableFrom(t) ) {
				per = t;
				break;
			}
		}
		if ( per == null ) {
			Console.WriteLine("Error: no interface");
			return;
		}
		// IPerson のコンストラクタを使ってオブジェクトを作成
		IPerson p = (IPerson)Activator.CreateInstance(per);
		p.Say("hello");
	}
}

何をやっているかわかり辛いですが、Person.dll というアセンブリを動的にロードしています。そして、この中にある IPerson というインターフェースを探して、見つかったら、IPerson::Say メソッドを実行しています。

このあたり、インターフェースを使わない場合はリフレクションを使うのですが、インターフェースを使ったほうが楽ですし、コンパイル時にチェックができるので、お奨めです。

この IPerson インターフェースは、次のコードです。

namespace Sample
{
	public interface IPerson
	{
		void Say( string val );
	}
}

実に単純ですね。単純に委譲をするためだけのインターフェースです。

実際に動作するところの Person クラスは以下のコードです。

using System;

namespace Sample
{
	public class Person : IPerson
	{
		public void Say( string val ) 
		{
			Console.WriteLine("in Person: {0}", val );
		}
	}
}

IPerson インターフェースを継承して Person クラスを作ります。メイン関数で IPerson::Say メソッドを呼び出したときには、この Say メソッドが呼び出されます。

さて、これをコンパイルするのはどうするかというと、こんな風です。

csc /t:library IPerson.cs
csc /t:library /r:iperson.dll Person.cs
csc /t:exe /r:iperson.dll main.cs

まず、iperson.dll だけを作ります。このアセンブリを、Person.cs と main.cs が読み込む訳です。当然なことですが、person.cs と main.cs の直接的な関係はありません。関係がないので、main.exe と person.dll は別々に開発することができるのです。

実行したのがこれです。

C:\masuda\alice>plugin
plugin test
class: Sample.Person
in Person: hello

さて、person.cs とは別の personOther.cs を作ります。

using System;

namespace Sample
{
	public class PersonOther : IPerson
	{
		public void Say( string val ) 
		{
			Console.WriteLine("in PersonOther: {0}", val );
		}
	}
}

のように PersonOther というクラスを作ります。そして、これをコンパイルする時に、person.dll が出力されるようにします。

csc /t:library /r:iperson.dll /out:Person.dll PersonOther.cs

いわゆる、DLL 名はそのままにして、中身をすげかえてしまうわけです。
そして、main.exe はそのままにして、実行すると、

C:\masuda\alice>plugin
plugin test
class: Sample.PersonOther
in PersonOther: hello

な風にメッセージ(動作)が変わります。

まあ、本来は main 関数で使っている Person.dll の名前を変えるのが普通なんですけどね。さくっとアセンブリを変えるだけで、クラス名までも変えられてしまう(インターフェースが合っているのだから、これは妥当なんだけど)のが不思議なところです。

さて、書籍に載っていたのですが、この Assembly.LoadFrom メソッドの引数、ローカルファイルだけでなくて WEB サイトに置いてあるファイルも参照できます。つまり

Assembly asse = Assembly.LoadFrom("http://servername/assemblries/Person.dll");

のように、http プロトコルが使えるそうです。へぇッ!!! 試していないのですが、これって結構アレな機能ですよね。インターフェースをうまく使うと、一度インストールしてしまえば、アップデート無しで機能を追加してしまうことが可能なのです。勿論、オフラインの時にも動作するようにするためには、ちょっと工夫が必要ですが。

カテゴリー: 開発, C# | アリスはプラグインで強化する(Assembly.LoadFrom を使う) はコメントを受け付けていません

コマンドラインで C# をコンパイルしよう(2)

続きです。csc.exe を使って C# のソースコードがコンパイルできるようになると、C++ の make のようにたくさんのファイルを一度にコンパイルしたいですよね。

アセンブリを分けようと思って、/target と /reference /out を駆使して作ったバッチファイルは以下のものです。

csc /nologo /target:library /out:sample.datasets.dll datatable.cs
csc /nologo /target:library /r:sample.datasets.dll /out:sample.dao.dll dao.cs
csc /nologo /target:library /out:sample.models.dll /reference:sample.datasets.dll model.cs
csc /nologo /target:library /out:sample.bizlogic.dll ^
	/r:sample.models.dll /r:sample.datasets.dll /r:sample.dao.dll bizlogic.cs
csc /nologo /target:library /out:sample.dll ^
	/r:sample.models.dll ^
	/r:sample.bizlogic.dll ^
	/r:sample.datasets.dll ^
	SampleForm.cs
csc /nologo /target:exe ^
	/r:sample.dao.dll ^
	/r:sample.models.dll ^
	/r:sample.bizlogic.dll ^
	/r:sample.datasets.dll ^
	/r:sample.dll ^
	main.cs
csc /nologo /target:exe ^
	/r:sample.dao.dll ^
	/r:sample.models.dll ^
	/r:sample.bizlogic.dll ^
	/r:sample.datasets.dll ^
	test.cs
csc /nologo /target:exe ^
	/r:sample.datasets.dll ^

make.cmd って言う名前のファイルにしています。
実はコマンドラインは複数行の指定ができたんですね。知らんかった。
「^」をタイピングすると、次の行に移れます。コマンドプロンプトでもできるのですが、More の前に戻れないから編集できないし…まぁ、こんな風にバッチファイルを作るときに便利です。

さて、このファイルをアセンブリ(*.dll)と C# のソースコード(*.cs)の依存関係をつけたいなぁ、と思って、さて MSBuild でやるか、とマニュアルを見てみたのですが…

MSBuild リファレンス
http://msdn.microsoft.com/ja-jp/library/0k6kkbsd%28v=VS.80%29.aspx
.NETビルド・エンジン「MSBuild」使いこなし術
http://www.atmarkit.co.jp/fdotnet/special/msbuild01/msbuild01_01.html

結論を言えば、素の状態で MSBuild の XML ファイルを書いていくのは【無理】です。到底手軽にできるようなものではありません。Visual Studio で *.csproj とか *.sln のファイルを直接ビルドするときに使うと良いですね。

# 当時、Ant が流行っていて、NAnt ができて、make よりも高機能なことができる、ってのが売りなんですけど、make のようにコロンとタブで依存関係を作りたい場合は make のほうが適していますね。

という訳で、依存関係を使って手軽にビルドしたいときは make/nmake を使いましょう、って話でした。

カテゴリー: 開発 | 1件のコメント

コマンドラインで C# をコンパイルしよう

ドキュメント制作用のマシンだったり、サーバーマシンだったり、他人のマシンだったりすると、Visual Studio が入っていないどころではなくて、Perl やら Ruby やらのスクリプト言語も入っていないので、バッチ処理をするときに大変なのです。

で、意外と【素】な環境で使えるプログラム言語を挙げてみると、wscript が一番なのですが、プログラミングっていう点では弱いんですよね。特に入出力関係を CreateObject で作らないといけないので、ちと面倒くさい。

なので、C# や Visual Basic を使います。と言いますか、この手のコンパイル系の言語が OS と共に入っているのは、なんだかなぁと思うのですが、それは別として使えます。
# セキュリティ的に、業務マシンには余分なものを入れないとう制限があるので。

C:\WINDOWS\Microsoft.NET\Framework なフォルダを見ると .NET Framework が入っています。

2011/02/15  10:08    <DIR>          v1.0.3705
2011/02/10  16:56    <DIR>          v1.1.4322
2011/02/15  12:54    <DIR>          v2.0.50727

この中に

 csc.exe C# のコンパイラ
 vbc.exe Visual Basic のコンパイラ
 jsc.exe Javascript.NET のコンパイラ
 
があります。

Java の場合はランタイムしか入らないので javac.exe を入れるためには JDK をインストールする必要があるのですが、.NET Framework は、【Windows Update】をしちゃうと入ってしまいます。ついでに言えば、MSBuild.exe も入っています。

なので、

SET PATH=%PATH%;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727

な風にパスを通しておけば、Hello.cs なんかがコンパイルできます。

using System;

pubic class Hello 
{
  public static void Main(string[] args) {
    Console.WriteLine("Hello 秘密な C# World.");
  }
}

このコマンドライン版で何を作るかというと、クラス設計のモックアップを作っています。クラス図を作っても、それが実際に動くかどうかわからないし、それを動かしたときにはどのようにクラスが関係するのかがわかりづらいので、スケルトンなモデルを作ります。これをコンパイルして、Console.WriteLine でトレース結果を出せば、どのメソッドをどのように呼び出しているのかを試すことができます。また、その結果をクラス図のほうに反映させることもできます。

まぁ、実は【シーケンス図】を書けばいいだけなんですけどね。リフレクションを使ったデータコンバーターを作る予定もあって、C# でモデルを作っているところなのです。

カテゴリー: 開発 | コマンドラインで C# をコンパイルしよう はコメントを受け付けていません

美女Linux Windows サイドバーガジェットを製作中

ちょっと製作途中ですが、Windows Vista/7 用のサイドバーガジェットです。


何のことはない、美女Linux の javascript をそのまま読み込ませるだけで OK なのです。

お試しのダウンロードはこちらに。

美女Linux v0.9
http://bijo-linux.com/bp/bijo-linux-0.9.gadget
美女Linux offshot版 v0.9
http://bijo-linux.com/bp/bijo-linux-off-0.9.gadget
美女Linux バレンタイン版 v0.9
http://bijo-linux.com/bp/bijo-linux-feb-0.9.gadget

こんな感じに、script タグで読み込ませればそのままガジェットとして表示できます。このあたりは、Flash でもできると思うんですがまだ試していません。あと、Silverlight もそのままいけそう。

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
	    <title>美女linux v0.9</title>
	    <style type="text/css">
		    body
		    {
		        margin: 0;
		        width: 160px;
		        height: 262px;
		    }
	    </style>
	<head>
<body>
	<script src="http://bijo-linux.com/bp/js/bijo-0.9.js"></script >
</body>
</html>

サイドバーガジェットの作り方は、本家とか

Windows Sidebar
http://msdn.microsoft.com/ja-jp/library/aa965850(v=VS.85).aspx
ガジェットに画像を貼りつける | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1493

を参照するとよいかも。色々テクニック的にはあるでしょうが、javascript で作っておくとこのあたりが楽ですね。

# Linux 版はさてどうするか、と思案中なわけですが、X Window で作るのもいいんですが、ひとまず Adobe AIR を使うのも良いかなぁと思ってみたり。

カテゴリー: サイドバーガジェット | 3件のコメント

ルイスはアリスにプラダを電送する

電話機能(Telephone)とシリアライズ機能(ISerialize)を同時に利用すると、電送ができます(笑)。「物理転送」っていう言葉を使っているのは、最近 SF を読んでいないなぁと思いつつ本屋さんに行ったらミステリーしか並んでいないじゃんッ!!! 早川SF文庫は何処へと探していたら、アーサー・C・クラークのベスト集が売っていたので、仕方がなく「えッ!!! 1000円なんですか??? 文庫なのに、こんなに高いんじゃ講談社学術文庫や筑摩文庫と同じじゃないですかッ!!!」ってな感じで仕方なく買ってきて、100頁ほど一気読み、したからです。なるほど、SF ちっくな本を読もうとすると、ラノベのほうがそれらしい(インデックスとかレールガンとか白井黒子とか…)なんだなぁ、と思ったり思わなかったり。

そんな訳で、電送しているのが次のコードです。

// for ルイスはアリスにプラダを電送する
#include <string.h>
#include <iostream>
using namespace std;

class Person ;

class Telephone {
protected:
	Telephone *_telephone;
	Person *_person ;
	void (Person::*_onPhone)(const void *, int);

public:
	// 自分の onPhone を登録
	void setMyPhone(Person *person, void (Person::*onPhone)(const void *, int)) {
		_person = person ;
		_onPhone = onPhone;
	} 
	// 相手先を登録
	void setPhone( Telephone *telephone ) {
		this->_telephone = telephone ;
	}
	// データを送信
	virtual void sendData( const void *data, int size ) {
		if ( _telephone != NULL ) {
			_telephone->onPhone(data,size);
		}
	}
	// データを受信する関数
	virtual void onPhone( const void *data, int size ) {
		if ( _onPhone != NULL ) {
			(_person->*_onPhone)( data, size );
		}
	} 
};
class ISerialize {
	// シリアライズ化
	void *Serialize();
	// データから復元
	static void Serialize( void * );
};

class Bag : public ISerialize 
{
public:
	// 利用フラグ
	bool used ;
	// 持ち主の名前
	char ownerName[30];
	// ブランド名
	char brandName[30];
public:
	// コンストラクタ
	Bag() : used(false) {
		ownerName[0] = '\0';
		brandName[0] = '\0';
	}
	// アクセッサ
	bool getUsed() { return used; }
	string getOwnerName() { return ownerName; }
	void setOwnerName( string name ) {
		if ( ownerName[0] == '\0' ) {
			strcpy( ownerName, (const char*)name.c_str() );
		}
		
	}
	string getBrandName() { return brandName; }
	void setBrandName( string name ) {
		if ( brandName[0] == '\0' ) {
			strcpy( brandName, (const char*)name.c_str() );
		}
	}
	// 利用する
	virtual void Use() {
		this->used = true;
	}
	// シリアライズ
	virtual void *Serialize() {
		int size = sizeof(Bag);
		void *data = new char[size];
		memcpy( data,this, size );
		return data;
	}
	// 逆シリアライズ
	static Bag *Serialize(void *data) {
		Bag *bag = (Bag*)data;
		return bag;
	}
};
class Prada : public Bag 
{
public:
	Prada() : Bag() {
		strcpy( brandName, "Prada" );
	}
};
class Tiffany : public Bag 
{
public:
	Tiffany() : Bag() {
		strcpy( brandName, "Tiffany" );
	}
};

class Person
{
public:
	char _name[30];
	Telephone _phone;
public:
	// コンストラクタ
	Person( const char *name ) {
		strcpy( _name, name );
		// 自分のコールバックを登録
		_phone.setMyPhone( this, &Person::onPhone );
	}
	// 相手に電話を掛ける
	void setPhone( Person *person ) {
		_phone.setPhone(person->getPhone());
	}
	// 電話を受け取るアクセッサ
	Telephone *getPhone() {
		return &(this->_phone);
	}
	// データ受信のコールバック関数
	virtual void onPhone( const void *data, int size ) {
		cout << "to " << _name << ": " << (char*)data << endl;
	}
	// データ送信の関数
	virtual void sendPhone( const void *data, int size ) {
		if ( size == 0 ) {
			cout << "from " << _name << ": " << (char*)data << endl;
		} else {
			cout << "from " << _name << ": send bag." << endl;
		}
		_phone.sendData( data, size );
	}
};

class Alice : public Person 
{
private:
	Bag *_bag;
public:
	Alice() : Person("alice"), _bag(NULL) {
	}
	// データ受信のコールバック関数
	virtual void onPhone( const void *data, int size ) 
	{
		if ( size == 0 ) 
		{
			cout << _name << ":" << "this is no bag!!!" << endl;
		} else {
			Bag *bag = (Bag*)data;
			Prada *prada = dynamic_cast<Prada*>(bag);
			if ( prada == NULL ) {
				cout << _name << ":" << "this is no Prada!!!" << endl;
			} else {
				cout << _name << ":" <<	"thank you Prada." << endl;
				this->_bag = Bag::Serialize((void*)data );
			}
		}
	}
	string getBrandName() {
		return _bag->getBrandName();
	}
		
};

int main(void)
{
	Alice alice;
	Person lewis("lewis");

	// アリスに電話を掛ける。
	lewis.setPhone( &alice );
	// hello と言う
	lewis.sendPhone( "hello", 0 );
	// ティファニーを転送
	Bag *bag = new Tiffany();
	lewis.sendPhone( bag->Serialize(), sizeof(Tiffany));
	delete bag;
	// プラダを転送
	bag = new Prada();
	lewis.sendPhone( bag->Serialize(), sizeof(Prada));
	delete bag;
	// アリスが持っているのはプラダの鞄
	cout << "alice has " << alice.getBrandName() << endl;
	return 0;
}

送ってくるサイズをチェックせずに Bag クラス決め打ちなのは、別に考えるとして、この電送方式は転送元でコピーを作っておいて、転送先でそのメモリを使うっていう方式ですね。勿論、転送先でコピーを作ってもいいわけですが、懐かしの WM_COPYDATA は、この方式なのでこれにしてみました。と言うのは後付けでして、電送というのだから、

  1. 電送元で、一度、電子レベルに分解して
  2. 電送している間は、単なるビット列にして、
  3. 電送先で、なんらかの形で元に戻す。

というスタートレックな電送方式がイメージできるものがいいですよね。この Telephone だと、電話と電話の間の電送線のイメージが入っていないので、単なるビット列ってのが想像しづらいのです。いわば、この間は SOAP だったり XML データだったりするわけで、一番簡単なのが元のデータのフォーマットを維持したビット列(今回の方式)ですね。ビット列の場合、両方の端末が同じ CPU/OS である必要があるので(エンディアンとかパディングとか)、この転送している間は、別のフォーマットがよかったりします。というかそういう現実になっています。

ん~、これくらいになるとクラス図を示さないと分かりづらいですね。このあたりは後程。

カテゴリー: C++ | ルイスはアリスにプラダを電送する はコメントを受け付けていません

ルイスはアリスに電話する(2)

前回は、直接の継承を使って Phone の機能を使いましたが、今回は委譲を使ってます。
若干、回りくどくなる分だけソースコードがややこしいのですが。

// for ルイスはアリスに電話する(2)
// Telephone クラスを Person と独立に機能させる

#include <string.h>
#include <iostream>
using namespace std;

class Person ;

class Telephone {
protected:
	Telephone *_telephone;
	Person *_person ;
	void (Person::*_onPhone)(const void *, int);

public:
	// 自分の onPhone を登録
	void setMyPhone(Person *person, void (Person::*onPhone)(const void *, int)) {
		_person = person ;
		_onPhone = onPhone;
	} 
	// 相手先を登録
	void setPhone( Telephone *telephone ) {
		this->_telephone = telephone ;
	}
	// データを送信
	virtual void sendData( const void *data, int size ) {
		if ( _telephone != NULL ) {
			_telephone->onPhone(data,size);
		}
	}
	// データを受信する関数
	virtual void onPhone( const void *data, int size ) {
		if ( _onPhone != NULL ) {
			(_person->*_onPhone)( data, size );
		}
	} 
};

class Person
{
public:
	char _name[30];
	Telephone _phone;
public:
	// コンストラクタ
	Person( const char *name ) {
		strcpy( _name, name );
		// 自分のコールバックを登録
		_phone.setMyPhone( this, &Person::onPhone );
	}
	// 相手に電話を掛ける
	void setPhone( Person *person ) {
		_phone.setPhone(person->getPhone());
	}
	// 電話を受け取るアクセッサ
	Telephone *getPhone() {
		return &(this->_phone);
	}
	// データ受信のコールバック関数
	virtual void onPhone( const void *data, int size ) {
		cout << "to " << _name << ": " << (char*)data << endl;
	}
	// データ送信の関数
	virtual void sendPhone( const void *data, int size ) {
		cout << "from " << _name << ": " << (char*)data << endl;
		_phone.sendData( data, size );
	}
};

int main(void)
{
	Person alice("alice");
	Person lewis("lewis");

	// アリスに電話を掛ける。
	lewis.setPhone( &alice );
	lewis.sendPhone( "hello i'm lewis", 0 );

	return 0;
}

Telephone と Person クラスの間には、継承の関係はありません。Phone has a Telephone となって、has_a の関係になりますね。ITelephone を Person クラスが継承した場合は is_a の関係です。

この Phone 機能を使う環境では、main 関数の呼び出しは変わりません(メソッド名を変えたのでそこだけです)。Person クラスの中でコールバックを登録する Person::setPhone メソッドでは、メンバ関数ポインタを使いますが、C# なんかだとデリゲートで済みます。ここは、C++ なので若干ややこしいことになっているかと。ひとつ注意したいのが、リスナーパターンを利用した場合には、Person クラスはなんらかのインターフェースを継承する必要があります(Telephone クラスからコールバックのメソッドを呼び出すため)。ですが、Person クラスの継承元を使わずに/継承させずに、Phone の機能が使えるという点で、デリゲートや関数ポインタを使った場合は異なるのです。このあたり、差異が微妙ですが、継承元というのがポイントですね。

ここで、転送するデータは音声ということで文字列しか扱っていませんが、任意のデータを送ることができます。分かりますね、

アリスは物理転送機を持っている | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2025

のところで話した、シリアライズの機能を使ってプラダのバッグを送ることができるのです。

ってな訳で、この話は次回に。

カテゴリー: C++ | ルイスはアリスに電話する(2) はコメントを受け付けていません

ルイスはアリスに電話する

誰かが使ったらアリスに知らせろ | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2017
誰かが使ったらアリスに知らせろ(2) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2023

なところで、イベントを受信するパターンを書いたわけですが、これを使うと電話が作れます。いわゆる、

  • 電話を掛けるというコネクション
  • 電話を受けるという非同期イベント

な感じです。電話を受けるときには、電話番をしている訳ではなくて(電話番の場合はポーリングな訳ですが)、電話のベルが鳴ったら受話器を取るという、非同期的なイベントになります。

// for ルイスはアリスに電話する

#include <string.h>
#include <iostream>
using namespace std;

class ITelephone {
protected:
	ITelephone *_person;
public:
	// 相手先を登録
	void setPhone( ITelephone *person ) {
		this->_person = person ;
	}
	// データを送信
	virtual void sendData( const void *data, int size ) {
		if ( _person != NULL ) {
			_person->onPhone(data,size);
		}
	}
	// データを受信する仮想関数
	virtual void onPhone( const void *data, int size ) = 0;
};

class Person : public ITelephone
{
public:
	char _name[30];
public:
	Person( const char *name ) {
		strcpy( _name, name );
	}
	virtual void onPhone( const void *data, int size ) {
		cout << "to " << _name << ": " << (char*)data << endl;
	}
	virtual void sendData( const void *data, int size ) {
		cout << "from " << _name << ": " << (char*)data << endl;
		ITelephone::sendData( data, size );
	}
};

int main(void)
{
	Person alice("alice");
	Person lewis("lewis");

	// アリスに電話を掛ける。
	lewis.setPhone( &alice );
	lewis.sendData( "hello i'm lewis", 0 );

	return 0;
}

これを実行するとこんな感じ。

D:\work\blog\src\alice>a
from lewis: hello i'm lewis
to alice: hello i'm lewis

ルイスからアリスに電話を掛けると、アリスの onPhone メソッドが呼び出されるという仕組みです。
実は、この ITelephone インターフェースは、Person で継承しているために、

人に電話が内蔵されている

って意味になっていますがw、ま、気にせずにッ!!! 単純に継承したほうが、機能拡張は楽だというオブジェクト指向の落とし穴に陥った例です。2,3 機能追加であれば、このように直で継承するほうがいいのですが、これの数が増えてしまう場合には、委譲という形で機能を分離したほうがよいのです。

人が電話を持っているパターンは次回に作り直します。

カテゴリー: C++ | ルイスはアリスに電話する はコメントを受け付けていません