「ほっ」と。キャンペーン
<   2010年 11月 ( 32 )   > この月の画像一覧
.Net(C#)アプリからExcelのVBAを呼び出したい(遅延バインディング)
.NetアプリからExcelのVBAを呼び出したい(事前バインディング) で参照設定からCOMのOffice操作DLLを呼び出す方法を書きました。(あらかじめ呼び出すオブジェクトのタイプライブラリを参照してコンパイル時に型チェックをするこの方法を事前バインディングと言います。)

しかしこの方法では参照設定したバージョンのExcelが入っているPCでしか実行できません。

対策としては、実行時にバインドする遅延バインディングを用います。

事前バインディングのコードと比較しながら遅延バインディングのコードを下記に書いてみます。
(コメント部分のコードが事前バインディングになります。)

using System.Runtime.InteropServices;
using System.Reflection;
 
public ExeMacro()
{
 
//Excelマクロファイルパス
string strMacroPath = @"D:\test.xls";
 
// Excel操作用COMオブジェクトを生成する
//ApplicationClass oExcel = new ApplicationClass();
object oExcel = CreateObject("Excel.Application");
 
//ワークブックコレクションオブジェクトを生成する。
//Workbooks oBooks = oExcel.Workbooks;
object oBooks = oExcel.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, oExcel, null);
 
//Excelファイルのオープン
//Workbook oBook = oBooks.Open(strMacroPath);
object oBook = oBooks.GetType().InvokeMember(
"Open", BindingFlags.InvokeMethod, null,
oBooks, new object[] {
strMacroPath
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
, Type.Missing
});
 
// Excelファイルの表示
//oExcel.Visible = true;
oExcel.GetType().InvokeMember("Visible", BindingFlags.SetProperty, null, oExcel, new object[] { true });
 
//マクロ実行(Testというサブプロシージャを実行する)
//oExcel.Run("Test");
oExcel.GetType().InvokeMember("Run", BindingFlags.InvokeMethod, null, oExcel, new object[] { "Test" });
 
//閉じる
//oBook.Close(false);
oExcel.GetType().InvokeMember("Quit", System.Reflection.BindingFlags.InvokeMethod, null, oExcel, null);
//COM解放
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oBook);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oBooks);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oExcel);
oBook = null;
oBooks = null;
oExcel = null;
}
 
/// <summary>
/// COMオブジェクトへの参照を作成および取得します
/// </summary>
/// <param name="progId">作成するオブジェクトのプログラムID</param>
/// <param name="serverName">
/// オブジェクトが作成されるネットワークサーバー名
/// </param>
/// <returns>作成されたCOMオブジェクト</returns>
public static object CreateObject(string progId, string serverName)
{
Type t;
if (serverName == null || serverName.Length == 0)
t = Type.GetTypeFromProgID(progId);
else
t = Type.GetTypeFromProgID(progId, serverName, true);
return Activator.CreateInstance(t);
}
 
/// <summary>
/// COMオブジェクトへの参照を作成および取得します
/// </summary>
/// <param name="progId">作成するオブジェクトのプログラムID</param>
/// <returns>作成されたCOMオブジェクト</returns>
public static object CreateObject(string progId)
{
return CreateObject(progId, null);
}


使い終わったら、Quitメソッドで、Excelファイルを閉じて、Marshal.FinalReleaseComObjectメソッドを使って、COMオブジェクトを必ず解放するようにしましょう。
上記のコードで事前と遅延バインディングの比較をしてもらうとわかるんですが、基本的に oExcel.GetType().InvokeMember メソッドでExcel内のメソッド実行したりプロパティを変更できるようです。(たしかに型が分からないわけですがら、リフレクションを使ってメソッド名やプロパティを指定しないといけませんね。)


VB.Netだとより簡単に遅延バインディング(実行時バインディング)ができるんですが、C#は結構面倒ですね。(VBはVB6時代から実行時バインディング使ってましたからね。)

参考:
(VB.Net)COMオブジェクトを解放する
MSサポート:Visual C# .NET で Office オートメーション サーバーをバインドする方法
とほほな日々 備忘録: Excel 遅延バインディング(LateBinding)
C#から遅延バインディングでEXCELのシート保護をする - とりあえずですけれども - 1981s
リフレクションを利用したレイトバインディングでExcelファイルを開く - Bug Catharsis
(続)リフレクションを利用したレイトバインディングでExcelファイルを開く - Bug Catharsis
Excelの解放: DOBON.NETプログラミング掲示板過去ログ
圧砕の雑記的な何か: 遅延バインディング
[PR]
by jehoshaphat | 2010-11-27 13:30 | .Net開発 | Trackback | Comments(0)
.Net(C#)アプリからExcelのVBAを呼び出したい(事前バインディング)
.NetアプリケーションからExcel2007のVBA(マクロ)標準モジュール内のサブプロシージャを呼び出す方法です。


まず、参照設定で、COMの Microsoft Excel 12.0 Object Library を追加します。
(12.0とかはExcelのバージョンで変わってきます。)

そして、下記のようなコードを書きます。(C#)
using Microsoft.Office.Interop.Excel;
 
//Excelマクロファイルパス
string strMacroPath = @"D:\test.xls";

 
//Excel操作用オブジェクト生成
ApplicationClass oExcel = new ApplicationClass();
//表示する
oExcel.Visible = true;
Workbooks oBooks = oExcel.Workbooks;

//Excelファイル開く
Workbook oBook = oBooks.Open(strMacroPath);
 
//マクロ実行(Testというサブプロシージャを動かす)
oExcel.Run("Test");
 
//終了処理(リソース解放)
oBook.Close(false);

System.Runtime.InteropServices.Marshal.ReleaseComObject(oBook);
oBook = null;
 
System.Runtime.InteropServices.Marshal.ReleaseComObject(oBooks);
oBooks = null;

 
oExcel.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcel);
oExcel = null;

簡単ですね。といってもほぼ参考先まるままですが。。
しかし、この方法ではExcel2007が入っているPCでしか実行できません。
例えばExcel2003が入っているPCとかだと、エラーとなります。
ということで、どのバージョンでのExcelでも実行できる方法は次の記事で書きます。

参考:
全ては時の中に… : 【VB.NET】VB.NETからExcelのVBA(マクロ)を実行する
[PR]
by jehoshaphat | 2010-11-27 13:25 | .Net開発 | Trackback | Comments(0)
(.Net).NetアプリケーションからAccess(MDB)のテーブルをExcelファイルにエクスポート
Accessにはテーブルを各種形式にファイルにエクスポートできる機能があります。
(テーブル選択し、ファイルメニュー→エクスポートのことです。)

さて、これを .Net のアプリケーションからAccessを起動しなくてもExcelファイル(xls)にエクスポートしてやろうと言うのが今回のしたいことです。

●SQLクエリを使う方法
まず JETデータベース(MDB) のSQLクエリ自身にエクスポートする機能があるようです。
下記のようなSQL構文になります。

SELECT * ITNO エクスポート先ファイル名 FROM テーブル名

.Netからは OLEDB 経由で上記構文のSQLを実行すればいいだけです。
下記にサンプルソースを載せます。(C#)

//OLEDB接続オブジェクト生成
System.Data.OleDb.OleDbConnection cn = new System.Data.OleDb.OleDbConnection();
cn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\"D:\\test.mdb\";";
//OLEDBコマンドオブジェクト生成
System.Data.OleDb.OleDbCommand cmd = new System.Data.OleDb.OleDbCommand();
cmd.Connection = cn;
 
//SQLクエリでエクスポートを指定
string strSQL = "SELECT * INTO [Excel 8.0;Database=D:\\test0.xls].[テストシート] FROM [tblTest]";
cmd.CommandText = strSQL;
 
cn.Open();
//実行
cmd.ExecuteNonQuery();
cn.Close();

上記を実行すると、D:\test.mdb 内の "tblTest" テーブルが D:\test0.xls ファイルの "テストシート" に出力されます。
この時 JET エンジンは Microsoft Jet 4.0 OLE DB プロバイダを使ってエクスポートするようですね。
対応するExcelファイルファイルのバージョンは下記のとおりです。
Excel 3.0
Excel 4.0
Excel 5.0(Excel95)
Excel 8.0(Excel97-2003)

(なお、Excel 12.0 以降(Excel2007)は JET 4.0 OLE DB じゃサポートしてないっぽいです。Microsoft Office 12.0 Access Database Engine OLE DB Providerをいれれば新形式も扱えるようになということがサンプルプログラム集 [631_ADOでAccess2007用のJETデータベースを扱う]に書かれていました。)


さて、まずこの方法で試みたわけですが、ExecuteNonQuery メソッドで下記のような例外が発生してしまいます。

System.Data.OleDb.OleDbException はハンドルされませんでした。
Message=数値フィールドがオーバーフローしました。
Source=Microsoft JET Database Engine
ErrorCode=-2147467259
StackTrace:
場所 System.Data.OleDb.OleDbCommand.ExecuteCommandTextErrorHandling(OleDbHResult hr)
場所 System.Data.OleDb.OleDbCommand.ExecuteCommandTextForSingleResult(tagDBPARAMS dbParams, Object& executeResult)
場所 System.Data.OleDb.OleDbCommand.ExecuteCommandText(Object& executeResult)
場所 System.Data.OleDb.OleDbCommand.ExecuteCommand(CommandBehavior behavior, Object& executeResult)
場所 System.Data.OleDb.OleDbCommand.ExecuteReaderInternal(CommandBehavior behavior, String method)
場所 System.Data.OleDb.OleDbCommand.ExecuteNonQuery()
.....

調べてみたんですがエクスポート時におきる現象はどうもあまりないようです。
Accessのファイルメニューからエクスポートした時はうまく行くんですが。。。
簡単な mdb を作るとうまくいきました。
今回ターゲットにしてる mdb は数万件のデータが入っており、mdb にしてはそこそこ大きいので、どっかで型の不整合があるのかもしれません。
原因追究したかったんですが、時間が無いので別の方法をとることにしました。

参考:
きままにスクリプト
サンプルプログラム集 [623_ADOでJETデータベースのエクスポート]
MSサポート:ADO を使用して ADO データ ソースから Excel にデータを転送する方法


●VBScriptを使う方法
VBScript(WSH)から Access の TransferSpreadsheet メソッドを呼び出してもExcel形式にエクスポートできるようなので、その方法を試してみました。
下記のようなスクリプトになります。

'モード指定 1がエクスポートみたい(0だとインポート)
Const acExport = 1
'エクスポートするExcelファイルのバージョン
Const acSpreadsheetTypeExcel9 = 8
'Access操作用オブジェクト生成
Set objAccess = CreateObject("Access.Application")
'対象となるMDB指定
objAccess.OpenCurrentDatabase "D:\test.mdb"
'Excelファイルにエクスポート
objAccess.DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel9, "tblTest", "D:\test1.xls", True

上記のコードはMS:Hey, Scripting Guy! Access データベースのテーブルをワークシートとして保存する方法はありますかまるままです。
ただSQLクエリの時と違って、Excelのシート名の指定はできません。エクスポートしたテーブル名がシート名となります。(Accessファイルメニューで行うときと同じですね。)

後はこのスクリプトを .Net アプリケーションから実行してやればいいだけです。
上記のスクリプトが export.vbs というファイル名で、exe と同じパスにあるとした場合、こんな感じです。(C#)

using System.Diagnostics;
 
string strScrptPath = System.Windows.Forms.Application.StartupPath + "\\export.vbs";
//VBScript起動(wscriptなのはWaitForInputIdleを使えるようにするため)
Process prc = Process.Start("wscript.exe", "\"" + strScrptPath + "\"");
//↑で起動したプロセスが終わるまで待つ。
while (true)
{
System.Threading.Thread.Sleep(500);
try {
Process.GetProcessById(prc.Id);
} catch (Exception){
break;
}
}
//prc.WaitForInputIdle();

プロセス終了の判定ですが、本当はWaitForInputIdleメソッド使いたかったんですが、どうやらwscriptのようなアクティブウィンドウを持たない場合は使えないようです。
仕方ないので、無限ループで回して、プロセスIDが無くなったらループ抜ける仕組みにしました。

これでようやく要件どおりに動くようになりました。


参考:
Excelへデータを出力する方法 - TransferSpreadsheetメソッド:SampleFile126
[VBA]AccessからExcelにデータをインポートする方法 (ADO編) - DQNEO起業日記 別の方法としてExcelのVBAからmdbひらいてコピペする方法もあるようです。
MSサポート:Visual Basic .NET と ADO.NET を使用して Excel ブックのレコードの取得と変更を行う方法 JET OLE DBプロバイダを使って.NetからExcelを操作する方法もあるようです。
[PR]
by jehoshaphat | 2010-11-27 13:21 | .Net開発 | Trackback | Comments(0)
(仮想化)仮想HDDをコピーしてドメインにログインしようとしたらできない

Hyper-Vで仮想環境を構築してるわけですが、あるWindowsServer2003が入ったVHDをコピーして、別仮想インスタンスとし、コンピュータ名を変更後ドメインにログインしようとしたらできなくなりました。
ログイン時に「指定されたドメインの名前またはセキュリティ ID (SID) はそのドメインの信頼情報と矛盾します。」と言われるのです。

どうやらコピー元とSIDが一緒になってるの矛盾が生じてるようですね。


対応策として、SIDを変える必要があります。
Windows Server 2003 インストールディスク内の SUPPORT\TOOLS 内の DEPLOY.CAB 内に必要なツールが入ってるので、中を一式ローカルディスクに展開します。

で、sysprep.exe を起動し、表示されたダイアログ(システム準備ツール)でOKを押下します。
次のダイアログで、"再ツール" を押下し、確認画面でOKを押すとシャットダウンし、次回起動時に SID の再生成が始まります。
次回起動時には Windows のセットアップウィザードが始まりインストール時のように使用者名やプロダクトキー、ネットワークの設定等をしなければなりません。

しかし、とりあえずこれで無事にドメイン参加しログインすることができました。


参考:
無問題(めいうぇんち):sysprepとsetupcl:
環境の複製にはnewsidではなくsysprepを利用する - @IT
[PR]
by jehoshaphat | 2010-11-27 13:15 | 豆知識 | Trackback | Comments(0)
2010年プログラミング言語のシェア
TIOBE Programming Community Index for November 2010 から検索エンジンの結果から人気のあるプログラミング言語のランキングを確認できます。

e0091163_13122015.jpg

e0091163_13122420.jpg



やはり、Java、Cが圧倒的ですね。
ただJavaは以前に比べると徐々にシェアを落としている感じがします。

スクリプト言語(動的型付け言語)はPHPが一番シェア占めています。ただ徐々にPythonが伸ばしています。

このシェアからどの言語使うかという参考にもなりそうです。


参考:
人気が急上昇した言語はグーグルの「Go」と「PHP」、メジャーなのは静的型付け言語。オランダのTIOBEが発表
世の中の開発言語のシェアの推移
[PR]
by jehoshaphat | 2010-11-27 13:13 | 思ったこととかニュースとか。。 | Trackback | Comments(0)
WEBサイトのセキュリティチェックに役立つオンラインツール
任意のWEBサイトが安全かどうか、また危険なWEBサイトがどんなマルウェアが置いてあるのかを調べる方法です。
いろいろなオンラインツールがあるようです。



e0091163_2041990.jpg

aguse


↑一番分かりやすいサイトです。日本発ですし、カペルスキーでのマルウェアチェック、リン先オブジェクトの一覧、ブラックリスト、ドメイン情報の詳細(GoogleMapで表示)などがあります。

↓最近発見した国内の感染サイトをチェックした時のスクリーンショットです。
e0091163_2064642.jpg




e0091163_2065968.jpg

Dr.WEB

↑ロシアのセキュリティ企業みたいです。http://online.us.drweb.com/のページだとローカル上のファイルのチェックもできるようです。

↓INFECTEDだと感染しています。
e0091163_2073278.jpg




e0091163_2074589.jpg

URL Void

↑十数種類のアンチウイルスサービスでチェックしてくれます。各製品のウイルス定義名も表示されるので便利です。

↓サイトの全体の評価です。
e0091163_2084769.jpg


↓上記のページで、Scan Website リンクを押下すると各アンチウイルス製品の結果が出ます。
e0091163_2092245.jpg




e0091163_2095181.jpg

Wepawet

↑FlashやJavaScript/PDFを検査する時に使えます。

↓ちょっと分かりづらいですがマルウェアと関係してるかどうかが把握できます。
e0091163_20103149.jpg




e0091163_2219276.jpg

VirusTotal

↑複数のアンチウイルスソフトで検査できます。ローカルファイル以外にも、URLを指定してサイトを検査することもできるようです。今のところ、43種類のエンジンで検査できるようです。
昔は日本語表示できたようでしたが、今は何故か英語でした。。。




e0091163_2133877.jpg

vURL Online

↑指定したページのHTMLソースを表示してくれます。JavaScript部分は赤色で表示されるので、怪しいサイトにブラウザでアクセスしたくないけどソースは確認したいという時に使えるかもしれません。



e0091163_2153571.jpg

Sucuri SiteCheck

↑英語表記ですが、指定したページの安全性をチェックしてくれます。リンク先のJavaScriptファイルについても変なところにリダイレクトしていないかチェックしてくれるので、優れものです。

↓最近発見した感染サイトをチェックした時のスクリーンショットです。
e0091163_2144579.jpg




e0091163_2193283.jpg

Unmask Parasites

↑英語表記ですが、シンプルな解析をしてくれます。JavaScriptの不正リダイレクトも一部ソースを表示してくれます。

↓最近発見した感染サイトをチェックした時のスクリーンショットです。
e0091163_2195221.jpg



参考:
無料URL(Webサイト)ウイルスチェック一覧 - フリーソフト100
サイト診断&オンラインリンクスキャン 【URLウイルスチェック】
無料オンラインウイルス動作挙動解析サイト 【サンドボックス】
悪意あるPDF(malicious PDF)に含まれる Exploit コードを pdf-parser.py で確認する - 思い立ったら書く日記 PDFの実行コードに関する話です。
[PR]
by jehoshaphat | 2010-11-23 20:14 | ツール | Trackback | Comments(0)
テキストエディタで任意の文字が含む行を削除

ログファイルを解析してて特定の文字列を含む行を削除したい時があります。

そういう場合、テキストエディタの機能で正規表現を使った置換ができるなら、簡単に削除できるようです。

検索文字列:「^.*文字.*\n」
置換文字列:なし

これができるとログ解析の作業が非常に楽になりますね。


参考:
ある文字が含まれる行を削除する - 逆引き秀丸の正規表現で置換サンプル集 - DEARIE 秀丸でのサンプルですが三流プログラマ愛用のEmEditorでもできました。
[PR]
by jehoshaphat | 2010-11-23 19:59 | 豆知識 | Trackback | Comments(0)
プログレスバーでアップロード進捗状況がわかるUber-Uploader
100MB越えのファイルを送ってもらう必要があります。
しかし、相手はPC,ネット素人さん。

通常のアップローダだと進捗わからないのでアップしきる前に切断されそうですし、オンラインストレージ使わすのは嫌ですし、FTPは論外ということで進捗状況がわかるPHPのアップローダ Uber-Uploader を試してみました。

Uber-Uploader は PHP , Peral , AJAX を使ってプログレスバーで進捗状況を表示することができます。

まず設置方法。
ダウンロードはhttp://uber-uploader.sourceforge.net/からできます。


解凍したルートフォルダの INSTALL_AND_FAQ.HTML ファイルを見れば導入方法わかるんですが、何せ英語なもんで。。。。

cgi-bin\ubr_upload.pl ファイルをWEBサーバのCGIファイル置き場に置きます。
LinuxにてパッケージインストールしたApacheならたいていは /var/www/cgi-bin になろうかと思います。

html\ubr_default_config.php が設定ファイルのようです。

とりあえず変更したのは下記です。
$_CONFIG['allow_extensions'] : 拡張子
$_CONFIG['check_file_name_regex'] : アップ可能ファイル名正規表現(すべてのファイルをOKにするには '.*' と定義します)
$_CONFIG['strict_file_name_regex'] : 同上?
$_CONFIG['max_upload_size'] : アップ可能最大ファイルサイズ
$_CONFIG['redirect_url'] : アップが終わった後に表示するページのパス
$_CONFIG['upload_dir'] : アップロードしたファイル保存先

後は html ファイルをWEBサーバドキュメントルート配下に配置します。


アップロード開始は ubr_file_upload.php になります。
参照ボタンからファイルを選びます。ファイル選択すると、リストに追加されます。
次々と追加していく感じでOKみたいですね。
リストから消すには×ボタンを押下します。
e0091163_19554421.jpg


↓アップロード中の画面です。
e0091163_19555537.jpg


↓[+]ボタンを押下すると詳細内容が展開されます。
e0091163_1956467.jpg


↓アップ終了画面です。
e0091163_1956985.jpg


安定もしており、結構優れものです。


参考:
MOONGIFT : Uber Uploader オープンソース・ソフトウェア/フリーウェアを毎日紹介
AJAXファイルアップローダ『Uber-Uploader』 - Developer☆STYLE
phpでファイルアップロードの進捗状況を表示する(AJAX) - 蝸牛の歩み
[PR]
by jehoshaphat | 2010-11-23 19:57 | ツール | Trackback | Comments(0)
BlogPetがサービス終了するらしい
Exciteブログで公式対応してるブログツールに、BlogPetというのがあり現在それを使ってます。
e0091163_195047.jpg


このブログパーツにはアクセス解析の機能があり、その機能メインで使ってました。
(とくに生ログがとれるのが有用でした。)

しかし、12/15でサービス終了することになったようです。
e0091163_1950486.jpg


残念ですね。しかし、さっきExciteの公式対応ブログページ見たら忍者アクセス解析が対応してたのでそっちに乗り換えようかと思います。

とりあえず、BlogPetのアクセス解析機能のハードコピーでも載せておきます。

↓日別のページビューとユニークアクセス数のグラフです。
e0091163_19521475.jpg




↓どのリンクから来たかです。
e0091163_195113.jpg


↓生ログです。ダウンローでもできるので助かってました。(一月単位だと件数多すぎてダメなので最近は週単位でダウンロードしてました。)
e0091163_1950672.jpg

[PR]
by jehoshaphat | 2010-11-23 19:52 | 思ったこととかニュースとか。。 | Trackback | Comments(0)
Google Chromeのキャッシュ先を変更する
自宅PCのメモリを増強し、RAMディスクを使うようになったので、RAMDisk上にGoogleChromeのキャッシュを置くことでレスポンスの向上を試みました。

Chromeの場合は、起動オプションで設定できるようです。

起動オプションは下記のとおりです。
・ディスクキャッシュの変更
--disk-cache-dir="ディスクキャッシュ保存先パス

・ディスクキャッシュサイズ
--disk-cache-size=バイト数

・メディアキャッシュサイズ(HTML5のVideo/Audioファイルがキャッシュ。--disk-cache-dir オプションで指定した場所に「Media Cache」フォルダが作成)
--media-cache-size=バイト数


起動オプションは Google Chrome のショートカットで指定します。
下記のような感じです。(ディスクキャッシュを 200MB とした場合)

C:\Users\a\AppData\Local\Google\Chrome\Application\chrome.exe --disk-cache-dir="H:\cache\GoogleChrome" --disk-cache-size=204800000 --media-cache-size=204800000


これで起動しなおせば指定したディスクキャッシュフォルダが自動的に作られます。

まだ設定したばかりなので高速化を実感するところまではいってません。
ここでChromeやIE8等マルチプロセスブラウザで、しばらく表示しないタブがあるとページアウトされて次に表示したときに遅いという現象がありましたが、解決するんといいんですが。。。


しかし、Windows7はタスクバーのショートカットのプロパティを簡単には変更できないのが不便ですね。


参考:
Google Chromeのキャッシュを高速なRAMに移動する方法 - じゃがめブログ
起動オプション - Google Chrome まとめWiki
SA-D-PC GoogleChromeのキャッシュをRAMディスクへ Chromeのショートカット以外から起動した場合は起動オプションが効かないため、レジストリの編集が必要なようです。ただ3流PGはChromeを既定のブラウザにしてないですし、基本ショートカットから起動するのでまだレジストリでの設定をやってません。
[PR]
by jehoshaphat | 2010-11-23 09:13 | 豆知識 | Trackback | Comments(0)