最近のトラックバック

2017年9月
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Google AdSense2

C#サンプル

C#でDIを実装

C#でDI(Dependency Injection)を実装するサンプルを紹介する。


■DIとは

依存性の注入 (いぞんせいのちゅうにゅう、Dependency Injection)とは、 コンポーネント間の依存関係をプログラムのソースコードから排除し、 外部の設定ファイルなどで注入できるようにするソフトウェアパターンである。
依存性の注入を利用したプログラムを作成する場合、 コンポーネント間の関係はインターフェースを用いて記述し、 具体的なコンポーネントを指定しない。 具体的にどのコンポーネントを利用するかは外部ファイルに記述することで、 コンポーネント間の依存関係をソースコードから排除できる。

依存性の注入 - Wikipediaから抜粋。

私の独自解釈では以下のとおり。

DIのインタフェースは主にStrategy パターンの為の規約であると考えている。
Strategy パターンはアルゴリズムを動的に切り替え可能な仕組みである。インタフェース(interface、base class)で内部構造を公開し、派生クラス先で独自の実装が可能とする。
複数の派生クラスの切り替え方法を設定ファイルを利用することでビルドを利用しない疎結合が生まれ、開発後に容易にアプリケーション内部の動作を変更できる。

※独自解釈の為、誤りや理解不足があると思います。話半分ぐらいで読んでいただけると非常に助かります。

■登場するクラス/処理
①インタフェース(intaface、base class)
②インタフェース継承クラス
③設定ファイルを基にインタフェース継承クラスのインスタンスを生成し、①②の変数に設定。
④③で作成したインスタンスをインタフェース経由で利用する処理。

■プロジェクト構成
本サンプルは以下の4プロジェクト構成となっている。
DLL/EXEファイル 概要 クラス/インタフェース プロジェクト名 プロジェクト参照/アセンブリ参照
DIBaseLib.dll 内部構造の公開仕様となるインタフェース/ベースクラスを含むクラスライブラリ。 DIInterface
DIBaseClass
DIBaseLib -
DIDerivedLib1.dll 公開仕様の派生クラスその1。
DIInterfaceまたはDIBaseClassの派生クラスを持つクラスライブラリ。
DIDerivedInterface1
DIDerivedClass1
DIDerivedLib1 DIBaseLib
DIDerivedLib2.dll 公開仕様の派生クラスその2。
DIInterfaceまたはDIBaseClassの派生クラスを持つクラスライブラリ。
DIDerivedInterface2
DIDerivedClass2
DIDerivedLib2 DIBaseLib
UseDIConsole.exe DIを利用するコンソールアプリケーション。
DI切り替えの設定ファイルも本プロジェクトで作る。
Program
Setting
UseDIConsole DIBaseLib System.Configuration



実際の開発時には上記のように複数プロジェクト・DLLに分けるのは必須ではない。
内部構造の公開仕様に当たるDIBaseLib.dllの部分を、外部の開発者に公開するつもりがなければ、1プロジェクトで完結することも可能だ。

■ファイル・フォルダ構成
各種ファイルは以下の配置をするものとする。
実行ファイルを起点とした相対パスでDI派生クラスDLL格納先binフォルダを作成する。

アプリケーションフォルダ
│  DIBaseLib.dll
│  UseDIConsole.exe
│  UseDIConsole.exe.config
│  
└─bin
        DIDerivedLib1.dll
        DIDerivedLib2.dll



なおGAC(グローバル・アセンブリ・キャッシュ)にアセンブリを登録する場合、DLLファイルの保管先は上記以外でも構わない。

■アプリケーション構成

・DIDerivedInterface1クラス・DIDerivedInterface2クラスはDIInterfaceインタフェースを継承。
・DIDerivedClass1クラス・DIDerivedClass2クラスはDIBaseClassクラスを継承。
・Programクラスは、Settingクラスを経由して構成ファイルを読み取る。※Settingクラスはプロパティ-設定するとVisual Studioが自動生成。
・Programクラスは、構成情報ファイルからインタフェース継承クラスを作成、インタフェースインスタンスに設定する。
・Programクラスは、DIInterfaceインスタンス・DIBaseClassインスタンスを利用して各派生クラスのShow()メソッドを呼び出す。


■内部構造の公開仕様となるインタフェース/ベースクラスを含むクラスライブラリ。(DI.cs - DIBaseLib.dll)

■公開仕様の派生クラスその1。(DIDerived1.cs - DIDerivedLib1.dll)

■公開仕様の派生クラスその2。(DIDerived2.cs - DIDerivedLib2.dll)
■DIを利用するコンソールアプリケーション。(Program.cs - UseDIConsole.exe)
■DIを利用するコンソールアプリケーションの構成ファイル。(UseDIConsole.exe.config → UseDIConsoleプロジェクト内ではapp.config)
<DIDerivedLib1.dllを利用する場合>
<DIDerivedLib2.dllを利用する場合>
<画面イメージ:UseDIConsoleプロジェクトのプロパティ設定>


■実行結果
<DIDerivedLib1.dllを利用した場合>


<DIDerivedLib2.dllを利用した場合>


※NET FrameworkのDI機能を一部のみ掲載。
機能 概要 利点 DIの交換部品 備考
DBProviderFactory
[ADO.NET]
DB操作の切り替え ■DBMS毎に用意することでDB操作のパフォーマンス向上とプログラム内の接続・参照・更新・トランザクション等のDB操作の統一化が可能。
■DBMSの種類を交換した時に対応しやすい。※DMBS毎のSQLの方言は除く。
■(金銭面や納期の関係で)テスト環境・運用環境で異なるDBやDB接続ドライバを使う場合に対応しやすい。
■ODBC接続(昔のDAO)
■OLE DB接続(昔のADO)
■SQL Server
■Oracle
■DB2
■MySQL
その他に各種ベンダーが提供。
ADO.NET Entity Frameworkの裏方でも使われている。
設定ファイルに複数登録できるためプラグインとしての要素もある。
セッション
[ASP.NET]
セッションデータ保存先の切り替え ■開発PC・テストサーバ・運用サーバのスペックに応じた保存先の変更が可能。
■運用後に保存先を変更しやすい。※プログラム改修は不要。環境変更だけで対応可能。
■セッションを使わない。
■セッションデータをメモリー上に保持。(高速。IISダウンや再起動でデータが失われる。負荷分散しずらい。)
■セッションデータを別プロセス上に保持。(中速。データ保持プロセスダウンでデータが失われる。)
■セッションデータをDBサーバ上に保持。(低速。DBサーバにクラスタリングやHDDにRAIDなどを用いれば、ほぼデータが失わることはない。)
ミッションクリティカル(24時間365日止まらないことを要求されること)に近づくほどセッションデータ保持方法は重要度を増す。
ASP.NETプロバイダモデル(認証)
[ASP.NET]
認証方法の切り替え ■ユーザ認証など保持方法や操作方法に関わらず操作の統一的化が可能。
■Windows認証を利用した。クライアントPC、ActiveDirecotyからのユーザ情報取得。
■DB等のデータストアからの認証データ取得・更新。
■その他、独自ロジックを用いた認証データの取得・更新。
ASP.NETプロバイダモデルはASP.NET2.0以降ではASP.NET Frameworkの重要な概念(だそう)です。
認証以外にもあるようだ。

詳細を知りたい場合、以下の文献を参考してみてください。
プログラミングMicrosoft ASP.NET 3.5 (マイクロソフト公式解説書 Microsoft Visual Studio 2008)
プログラミングMicrosoft LINQ (マイクロソフト公式解説書 Microsoft Visual Studi 2008)
ASP.NET 2.0 ~実践.NET Framework+Ajax Extensionsで実現するWebアプリ

これらの選択は開発前に考えることであるため、以下も参照してみてください。
開発前に考えること - niyoな日記


※注意
この考察は記事を書いた時点での記述者の考えです。
「全てのケースに対応」とか「一般的な考え」とか「ベストプラクティス」とかではありませんし、いうつもりもありません。
話半分ぐらいで読んでいただけると非常に助かります。

■DIはどんな時につくるの?
ほとんどが環境的な要因でアルゴリズムを開発中・開発後に変更する場合だと思う。
①単体テスト用のスタブ。他システム連携のインタフェースのスタブ。
②複数プロジェクトで使うフレームワーク作成し、なおかつプロジェクトに応じて部分的な内部処理を交換可能にしたいとき。
③DLLを分割することでビルド時間を少しでも短縮したい場合。
④コーディングが始まったがミドルウエアや接続ドライバが確定しない。または手元に届かなかった場合の代用品。

④を除くと、複数の企業が参加する大規模開発かつインタフェース抽出が可能な場合でないと費用対効果はほとんどないと思う。
もしくは独自フレームワークソフトウエア作成し販売する企業や、フレームワークソフトウエアを配布するオープンソースプロジェクトぐらいかも?

また、下記ページの内容も興味深いので参考してほしい。
InfoQ: 依存性注入(DI)は成功したか?
InfoQ: Does Dependency Injection pay off?(上記原文)

■そもそもDIて必要?
ほとんどのアプリケーション開発では不要です。
但し、フレームワークソフトウエアを提供側からみれば、ほぼ必須の概念です。

■仕様公開するのはインタフェース。それともベースクラス?
個人的にはベースクラスのほうがインタフェースよりも使い勝手がよいと思う。
理由はC#でプラグイン実装 - niyoな日記の 説明と同じです。
またプログラミングMicrosoft ASP.NET 3.5 (マイクロソフト公式解説書 Microsoft Visual Studio 2008)に「インタフェースと基底クラス」という以下のコラムがあります。


プロバイダモデルを最初にサポートした最初のASP.NETのバージョンであるASP.NET2.0のプリベータビルドでは、Strategyパターンの定義をそのまま利用した(つまり、インタフェースを使用)プロバイダモデルを実装していました。Bate 1の期間に、インタフェースは基底クラスに置き換えられ、リリースバージョンでもそのようになりました。ASP.NETチームは、この問題に対して結論を出したように思えますが、実際のところどうなのでしょうか?
インタフェースは、論理的に関連するメソッドを集めてたものでありメンバの定義のみが含まれ、コードは一切含まれません。インタフェース型は、型の部分的な説明であり、複数のクラスによってサポートされている可能性があります。良いインタフェースとは、何種類かの型によって実装されクライアントが使用したいと思うような便利で汎用的な機能をカプセル化したものです。IDisposable、IComparable、IFormattableのように、多くのインタフェースの後ろに「able」が付いているのはこのためです。インタフェースを実装する便利なクラスが1つしかないとしたら、それは不適切な設計を選択した結果でしょう。経験則として、新しいインタフェースは控えめに、深慮をもって導入すべきです。
基底クラスは子クラスのツリーに共通する振る舞いとプログラミングインタフェースを定義します。クラスはインタフェースよりも柔軟であり、バージョン管理をサポートします。ASP.NET2.0のクラスに新しいメソッドを追加した場合、そのメソッドが抽象メソッドでなければ、既存の派生クラスの機能はそれ以降も変化しません。だが、インタフェースではそうはいきません。
これらの点を踏まえると、可能な限り、インタフェースではなく基底クラスを使用すべきである、という規則が得られます(「常に基底クラスを使用する」ということではありません。)。筆者が思うに、プロバイダモデルに関する限り、基底クラスはすばらしい選択に思えます。
より一般的にいえば、基底クラスとインタフェース間の議論には簡単な答えはありません。実際、一部のアプリケーションに関しては、インタフェースで定義できる振る舞いに基底クラスを使用するのは、全体的な設計向上につながるカスタム基底クラスから新しいクラスの派生という選択肢を奪うものである、という意見もあるでしょう。ASP.NETは、そのプロバイダモデルに基底クラスを使用しており、そのパターンに従わなければなりません。


ASP.NETプロバイダモデルはDIの発展したみたいな概念であるため、DIでも一考の余地はあると思います。








C#でプラグイン実装

C#でプラグインを実装するサンプルを紹介する。


■プラグインとは
ここに来る人ならプラグインがどんなものか知っていると思うが一応記述する。

  • アプリケーションソフトウェアの機能を拡張するために追加するプログラムの一種。
  • 誰でも差し替え可能になっているアプリケーションコードの一部分を、プラグインと呼ぶ。
  • プラグインを利用する者に開発者と同じコンパイラを用意させるのは現実的ではないので、プラグインの場合、ダイナミックリンクライブラリと呼ばれる機構を使って、アドレスを間接的に参照する事によりこの問題を回避する。

プラグイン - Wikipediaから抜粋。

というわけで、本サンプルでもDLLを利用しプラグインを実装する。
なお多く場合、C#ではインタフェースやベースクラスを利用したポリモーフィズム(多態性)を利用することになる。
デザインパターンでいえばStrategy パターンかな?

■プロジェクト構成
本サンプルは以下の4プロジェクト構成となっている。
DLL/EXEファイル 概要 クラス/インタフェース プロジェクト名 プロジェクト参照
PluginBaseLib.dll プラグイン仕様となるインタフェース/ベースクラスを含むクラスライブラリ。 PluginInterface
PluginBaseClass
PluginBaseLib -
PluginDerivedLib1.dll プラグインその1。
PluginInterfaceまたはPluginBaseClassの派生クラスを持つクラスライブラリ。
PluginDerivedInterface1
PluginDerivedClass1
PluginDerivedLib1 PluginBaseLib
PluginDerivedLib2.dll プラグインその2。
PluginInterfaceまたはPluginBaseClassの派生クラスを持つクラスライブラリ。
PluginDerivedInterface2
PluginDerivedClass2
PluginDerivedLib2 PluginBaseLib
UsePlginConsole.exe 上記プラグインを利用するコンソールアプリケーション。 UsePlugin
Program
UsePlginConsole PluginBaseLib



■フォルダ構成
プラグインDLLは所定の場所に置く必要がある。
実行ファイルを起点とした相対パスでプラグイン専用フォルダを作成する。

アプリケーションフォルダ
│  PluginBaseLib.dll
│  UsePlginConsole.exe
│  
└─Plugin
        PluginDerivedLib1.dll
        PluginDerivedLib2.dll



■アプリケーション構成

・PluginDerivedInterface1クラス・PluginDerivedInterface2クラスはPluginInterfaceインタフェースを継承。
・PluginDerivedClass1クラス・PluginDerivedClass2クラスはPluginBaseClassクラスを継承。
・UsePluginクラスのfnLoadPluginInstance()メソッドは指定フォルダからPluginDerivedLib1.dllとPluginDerivedLib2.dllをロード、 PluginInterfaceまたはPluginBaseClassを継承しているクラスを探し、インスタンス化したのち、継承元のリストに登録する。
・UsePluginクラスのfnUsePluginProc()メソッドはリスト登録されたPluginInterfaceまたはPluginBaseClassの派生クラスをShow()メソッドを介して、派生先でオーバライドされたShow()メソッドを実行。


■プラグイン仕様となるインタフェース/ベースクラスを含むクラスライブラリ。(Plugin.cs - PluginBaseLib.dll)
■プラグインその1。(PluginDerived1.cs - PluginDerivedLib1.dll)
■プラグインその2。(PluginDerived2.cs - PluginDerivedLib2.dll)
■上記プラグインを利用するコンソールアプリケーション:プラグイン利用クラス。(UsePlugin.cs - UsePlginConsole.exe)
■上記プラグインを利用するコンソールアプリケーション:Programクラス。(Program.cs - UsePlginConsole.exe)
■実行結果


※注意
この考察は記事を書いた時点での記述者の考えです。
「全てのケースに対応」とか「一般的な考え」とか「ベストプラクティス」とかではありませんし、いうつもりもありません。
話半分ぐらいで読んでいただけると非常に助かります。

■プラグインはどんな時に作る?
まず遊び心が必要です。
ついでに、
◆インタフェース/ベースクラスを抽出できるが、開発者サイドで全ての機能を用意できない(しない)場合。
◆処理拡張が必要で、かつインタフェース/ベースクラスを抽出できる場合。
◆画像・音楽・動画のように多種の既知・未知のファイルフォーマットを扱う場合。
◆一般公募や特典のオマケでゲームを拡張したい場合。
 ※アクションゲームの武器、カーレースで車種・パーツ拡張・格闘ゲームで追加キャラ等
◆ブラウザやオフィスソフトのようなアドオン機能を提供する場合。
◆既知・未知を含め複数デバイスをサポートするアプリケーションの場合。

■そもそもプラグインって必要?
ほとんどのアプリケーションでは不要です。

■プラグインはいつ構想・実装する?
開発初期から構想しておくほうが望ましい。。
初回リリースからプラグインを一般公開しなくても設計段階でインタフェース/ベースクラス抽出したほうがよい。
リリース後に必要に応じてプラグイン化も可能だが構造変更のインパクトが非常に大きい。

■仕様公開するのはインタフェース。それともベースクラス?
個人的にはベースクラスのほうがインタフェースよりも使い勝手がよいと思う。

<ベースクラスのメリット>
◆利用頻度の高いフィールド変数を事前に用意できる。
◆共通処理をメソッドとして提供しやすい。
◆デザインパターン「template method」やイベントが使いやすい。
◆提供側がバージョンアップ時の機能拡張がしやすい。

<ベースクラスのデメリット>
◆シンプルなインタフェースと違い処理を内包するため不具合が起こりやすい。
 →修正したモジュールの再配布率があがる。
  →修正モジュールの配布が非常に手間。
   →利用側が修正モジュールをテストをしてくれない。
    また不具合を利用したプラグインを作られると目も当てられない
◆多重継承ができないため、利用側の自由度が低くなる。






C#で共有フォルダ作成/削除

C#で共有フォルダを作成/削除するサンプルを紹介する。


本サンプルではWin32APIの「NetShareAdd」「NetShareDel」を利用している。
これらAPIの実行権限には以下のセキュリティ要件がある。


Administrators または Account Operators ローカルグループのメンバ、あるいは Communication Operators、Print Operators、Server Operators のいずれかのグループのメンバだけがこの関数を実行できます。
Print Operator はプリンタキューだけを追加/削除できます。
Communication Operator は、通信デバイスキューだけを追加/削除できます。


どのユーザーでも共有フォルダ作成/削除できるというわけでないことを念頭に置いてほしい。


下記サンプルの共有フォルダ作成/削除クラス(ShareFolderManager)の公開メソッド定義は以下の通り。
概要 メソッド名
共有フォルダ作成 fnShareAdd()
共有フォルダ削除 fnShareDel()

■共有フォルダ作成/削除クラス(ShareFolderManager)

using System;
using System.Runtime.InteropServices;

/// <summary>
/// 共有フォルダ作成/削除クラス
/// </summary>
/// <remarks>
/// ■セキュリティ要件(NetShareAdd/NetShareDel関数)
/// Administrators または Account Operators ローカルグループのメンバ、あるいは 
/// Communication Operators、Print Operators、Server Operators のいずれかのグループの
/// メンバだけがこの関数を実行できます。
/// Print Operator はプリンタキューだけを追加/削除できます。
/// Communication Operator は、通信デバイスキューだけを追加/削除できます。
/// ■参考URL
/// NetShareAdd 関数
/// http://msdn.microsoft.com/ja-jp/library/cc446956.aspx
/// NetShareDel 関数
/// http://msdn.microsoft.com/ja-jp/library/cc446962.aspx
/// NetShareSetInfo 関数
/// http://msdn.microsoft.com/ja-jp/library/cc446974.aspx
/// </remarks>
public static class ShareFolderManager
{
    //=========================================================================================
    // Win32API 定数
    //=========================================================================================
    /// <summary>
    /// 共有デバイスがディスクドライブを示す定数
    /// <summary>
    private const short STYPE_DISKETREE = 0;
    // ==================
    // Win32API 戻り値
    // ==================
    /// <summary>
    /// 成功
    /// <summary>
    private const int NERR_Success = 0;
    /// <summary>
    /// ユーザーには、要求した情報へのアクセス権がありません。
    /// <summary>
    private const int ERROR_ACCESS_DENIED = 5;
    /// <summary>
    /// level パラメータで指定した値が無効です。
    /// <summary>
    private const int ERROR_INVALID_LEVEL = 124;
    /// <summary>
    /// 文字またはファイルシステム名が無効です。
    /// <summary>
    private const int ERROR_INVALID_NAME = 123;
    /// <summary>
    /// 指定されたパラメータは無効です。
    /// <summary>
    private const int ERROR_INVALID_PARAMETER = 87;
    /// <summary>
    /// 指定した共有名は、既にこのサーバーで使われています。
    /// <summary>
    private const int NERR_DuplicateShare = 2118;
    /// <summary>
    /// リダイレクトされたリソースに対して、この操作は無効です。指定されたデバイス名は、
    /// 共有リソースに割り当てられています。
    /// <summary>
    private const int NERR_RedirectedPath = 2117;
    /// <summary>
    /// デバイスまたはディレクトリが存在しません。 
    /// <summary>
    private const int NERR_UnknownDevDir = 2116;
    /// <summary>
    /// 利用可能なメモリが不足しています。
    /// <summary>
    private const int ERROR_NOT_ENOUGH_MEMORY = 8;
    /// <summary>
    /// 指定された共有名が存在しません。
    /// <summary>
    private const int NERR_NetNameNotFound = 2310;

    //=========================================================================================
    // Win32API 構造体
    //=========================================================================================
    /// <summary>
    /// 共有フォルダの設定
    /// </summary>
    /// <remarks>
    /// lmshare.h における _SHARE_INFO_2構造体定義
    /// typedef struct _SHARE_INFO_2 {
    ///     LMSTR   shi2_netname;
    ///     DWORD   shi2_type;
    ///     LMSTR   shi2_remark;
    ///     DWORD   shi2_permissions;
    ///     DWORD   shi2_max_uses;
    ///     DWORD   shi2_current_uses;
    ///     LMSTR   shi2_path;
    ///     LMSTR   shi2_passwd;
    /// } SHARE_INFO_2, *PSHARE_INFO_2, *LPSHARE_INFO_2;
    /// </remarks>
    private struct SHARE_INFO_2
    {
        /// <summary>
        /// 共有名
        /// </summary>
        public int shi2_netname;
        /// <summary>
        /// 共有タイプ
        /// </summary>
        public int shi2_type; 
        /// <summary>
        /// コメント
        /// </summary>
        public int shi2_remark;
        /// <summary>
        /// パーミッション
        /// </summary>
        public int shi2_permissions;
        /// <summary>
        /// 最大接続数
        /// </summary>
        public int shi2_max_uses;
        /// <summary>
        /// ?
        /// </summary>
        public int shi2_current_uses;
        /// <summary>
        /// 共有フォルダの絶対パス
        /// </summary>
        public int shi2_path;
        /// <summary>
        /// パスワード
        /// </summary>
        public int shi2_passwd;
        /// <summary>
        /// 引数付コンストラクタ
        /// (VisualStudio2008でビルド警告を防ぐためだけに利用)
        /// </summary>
        /// <param name="shi2_netname">共有名</param>
        /// <param name="shi2_type">共有タイプ</param>
        /// <param name="shi2_remark">コメント</param>
        /// <param name="shi2_permissions">パーミッション</param>
        /// <param name="shi2_max_uses">最大接続数</param>
        /// <param name="shi2_current_uses">?</param>
        /// <param name="shi2_path">共有フォルダの絶対パス</param>
        /// <param name="shi2_passwd">パスワード</param>
        private SHARE_INFO_2(
            int shi2_netname, int shi2_type, int shi2_remark, int shi2_permissions,
            int shi2_max_uses, int shi2_current_uses, int shi2_path, int shi2_passwd)
        {
            // VisualStudio2008でビルド警告を防ぐためだけに設定している
            // この初期化処理は全く意味がない
            this.shi2_netname = 0;      // 文字列:null
            this.shi2_type = 0;         // 数値:0
            this.shi2_remark = 0;       // 文字列:null
            this.shi2_permissions = 0;  // 数値:0
            this.shi2_max_uses = 0;     // 数値:0 
            this.shi2_current_uses = 0; // 数値:0
            this.shi2_path = 0;         // 文字列:null
            this.shi2_passwd = 0;       // 文字列:null
        }
    }

    //=========================================================================================
    // Win32API 関数参照
    //=========================================================================================
    /// <summary>
    /// 共有フォルダ作成
    /// </summary>
    /// <param name="servername">実行対象のリモートサーバー</param>
    /// <param name="level">情報レベル</param>
    /// <param name="buf">情報を保持しているバッファ</param>
    /// <param name="parm_err">エラーの発生場所を示すインデックス</param>
    /// <returns>
    /// ※エラー内容は上記の「Win32API 定数 - Win32API 戻り値」参照
    /// ERROR_ACCESS_DENIED
    /// ERROR_INVALID_LEVEL
    /// ERROR_INVALID_NAME
    /// ERROR_INVALID_PARAMETER
    /// NERR_DuplicateShare
    /// NERR_RedirectedPath
    /// NERR_UnknownDevDir
    /// </returns>
    /// <remarks>
    /// API定義
    /// NET_API_STATUS NetShareAdd(
    ///     LPWSTR servername, // 実行対象のリモートサーバー
    ///     DWORD level,       // 情報レベル
    ///     LPBYTE buf,        // 情報を保持しているバッファ
    ///     LPDWORD parm_err   // エラーの発生場所を示すインデックス
    /// );
    /// </remarks>
    [DllImport("Netapi32.dll", CharSet= CharSet.Unicode)]
    private static extern int NetShareAdd(
        string servername, int level, ref SHARE_INFO_2 buf, ref int parm_err);
    /// <summary>
    /// 共有フォルダ削除
    /// </summary>
    /// <param name="servername">実行対象のリモートサーバー</param>
    /// <param name="netname">削除対象の共有</param>
    /// <param name="reserved">予約済み</param>
    /// <returns>
    /// ※エラー内容は上記の「Win32API 定数 - Win32API 戻り値」参照
    /// ERROR_ACCESS_DENIED
    /// ERROR_INVALID_PARAMETER
    /// ERROR_NOT_ENOUGH_MEMORY
    /// NERR_NetNameNotFound
    /// </returns>
    /// <remarks>
    /// API定義
    /// NET_API_STATUS NetShareDel(
    ///     LPWSTR servername,  // 実行対象のリモートサーバー
    ///     LPWSTR netname,     // 削除対象の共有
    ///     DWORD reserved      // 予約済み
    /// );
    /// </remarks>
    [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
    private static extern int NetShareDel(string servername, string netname, int reserved);

    //=========================================================================================
    // 公開メソッド
    //=========================================================================================
    /// <summary>
    /// 共有フォルダ作成
    /// </summary>
    /// <param name="sServerName">サーバー名</param>
    /// <param name="sShareName">共有名</param>
    /// <param name="iMaxUses">同時共有できる最大ユーザ数</param>
    /// <param name="sComment">コメント</param>
    /// <param name="sPhysicsFolderPath">共有フォルダの物理フォルダパス</param>
    /// <param name="sPassword">パスワード</param>
    /// <exception cref="SystemException">共有フォルダ作成失敗</exception>
    public static void fnShareAdd(
        string sServerName, string sShareName, int iMaxUses, string sComment,
        string sPhysicsFolderPath, string sPassword)
    {
        SHARE_INFO_2 si2;
        //-------------------------------------------------------------------------------------
        // 共有フォルダの設定
        //-------------------------------------------------------------------------------------
        // 共有名
        GCHandle gchNetName = GCHandle.Alloc(sShareName, GCHandleType.Pinned);
        si2.shi2_netname = gchNetName.AddrOfPinnedObject().ToInt32();
        // 共有タイプ
        si2.shi2_type = STYPE_DISKETREE;
        // コメント
        GCHandle gchNetRemark = GCHandle.Alloc(sComment, GCHandleType.Pinned);
        si2.shi2_remark = gchNetRemark.AddrOfPinnedObject().ToInt32();
        // パーミッション
        si2.shi2_permissions = 0;
        // 最大接続数
        si2.shi2_max_uses = iMaxUses;
        // ?
        si2.shi2_current_uses = 0;
        // 共有フォルダパス
        GCHandle gchPath = GCHandle.Alloc(sPhysicsFolderPath, GCHandleType.Pinned);
        si2.shi2_path = gchPath.AddrOfPinnedObject().ToInt32();
        // パスワード
        GCHandle gchPassword = GCHandle.Alloc(sPassword, GCHandleType.Pinned);
        si2.shi2_passwd = gchPassword.AddrOfPinnedObject().ToInt32();
        //-------------------------------------------------------------------------------------
        // 共有フォルダ作成
        //-------------------------------------------------------------------------------------
        int iParamErr = 0;
        // 第二引数はSHARE_INFO_2構造体を使うことを示している(らしい)
        int iResult = NetShareAdd(sServerName, 2, ref si2, ref iParamErr);
        //-------------------------------------------------------------------------------------
        // GCHandle解放
        //-------------------------------------------------------------------------------------
        gchNetName.Free();
        gchNetRemark.Free();
        gchPath.Free();
        gchPassword.Free();
        //-------------------------------------------------------------------------------------
        // 戻り値判定
        //-------------------------------------------------------------------------------------
        switch (iResult)
        {
            case NERR_Success:
                return;
            case ERROR_ACCESS_DENIED:
                throw new SystemException(
                    "ユーザーには、要求した情報へのアクセス権がありません。");
            case ERROR_INVALID_LEVEL:
                throw new SystemException("level パラメータで指定した値が無効です。");
            case ERROR_INVALID_NAME:
                throw new SystemException("文字またはファイルシステム名が無効です。");
            case ERROR_INVALID_PARAMETER:
                throw new SystemException("指定されたパラメータは無効です。");
            case NERR_DuplicateShare:
                throw new SystemException(
                    "指定した共有名は、既にこのサーバーで使われています。");
            case NERR_RedirectedPath:
                throw new SystemException(
                    "リダイレクトされたリソースに対して、この操作は無効です。" +
                    "指定されたデバイス名は、共有リソースに割り当てられています。");
            case NERR_UnknownDevDir:
                throw new SystemException("デバイスまたはディレクトリが存在しません。 ");
            default:
                string sMsg = string.Format(
                    "不明なエラー。NetShareAdd戻り値:{0}、iParamErr:{1}",
                    iResult, iParamErr);
                throw new SystemException(sMsg);
        }
    }
    /// <summary>
    /// 共有フォルダ削除
    /// </summary>
    /// <param name="sServerName">サーバー名</param>
    /// <param name="sShareName">共有名</param>
    /// <exception cref="SystemException">共有フォルダ削除失敗</exception>
    public static void fnShareDel(string sServerName, string sShareName)
    {
        //-------------------------------------------------------------------------------------
        // 共有フォルダ削除
        //-------------------------------------------------------------------------------------
        int iResult = NetShareDel(sServerName, sShareName, 0);
        //-------------------------------------------------------------------------------------
        // 戻り値判定
        //-------------------------------------------------------------------------------------
        switch (iResult)
        {
            case NERR_Success:
                return;
            case ERROR_ACCESS_DENIED:
                throw new SystemException(
                    "ユーザーには、要求した情報へのアクセス権がありません。");
            case ERROR_INVALID_PARAMETER:
                throw new SystemException("指定されたパラメータは無効です。");
            case ERROR_NOT_ENOUGH_MEMORY:
                throw new SystemException("利用可能なメモリが不足しています。");
            case NERR_NetNameNotFound:
                throw new SystemException("指定された共有名が存在しません。");
            default:
                string sMsg = string.Format(
                    "不明なエラー。NetShareDel戻り値:{0}", iResult);
                throw new SystemException(sMsg);
        }
    }
}


■上記クラスの利用方法は以下の通り

using System;

class Program
{
    static void Main(string[] args)
    {
        //-------------------------------------------------------------------------------------
        // 共有フォルダ作成
        //-------------------------------------------------------------------------------------
        ShareFolderManager.fnShareAdd(
            null,                           // サーバ名(nullの場合、ローカルPCに作成)
            "アプリ設定した共有フォルダ",   // 共有名
            10,                             // 最大接続数(-1で無制限)
            "コメント文",                   // コメント
            @"C:\temp\share",               // 共有フォルダの物理パス(事前に用意すること)
            "");                            // パスワード
        //-------------------------------------------------------------------------------------
        // 作成確認などのため1分間待機
        //-------------------------------------------------------------------------------------
        System.Threading.Thread.Sleep(60000);
        //-------------------------------------------------------------------------------------
        // 共有フォルダ削除
        //-------------------------------------------------------------------------------------
        ShareFolderManager.fnShareDel(
            null,                           // サーバ名(nullの場合、ローカルPCに作成)
            "アプリ設定した共有フォルダ");  // 共有名
    }
}


本サンプルとは直接関係ないことだが、共有フォルダには「隠し共有フォルダ」という属性がある。
設定方法は簡単で共有名の最後に$をつければよい。


隠し共有フォルダ設定にするとエクスプローラから共有フォルダが表示されなくなる。


しかしプログラムやショートカットファイルやエクスプローラアドレスバーに[\\localhost\hidden_share$]のようにパス指定すれば、隠し共有フォルダにアクセスできる。


隠し共有フォルダは要件次第では知っておいて損のない機能だ。



C#でショートカットファイル作成

C#でショートカットファイル作成するサンプルを紹介する。
ショートカットファイルを作る為にWSH(Windows Scripting Hosting)のCOMオブジェクトの1つである 「WshShortcutクラスのインタフェースIWshShortcut」を利用する。



■IWshShortcutインタフェースのプロパティ/メソッド
IWshShortcutインタフェースには以下のプロパティ/メソッドがある。
  プロパティ/メソッド名 概要 備考
TargetPath リンク先 必須
Arguments コマンドライン引数 TargetPathプロパティのコマンドライン引数
WorkingDirectory 作業フォルダ 起動アプリケーションの作業フォルダ
Hotkey ショートカットキー -
実行時の大きさ WindowStyle 1:通常のウィンドウ、3:最大、7:最小
コメント Description -
アイコン IconLocation [アイコンファイル]または[アイコンリソースが格納されたexe,dllファイル+","+"リソースの順番"
保存(メソッド) Save() -

上記表の①~⑦は以下の図の番号の設定に該当する。


■プロジェクトに"Windows Script Host Object Model"の参照追加
①Viual Sutidoの[プロジェクト]メニュー→[参照の追加]メニューから[参照の追加]ダイアログを開く。


②[COM]タブを選択し、"Windows Script Host Object Model"を選択する。


※この操作はVisualStudio2008上で操作です。バージョン違いなどで操作手順やイメージが異なる場合がある。


サンプルソースは以下の通り。

using System;
using System.Runtime.InteropServices;
using IWshRuntimeLibrary;

namespace CreateShortcutFile
{
    /// <summary>
    /// ショートカットファイル作成サンプル
    /// </summary>
    /// <remarks>
    /// 参考:
    /// http://homepage2.nifty.com/pasocon/nyumon/12.html
    /// </remarks>
    class Program
    {
        /// <summary>
        /// ショートカットファイル作成[シンブル版]
        /// </summary>
        /// <param name="sShortcutFilePath">ショートカットファイルの保存先パス</param>
        /// <param name="sLinkPath">リンク先</param>
        private static void fnCreateShortcutFile(string sShortcutFilePath, string sLinkPath)
        {
            IWshShell shell = null;         // シェルオブジェクト
            IWshShortcut shortcut = null;   // ショートカットオブジェクト
            try
            {
                //---------------------------------------------------------------------------------
                // オブジェクト作成
                //---------------------------------------------------------------------------------
                // シェルオブジェクト作成
                shell = new WshShellClass();
                // ショートカットオブジェクト作成
                //---------------------------------------------------------------------------------
                // ショートカット プロパティ設定
                //---------------------------------------------------------------------------------
                shortcut = (IWshShortcut)shell.CreateShortcut(sShortcutFilePath);
                // ショートカットのリンク先を設定
                shortcut.TargetPath = sLinkPath;
                //---------------------------------------------------------------------------------
                // ショートカットファイル作成
                //---------------------------------------------------------------------------------
                shortcut.Save();
            }
            finally
            {
                //---------------------------------------------------------------------------------
                // COMオブジェクト解放
                //---------------------------------------------------------------------------------
                // ショートカットオブジェクトの解放
                if (shortcut != null) Marshal.ReleaseComObject(shortcut);
                // シェルオブジェクトの解放
                if (shell != null) Marshal.ReleaseComObject(shell);
            }
        }
        /// <summary>
        /// ショートカットファイル作成[全プロパティ版]
        /// </summary>
        /// <param name="sShortcutFilePath">ショートカットファイルの保存先パス</param>
        /// <param name="sTargetPath">リンク先</param>
        /// <param name="sArguments">TargetPathのコマンドライン引数</param>
        /// <param name="sWorkingDirectory">作業フォルダ</param>
        /// <param name="sHotkey">ショートカットキー</param>
        /// <param name="iWindowStyle">実行時の大きさ(1:通常のウィンドウ、3:最大、7:最小)</param>
        /// <param name="sDescription">コメント</param>
        /// <param name="sIconLocation">アイコン</param>
        private static void fnCreateShortcutFile(
            string sShortcutFilePath,
            string sTargetPath,
            string sArguments,
            string sWorkingDirectory,
            string sHotkey,
            int iWindowStyle,
            string sDescription,
            string sIconLocation)
        {
            IWshShell shell = null;         // シェルオブジェクト
            IWshShortcut shortcut = null;   // ショートカットオブジェクト
            try
            {
                //---------------------------------------------------------------------------------
                // オブジェクト作成
                //---------------------------------------------------------------------------------
                // シェルオブジェクト作成
                shell = new WshShellClass();
                // ショートカットオブジェクト作成
                shortcut = (IWshShortcut)shell.CreateShortcut(sShortcutFilePath);
                //---------------------------------------------------------------------------------
                // ショートカット プロパティ設定
                //---------------------------------------------------------------------------------
                // リンク先
                shortcut.TargetPath = sTargetPath;
                // TargetPathのコマンドライン引数
                shortcut.Arguments = sArguments;
                // 作業フォルダ
                shortcut.WorkingDirectory = sWorkingDirectory;
                // ショートカットキー
                shortcut.Hotkey = sHotkey;
                // 実行時の大きさ
                shortcut.WindowStyle = iWindowStyle;
                // コメント
                shortcut.Description = sDescription;
                // アイコン
                shortcut.IconLocation = sIconLocation;
                //---------------------------------------------------------------------------------
                // ショートカットファイル作成
                //---------------------------------------------------------------------------------
                shortcut.Save();
            }
            finally
            {
                //---------------------------------------------------------------------------------
                // COMオブジェクト解放
                //---------------------------------------------------------------------------------
                // ショートカットオブジェクトの解放
                if (shortcut != null) Marshal.ReleaseComObject(shortcut);
                // シェルオブジェクトの解放
                if (shell != null) Marshal.ReleaseComObject(shell);
            }
        }
        /// <summary>
        /// メイン関数
        /// </summary>
        /// <param name="args"></param>
        static void Main()
        {
            //-------------------------------------------------------------------------------------
            // ショートカットファイル作成[シンブル版]
            //-------------------------------------------------------------------------------------
            // Excelファイルのショートカットファイル作成
            fnCreateShortcutFile(
                @"c:\temp\simple_excel.lnk",    // ショートカットファイルパス
                @"c:\temp\excel.xls");          // リンク先ファイルパス
            // メモ帳のショートカットファイル作成
            fnCreateShortcutFile(
                @"c:\temp\simple_notepad.lnk",  // ショートカットファイルパス
                @"notepad.exe");                // 実行ファイル名
            // Yahoo! JAPANのURLショートカットファイル作成
            fnCreateShortcutFile(
                @"c:\temp\simple_yahoo.lnk",    // ショートカットファイルパス
                @"http://www.yahoo.co.jp");     // リンク先URL
            //-------------------------------------------------------------------------------------
            // ショートカットファイル作成[全プロパティ版]
            //-------------------------------------------------------------------------------------
            // 「メモ帳で指定ファイルを開く」ショートカットファイル作成
            fnCreateShortcutFile(
                @"c:\temp\full_notepad.lnk",    // ショートカットファイルパス
                "notepad.exe",                  // リンク先
                @"c:\temp\memo.txt",            // コマンドライン引数
                @"c:\temp",                     // 作業フォルダ
                "",                             // ショートカットキー                        
                3,                              // 実行時の大きさ(3:最大化)
                "メモ帳で開きます。",           // コメント
                @"C:\Windows\regedit.exe, 1");  // アイコン
            // 「IEでYahoo! JAPANを開く」ショートカットファイル作成
            fnCreateShortcutFile(
                @"c:\temp\full_ie_yahoo.lnk",                           // ショートカットファイルパス
                @"C:\Program Files\Internet Explorer\iexplore.exe",     // リンク先
                @"http://www.yahoo.co.jp",                              // コマンドライン引数
                "",                                                     // 作業フォルダ
                "",                                                     // ショートカットキー                        
                7,                                                      // 実行時の大きさ(7:最小化)
                "IEでYahoo! JAPANを開く",                               // コメント
                @"C:\Program Files\Internet Explorer\iexplore.exe,1");  // アイコン
        }
    }
}



C#でCSV・TSVファイル作成

C#でDataTableからCSVファイルまたはTSVファイルを作成してみた。



「データをカンマで区切り、改行で1レコード」という条件ででDataTableからCSVファイルを作るサンプルコード。

実行結果及びASP.NETソースコードの確認

//-------------------------------------------------------------------------------------
// データ作成
//-------------------------------------------------------------------------------------
DataTable dtData = new DataTable();
// 列作成
dtData.Columns.Add("colInt32", typeof(Int32));
dtData.Columns.Add("colInt64", typeof(Int64));
dtData.Columns.Add("colDouble", typeof(Double));
dtData.Columns.Add("colDecimal", typeof(Decimal));
dtData.Columns.Add("colNull", typeof(Int32));
dtData.Columns.Add("colDateTime", typeof(DateTime));
dtData.Columns.Add("colBoolean", typeof(Boolean));
dtData.Columns.Add("colString", typeof(String));
// データ投入
for (int i = 1; i <= 5; i++)
{
    DataRow dr = dtData.NewRow();
    dr["colInt32"] = i;
    dr["colInt64"] = i * i;
    dr["colDouble"] = 1.1 * i;
    dr["colDecimal"] = 10.1 * i;
    dr["colNull"] = DBNull.Value;
    dr["colDateTime"] = new DateTime(2010, 1, i);
    dr["colBoolean"] = (i % 2 == 0) ? true : false;
    dr["colString"] = string.Format("文字列{0}", (char)('A' + i - 1));
    dtData.Rows.Add(dr);
}
//-----------------------------------------------------------------------------------------
// CSVファイル作成
//-----------------------------------------------------------------------------------------
// 一時ファイル作成
string sTempFilePath = Path.GetTempFileName();
// CSVファイル書込
using (StreamWriter sw = new StreamWriter(sTempFilePath, false, Encoding.GetEncoding("Shift-JIS")))
{
    // 行列ループ
    for (int iRow = 0; iRow < dtData.Rows.Count; iRow++)
    {
        string sRecord = "";  // レコード
        // 1レコード作成
        for (int iCol = 0; iCol < dtData.Columns.Count; iCol++)
        {
            sRecord += string.Format("{0},", dtData.Rows[iRow][iCol]);
        }
        // 行末のカンマを削除
        sRecord = sRecord.Remove(sRecord.Length - 1, 1);
        // ファイル書込
        sw.WriteLine(sRecord);
    }
    // ファイルクローズ
    sw.Close();
}

上記ソースで作成されるCSVファイルの内容


大概は上記サンプルでも通用することが多いが、CSVとしては以下の問題がある。
  • データにカンマ、改行を含む文字列がある場合に正しく処理できない。
  • 空文字とnull文字の判定ができない。
  • Byte配列、Byte型、SByte型等の文字列変換が困難なデータ型を文字変換すると予期せぬ文字列を返す。

PS. DataTableに格納できる基本データ型
  • Boolean
  • Byte
  • Char
  • DateTime
  • Decimal
  • Double
  • Int16
  • Int32
  • Int64
  • SByte
  • Single
  • String
  • TimeSpan
  • UInt16
  • UInt32
  • UInt64
  • Byte[]
参考:DataColumn.DataType プロパティ

また使い勝手として以下が考えられる。
  • CSV(カンマ区切り)でなく、TSV(タブ区切り)を指定したい。
  • 文字コードはShift-JIS以外を指定したい。
  • 改行文字はCRLF・CR・LFのいずれかを指定したい。
  • ヘッダ行を出力有無を指定したい。
  • 最終行に改行文字が出力有無を指定したい。
  • Boolean型の出力書式は、True/Falseでなく1/0を指定したい。。
  • DateTime型の出力書式は「YYYY/MM/DD HH:mm:ss」「YYYYMMDD」等の時間書式を指定したい。


CSVファイルはRFC 4180で国際規格化されている。
しかしRFC 4180は現行仕様の追認形式をとっているため仕様が厳格な規格ではない。

Comma-Separated Values - Wikipediaを参考にRFC 4180をまとめると以下の通り。
  • ファイルは1つ以上のレコードからなる。レコードは改行で区切られる。最後のレコードの後には改行はあってもなくてもいい。
  • レコードは1つ以上の同じ個数のフィールドからなる。フィールドはコンマ「,」で区切られる。最後のフィールドの後にはコンマは付けない。
  • 1,2,となっている場合は3列目はnull文字を表す
  • ファイルの先頭には、オプションとして、通常のレコードと同一の書式の「ヘッダ行」があってもいい。ヘッダ行は、他のレコードと同じ個数のフィールドを持ち、フィールドの名称が書かれている。
  • フィールドは、ダブルクォート「"」で囲んでも囲まなくてもよい。
  • フィールドがコンマ、ダブルクォート、改行を含む場合は、かならずダブルクォートで囲む。また、フィールドに含まれるダブルクォートは2つ並べてエスケープする。


これまでの条件を満たし規格や使い勝手を考慮したDataTableからCSV/TSVファイルを作成するサンプルソース(DataTable2CharacterSeparatedValuesクラス)を記述する。

サンプルソースのpublicメソッドは以下の通り。
メソッド名 シグニチャ 概要
テキストデータファイル作成 DataTable2CharacterSeparatedValues.CreateFile
(
  DataTable dtData,
  string sDustFilePath,
  Option opt
)
CSV/TSVファイルの作成。
引数は以下の通り
dtData 出力対象のDataTable
sDustFilePath 出力ファイルパス
opt オプション(後述)

上記メソッドの第3引数のOptionクラスのフィールドは以下の通り。
フィールド シグニチャ 既定値 概要
文字コード Encoding enc Shift-JIS ファイルの文字コードを指定。
フィールド区切り文字 string sFiledSeparator カンマ(,) フィールド区切り文字を指定。
行区切り文字 string sLineBreak CRLF 行区切り文字を指定。
フィールド囲み enumFieldEnclosed enFieldEnclosed 特殊文字列を含む文字列フィールドだけ囲む フィールドをダブルクォーテーション(")で囲むか指定。
ヘッダ行の出力有無 bool bOutputHeader false ヘッダ行の出力するか指定。
ヘッダ行の列名は引数dtDataのDataColumns[n].ColumnName。
最終レコードに行区切り文字挿入 bool bLastRecordLineBreak true 最終レコードに行区切り文字を挿入するか指定。
Boolean型の出力書式 bool bBooleanTypeFormat true Boolean型の出力書式を指定。
trueの場合、True/Falseで出力。
falseの場合、1/0で出力。
DateTime型の出力書式 string sDateTimeTypeFormat yyyyMMdd HH:mm:ss DateTime型の出力書式を指定。
出力書式はDateTime.ToString()と同じ書式。

実行結果及びASP.NETソースコードの確認

using System;
using System.IO;
using System.Data;
using System.Text;

/// <summary>
/// DataTableからテキストファイル(CSV/TSV)作成クラス
/// </summary>
public class DataTable2CharacterSeparatedValues
{
    /// <summary>
    /// フィールド囲み
    /// </summary>
    public enum enumFieldEnclosed
    {
        None,                               // フィールドを囲まない
        AllField,                           // 全フィールドを囲む
        StringField,                        // 文字列フィールドだけ囲む
        ContainingSpecialCharactersField    // 特殊文字列を含む文字列フィールドだけ囲む
    }
    /// <summary>
    /// オプション
    /// </summary>
    public class Option
    {
        /// <summary>
        /// 文字コード
        /// </summary>
        public Encoding enc = Encoding.GetEncoding("Shift-JIS");
        /// <summary>
        /// フィールド区切り文字
        /// </summary>
        public string sFiledSeparator = ",";
        /// <summary>
        /// 行区切り文字
        /// </summary>
        public string sLineBreak = "\r\n";
        /// <summary>
        /// フィールド囲み
        /// </summary>
        public enumFieldEnclosed enFieldEnclosed = enumFieldEnclosed.ContainingSpecialCharactersField;
        /// <summary>
        /// ヘッダ行の出力有無
        /// </summary>
        public bool bOutputHeader = true;
        /// <summary>
        /// 最終レコードに行区切り文字挿入
        /// </summary>
        public bool bLastRecordLineBreak = true;
        /// <summary>
        /// Boolean型の出力書式(trueでTrue/False、falseで'1'/'0'と出力)
        /// </summary>
        public bool bBooleanTypeFormat = true;
        /// <summary>
        /// DateTime型の出力書式
        /// </summary>
        public string sDateTimeTypeFormat = "yyyyMMdd HH:mm:ss";
    }
    /// <summary>
    /// テキストデータファイル作成
    /// </summary>
    /// <param name="dtData">出力対象のDataTable</param>
    /// <param name="sDustFilePath">出力ファイルパス</param>
    /// <param name="opt">
    /// オプション(DataTable2TextFile.Optionクラス)
    /// enc                 : 文字コード            既定:Shift-JIS
    /// sFiledSeparator     : フィールド区切り      既定:カンマ(,)
    /// sLineBreak          : 行区切り              既定:CRLF
    /// enFieldEnclosed     : フィールド囲み        既定:特殊文字列を含む文字列フィールドだけ囲む
    /// bOutputHeader       : ヘッダ行の出力有無    既定:false
    /// bBooleanTypeFormat  : DateTime型の出力書式  既定:true(yyyyMMdd HH:mm:ss)
    /// sDateTimeTypeFormat : DateTime型の出力書式  既定:true(yyyyMMdd HH:mm:ss)
    /// </param>
    public static void CreateFile(DataTable dtData, string sDustFilePath, Option opt)
    {
        StringBuilder sbContents = new StringBuilder(1024); // ファイルコンテンツ
        string sFieldEnclosed;                              // フィールド囲み文字
        //-----------------------------------------------------------------------------------------
        // データ型チェック
        //-----------------------------------------------------------------------------------------
        foreach (DataColumn col in dtData.Columns)
        {
            // データ型取得
            Type type = col.DataType;
            // 配列型チェック(Byte[]向け)
            // ※バイト配列型は印字不可能な文字になるかもしれないので削除
            if (type.IsArray)
            {
                throw new ApplicationException("配列はフィールドに設定できません");
            }
            // バイト型(Byte/SByte)チェック
            // ※バイト型は印字不可能な文字になるかもしれないので削除
            if (type == typeof(Byte) || type == typeof(SByte))
            {
                throw new ApplicationException("バイト型はフィールドに設定できません");
            }
        }
        //-----------------------------------------------------------------------------------------
        // フィールド囲み文字
        //-----------------------------------------------------------------------------------------
        // 「全フィールドを囲む」の場合
        if (opt.enFieldEnclosed == enumFieldEnclosed.AllField)
        {
            sFieldEnclosed = "\"";
        }
        // 「フィールドを囲まない」
        // 「文字列フィールドだけ囲む」
        // 「特殊文字列を含む文字列フィールドだけ囲む」の場合
        else
        {
            sFieldEnclosed = "";
        }
        //-----------------------------------------------------------------------------------------
        // ヘッダ行作成
        //-----------------------------------------------------------------------------------------
        if (opt.bOutputHeader)
        {
            // 列名作成
            foreach (DataColumn col in dtData.Columns)
            {
                sbContents.Append(col.ColumnName);
                sbContents.Append(opt.sFiledSeparator);
            }
            // 最後のフィールド区切りを削除
            if (sbContents.Length > 0)
            {
                sbContents.Remove(sbContents.Length - opt.sFiledSeparator.Length, opt.sFiledSeparator.Length);
            }
            // 行区切り挿入
            sbContents.Append(opt.sLineBreak);
        }
        //-----------------------------------------------------------------------------------------
        // コンテンツ行作成
        //-----------------------------------------------------------------------------------------
        for (int iRow = 0; iRow < dtData.Rows.Count; iRow++)
        {
            // 列データ作成
            for (int iCol = 0; iCol < dtData.Columns.Count; iCol++)
            {
                // フィールドデータ取得
                object objField = dtData.Rows[iRow][iCol];
                // NULLの場合
                if (objField is DBNull)
                {
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 文字(String/Char)の場合
                else if (objField is String || objField is Char)
                {
                    string sField = objField.ToString();
                    switch(opt.enFieldEnclosed)
                    {
                        //「フィールドを囲まない」
                        case enumFieldEnclosed.None:
                            sbContents.Append(sField);
                            break;
                        //「全フィールドを囲む」の場合
                        case enumFieldEnclosed.AllField:
                            sField = sField.Replace("\"", "\"\"");
                            sbContents.AppendFormat("\"{0}\"", sField);
                            break;
                        //「文字列フィールドだけ囲む」の場合
                        case enumFieldEnclosed.StringField:
                            sField = sField.Replace("\"", "\"\"");
                            sbContents.AppendFormat("\"{0}\"", sField);
                            break;
                        //「特殊文字列を含む文字列フィールドだけ囲む」の場合
                        case enumFieldEnclosed.ContainingSpecialCharactersField:
                            sField = sField.Replace("\"", "\"\"");
                            if (sField.IndexOf(opt.sFiledSeparator) != -1)
                            {
                                sbContents.AppendFormat("\"{0}\"", sField);
                            }
                            else if (sField.IndexOf(opt.sLineBreak) != -1)
                            {
                                sbContents.AppendFormat("\"{0}\"", sField);
                            }
                            else if (sField.IndexOf("\"") != -1)
                            {
                                sbContents.AppendFormat("\"{0}\"", sField);
                            }
                            else
                            {
                                sbContents.AppendFormat("{0}", sField);
                            }
                            break;
                    }
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 真偽型(Boolean)の場合
                else if (objField is Boolean)
                {
                    string sValue;
                    // True/Falseで出力
                    if (opt.bBooleanTypeFormat)
                    {
                        sValue = ((bool)objField).ToString();
                    }
                    // 1/0で出力
                    else
                    {
                        sValue = ((bool)objField) ? "1" : "0";
                    }
                    sbContents.AppendFormat("{1}{0}{1}", sValue, sFieldEnclosed);
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 日時(DateTime)
                else if (objField is DateTime)
                {
                    string sFormat = "{1}{0:" + opt.sDateTimeTypeFormat + "}{1}";
                    sbContents.AppendFormat(sFormat, objField, sFieldEnclosed);
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 数値型(Decimal/Double/Int16/Int32/Int64/Single/UInt16/UInt32/UInt64)
                // 日時(TimeSpan)
                else
                {
                    sbContents.AppendFormat("{1}{0}{1}", objField, sFieldEnclosed);
                    sbContents.Append(opt.sFiledSeparator);
                }
            }
            // 最後のフィールド区切りを削除
            sbContents.Remove(sbContents.Length - opt.sFiledSeparator.Length, opt.sFiledSeparator.Length);
            // 行区切り挿入
            sbContents.Append(opt.sLineBreak);
        }
        // 最終レコードの行区切り文字を削除
        if (!opt.bLastRecordLineBreak && sbContents.Length > 0)
        {
            sbContents.Remove(sbContents.Length - opt.sLineBreak.Length, opt.sLineBreak.Length);
        }
        //-----------------------------------------------------------------------------------------
        // ファイル書込
        //-----------------------------------------------------------------------------------------
        using (StreamWriter sw = new StreamWriter(sDustFilePath, false, opt.enc))
        {
            sw.Write(sbContents.ToString());
            sw.Close();
        }
    }
}




using System;
using System.Data;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        //-------------------------------------------------------------------------------------
        // 表示用データ作成
        //-------------------------------------------------------------------------------------
        DataTable dtData = new DataTable();
        // 列作成
        dtData.Columns.Add("colInt32", typeof(Int32));
        dtData.Columns.Add("colInt64", typeof(Int64));
        dtData.Columns.Add("colDouble", typeof(Double));
        dtData.Columns.Add("colDecimal", typeof(Decimal));
        dtData.Columns.Add("colNull", typeof(Int32));
        dtData.Columns.Add("colDateTime", typeof(DateTime));
        dtData.Columns.Add("colBoolean", typeof(Boolean));
        dtData.Columns.Add("colString", typeof(String));
        dtData.Columns.Add("colSpecialString", typeof(String));
        // データ投入
        for (int i = 1; i <= 5; i++)
        {
            DataRow dr = dtData.NewRow();
            dr["colInt32"] = i;
            dr["colInt64"] = i * i;
            dr["colDouble"] = 1.1 * i;
            dr["colDecimal"] = 10.1 * i;
            dr["colNull"] = DBNull.Value;
            dr["colDateTime"] = new DateTime(2010, 1, i, 23, 59, 59);
            dr["colBoolean"] = (i % 2 == 0) ? true : false;
            dr["colString"] = string.Format("文字列{0}", (char)('A' + i - 1));
            if (i % 5 == 1)
            {
                dr["colSpecialString"] = ",";
            }
            else if (i % 5 == 2)
            {
                dr["colSpecialString"] = "\t";
            }
            else if (i % 5 == 3)
            {
                dr["colSpecialString"] = "\r\n";
            }
            else if (i % 5 == 4)
            {
                dr["colSpecialString"] = "\"";
            }
            else
            {
                dr["colSpecialString"] = "";
            }
            dtData.Rows.Add(dr);
        }
        //-------------------------------------------------
        // CSV/TSVファイル作成
        //-------------------------------------------------
        DataTable2CharacterSeparatedValues.Option opt = new DataTable2CharacterSeparatedValues.Option();
        // 文字コード
        opt.enc = Encoding.GetEncoding("Shift-JIS");
        // フィールド区切り(","・"\t"のいずれか)
        opt.sFiledSeparator = ",";
        // 行区切り("\r\n"・"\r"・"\n"のいずれか)
        opt.sLineBreak = "\r\n";
        // フィールド囲み(ダブルクォーテーション(")の囲み方を指定)
        opt.enFieldEnclosed = DataTable2CharacterSeparatedValues.enumFieldEnclosed.AllField;
        // ヘッダ行を作成
        opt.bOutputHeader = true;
        // 最終レコードの後ろを改行
        opt.bLastRecordLineBreak = true;
        // Boolean型の出力形式(true:True/False or false:1/0)
        opt.bBooleanTypeFormat = false;
        // DateTime型の出力書式
        opt.sDateTimeTypeFormat = "yyyy/MM/dd hh:mm:ss";
        // ファイル作成
        string sTempFilePath = @"c:\temp\test.csv";
        DataTable2CharacterSeparatedValues.CreateFile(dtData, sTempFilePath, opt);
    }
}


上記ソースで作成されるCSVファイルの内容


  • RFC 4180
  • Comma-Separated Values - Wikipe
  • DataColumn.DataType プロパティ

  • ASP.NET(C#)でWeb API中継用プロキシサーバを作ってみた

    ASP.NET(C#)でWeb API中継用プロキシサーバを作ってみた。
    ※本内容はWebAPI利用時に問題となるクロスドメイン対策のためのプロキシサーバ作成を目的としてます。
    ※WebAPIから取得したXML形式のデータをJSON変換に利用したい場合、別記事の Web APIのレスポンスXML形式データをJSONに変換を参照してほしい。


    ■最初に
    開発環境は以下の通り

    • Visual Studio 2008 SP1
    • .NET Framework 3.5 SP1
    • ASP.NET3.5 (ASP.NET Webサービス)

    ※本内容だけでは不正アクセスへの対応は不十分です。


    ■中継サーバ作成
    1.「ASP.NET Webサービス」プロジェクト作成


    フォルダ/ファイル構成


    2.web.configの変更
    web.configにwebServicesタグを加える。

    
    <configuration>
      <system.web>
        :
        :
        <!--  WebServiceアクセスを許可する。(*.asmxを使ったWebサービス通信) -->
        <webServices>
          <protocols>
            <add name="HttpPost" />
            <add name="HttpGet" />
          </protocols>
        </webServices>
      </system.web>
    </configuration>
    
    
    3.プロキシサーバ作成
    プロジェクトに最初から存在するService.csを変更。
    
    using System;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Web;
    using System.Web.Script.Services;
    using System.Web.Services;
    
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    // この Web サービスを、スクリプトから ASP.NET AJAX を使用して呼び出せるようにするには、次の行のコメントを解除します。 
    [System.Web.Script.Services.ScriptService]
    public class Service : System.Web.Services.WebService
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Service () {
    
            //デザインされたコンポーネントを使用する場合、次の行をコメントを解除してください 
            //InitializeComponent(); 
        }
        /// <summary>
        /// 外部サーバURLのREST呼び出し
        /// </summary>
        /// <param name="sUriEncodingURL">URIエンコードされた外部サーバURL</param>
        /// <remarks>
        /// クロスドメイン対策プロキシ<br />
        /// 常に安全対策を考慮すること<br />
        /// </remarks>
        [WebMethod]
        [ScriptMethod]
        public void fnCallUrl(string sUriEncodingURL)
        {
            HttpRequest req = HttpContext.Current.Request;
            HttpResponse res = HttpContext.Current.Response;
            //-----------------------------------------------------------------------------------------
            // 不正アクセス対策
            //-----------------------------------------------------------------------------------------
            // 別ホストサーバ経由のアクセスの場合は何もしない
            if (req.UrlReferrer.Authority != req.Url.Authority)
            {
                return;
            }
            // Basic認証、ログインパスワード、ワンタイムパスワードなど
            ;
            //-----------------------------------------------------------------------------------------
            // 外部サーバからデータを取り出す。
            //-----------------------------------------------------------------------------------------
            WebRequest wrGETURL = WebRequest.Create(sUriEncodingURL);
            WebProxy myProxy = new WebProxy("call_proxy", 80);
            myProxy.BypassProxyOnLocal = true;
            string sContent;
            using (Stream objStream = wrGETURL.GetResponse().GetResponseStream())
            using (StreamReader objReader = new StreamReader(objStream, Encoding.GetEncoding("UTF-8")))
            {
                sContent = objReader.ReadToEnd();
                objReader.Close();
                objStream.Close();
            }
            //-----------------------------------------------------------------------------------------
            // レスポンスにコンテンツ内容を書き出す
            //-----------------------------------------------------------------------------------------
            res.Clear();
            //res.AddHeader("Content-Type",~);
            //res.AddHeader("Pragma", "no-cache");
            res.Write(sContent);
        }
    }
    
    
    4.テスト用XML作成
    動作確認用にXMLFile.xmlを作成。
    内容は何でもよい。
    
    <?xml version="1.0" encoding="utf-8" ?>
    <address>
      <item>
        <country>日本</country>
        <city>東京</city>
      </item>
      <item>
        <country>日本</country>
        <city>大阪</city>
      </item>
      <item>
        <country>日本</country>
        <city>名古屋</city>
      </item>
    </address>
    
    
    5.テスト用HTML作成
    稼働確認用のHTMLファイルtest.htmを作成。
    非同期通信で中継プロキシにアクセス。
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>Proxyテスト</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <script type="text/javascript">
    
    /// <summary>
    /// 価格.com Webサービスのキー(貴方のIDを設定してください)
    /// </summary>
    var g_sKakakuApiKey = "XXXXXXXXXXXXXXXXXXXX";
    
    /// <summary>
    /// 非同期リクエスト
    /// <summary>
    function fnAjaxRequest(sUrl, fnCallback)
    {
        var res;
        //---------------------------------------------------------------------------------------------
        // 非同期リクエストオブジェクト作成
        //---------------------------------------------------------------------------------------------
        if (window.XMLHttpRequest) {
            res = new XMLHttpRequest();
        }
        else {
            try {
                res = new ActiveXObject("Msxml2.XMLHTTP");
            } catch(e)
            {
                res = new ActiveXObject("Microsoft.XMLHTTP");
            }
        }
        //---------------------------------------------------------------------------------------------
        // 非同期リクエスト
        //---------------------------------------------------------------------------------------------
        // リクエスト準備
        res.open("GET", sUrl, true);
        res.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
        // レスポンス呼出の準備
        res.onreadystatechange = function() {
            if (res.readyState == 4) {
                fnCallback(res);
            }
        }
        // リクエスト
        res.send(null);
    }
    /// <summary>
    /// プロキシサーバからの受信先メソッド
    /// <summary>
    function fnSendResponse(res)
    {
        var divResult = document.getElementById("divResult");
        divResult.innerText = res.responseText;
    }
    /// <summary>
    /// [プロキシサーバ通信]ボタン押下
    /// <summary>
    function evSendRequest()
    {
        var sVirtualUrl;
        var sCallUrl;
        //---------------------------------------------------------------------------------------------
        // サーバURLおよびフォルダ取得
        //---------------------------------------------------------------------------------------------
        sVirtualUrl = window.location.href;
        var i = sVirtualUrl.indexOf('/', "http://".length);
        i = sVirtualUrl.indexOf('/', i+1);
        sVirtualUrl = sVirtualUrl.substr(0, i+1);
        //---------------------------------------------------------------------------------------------
        // 呼び出し先URL取得
        //---------------------------------------------------------------------------------------------
        var selCallUrl = document.getElementById("selCallUrl");
        switch(selCallUrl.value)
        {
            // 価格.com WEBサービス(REST)
            case "kakaku":
                sCallUrl = "http://api.kakaku.com/WebAPI/ItemSearch/Ver1.0/ItemSearch.aspx";
                sCallUrl += "?Keyword=%83o%83C%83I&CategoryGroup=pc&ResultSet=medium&SortOrder=pricerank&PageNum=1";
                sCallUrl += "&ApiKey=" + g_sKakakuApiKey;
                break;
            // Yahoo!トップページ
            case "yahoo":
                sCallUrl = "http://yahoo.co.jp";
                break;
            // 自鯖XMLファイル
            case "xml":
                sCallUrl = sVirtualUrl + "XMLFile.xml";
                break;
        }
        //---------------------------------------------------------------------------------------------
        // 作成したプロキシサーバに非同期リクエスト
        //---------------------------------------------------------------------------------------------
        // プロキシサーバURL作成
        var sProxyUrl = sVirtualUrl + "Service.asmx/fnCallUrl?sUriEncodingURL=";
        // リクエストURL作成
        var sRequestUrl = sProxyUrl + encodeURIComponent(sCallUrl);
        // 非同期リクエスト
        fnAjaxRequest(sRequestUrl, fnSendResponse);
    }
    </script>
    </head>
    <body>
        <select id="selCallUrl">
            <option value="kakaku" selected="selected">価格.com WEBサービス(REST)</option>
            <option value="yahoo">Yahoo!トップページ</option>
            <option value="xml">自鯖XMLファイル</option>
        </select>
        <input type="button" onclick="evSendRequest();" value="プロキシ通信" />
        <div id="divResult" style="background-color:#f0f0f0;"></div>
    </body>
    </html>
    
    
    6.稼働確認
    ・価格.com Webサービス(REST)への中継結果

    ・Yahoo!トップページへの中継結果

    ・自分のサーバのXMLFile.xlsに中継した結果


    ■中継サーバ運用時の注意
    • セキュリティ(不正アクセスの温床になったり、踏み台にされないこと)
    • 文字コードはWebAPIでは主にUTF-8だがShift-JIS等の他の文字コードもあるので注意。


    ■参考URL


    ■関連ページ

    NET Frameworkアプリケーションを64bit OS上で32bitモードで動かす方法(その2)

    .NET Frameworkアプリケーションを64bit OS上で32bitモードで動かす方法の紹介。



    ■最初に
    前回はWindowsアプリケーションとASP.NETを32bitアプリケーションとして動かすための設定を書いてみた。
    しかし心情としては、例えライブラリが32ビットにしか対応していなくても64ビットOS上では64ビットアプリケーションで動かしたいのが本音だと思う。
    今回は折衷案として以下の方式を考えてみた。

    ①64bitで動く部分は64bitアプリケーションとして作成する。
    ②32bitしか対応していないライブラリ(dll,ActiveX等)を利用した処理を切り出し、32ビットアプリケーションとして作成する。
    ③64bitアプリケーションから32bitアプリケーションをプロセス起動する。処理に必要な値はコマンドライン引数かファイルで渡す。

    なお本ページの環境構成は以下の通り。
    ・Windows Server 2008 64bit
    ・Visual Studio 2008
    ・.NET Framework 3.5 SP1



    ■32ビットアプリケーション作成サンプル
    まず32ビットアプリケーションの作成だ。

    手順1
    新規プロジェクトを「コンソールアプリケーション」として作成。
    プロジェクト名はとりあえず「proc32」とする。


    手順2
    コマンドライン引数を取得するコードを記述する。
    ※このサンプルではMessageBox.Show()を利用するためプロジェクト参照から「System.Windows.Forms」を追加している。

    
    using System;
    using System.Windows.Forms;
    
    class CProc32
    {
        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                MessageBox.Show(args[0]);
            }
        }
    }
    
    

    手順3
    プロジェクトのプロパティから「出力の種類」を"Windowsアプリケーション"に設定。


    手順4
    「ビルド」タブを選択し、「プラットフォーム ターゲット」を"x86"に設定する。


    ■ASP.NET32ビットプリケーションを64ビットアプリケーションから起動サンプル
    今度は64bitアプリケーションの作成だ。
    「プラットフォーム ターゲット」の既定値は"Any CPU"のためOSに合わせてアプリケーション動作を32ビット・64ビットに切り替えるが、今回は強制的に64bitアプリとして作成する。

    手順1
    新規プロジェクトを「Windows フォーム アプリケーション」を作成。
    プロジェクト名はとりあえず「Win64」とする。


    手順2
    フォームにボタンを作成し、ボタンイベントに以下のソースを記述。
    ポイントは「Process.StartInfo.WindowStyleプロパティ」と「Process.WaitForExit()メソッド」だ。

    
    using System;
    using System.Diagnostics;
    using System.Windows.Forms;
    
    namespace Win64
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            /// <summary>
            /// [32bitアプリを起動]ボタン押下
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button1_Click(object sender, EventArgs e)
            {
                //-------------------------------------------------------------------------------------
                // 32bitアプリを起動
                //-------------------------------------------------------------------------------------
                Process ps = new Process();
                // プロセス起動引数設定
                ps.StartInfo.FileName = "proc32.exe";
                ps.StartInfo.Arguments = "\"ビット64アプリからから32ビットアプリを起動しました。\"";
                // プロセルのウィンドウは非表示
                ps.StartInfo.WindowStyle =ProcessWindowStyle.Hidden;
                // プロセス起動
                ps.Start();
                // プロセスが終了するまで最大10秒(=10 * 1000ミリ秒)待機
                ps.WaitForExit(10 * 1000);
                //-------------------------------------------------------------------------------------
                // 終了メッセージ
                //-------------------------------------------------------------------------------------
                MessageBox.Show("ボタン処理が終了");
            }
        }
    }
    
    

    手順3
    プロジェクトのプロパティから「プラットフォーム ターゲット」を"x64"に設定する。


    手順4
    先の手順で作成した「proc32.exe」を「Win64.exe」と同じフォルダに配置。


    手順5
    「Win64.exe」を起動しボタンを押すと「proc32.exe」内のMessagBox処理が呼び出される。


    手順6
    このときのタスクマネジャーは以下の通りだ。



    ■関連ページ


    ASP.NETでPDFファイルのファイル分割

    ASP.NET(C#)でPDFファイルのファイル分割を行うサンプルを紹介する。
    このサンプルではiText#(iTextSharp)というフリーライブラリーを利用する。


    ■iTextSharpの準備方法
    iTextSharpはSourceForgeここからダウンロードできる。
    必要な物は以下の通り。

    DLL 説明
    itextsharp.dll iTextSharpのDLL
    iTextAsian.dll 日本語リソースのDLL
    これらDLLファイルをASP.NETプロジェクトのBinフォルダに配置すればOK。
    詳しくはここを参照。


    ■ASP.NET(C#)サンプル
    利用サンプル概要は以下の通り。
    OnceMail Labから実行結果及び全ソースを確認できる。

    
    using System.IO;
    using iTextSharp.text;
    using iTextSharp.text.pdf;
    
     :
     :
     :
    
    /// <summary>
    /// PDF分割
    /// </summary>
    /// <param name="sSourceFilePath">入力ファイルパス</param>
    /// <param name="sDestFilePath1">出力ファイルパス1</param>
    /// <param name="sDestFilePath2">出力ファイルパス2</param>
    /// <param name="iSplitPageNumber">分割ページ番号</param>
    public void fnSplitPdf(string sSourceFilePath, string sDestFilePath1, string sDestFilePath2, int iSplitPageNumber)
    {
        PdfReader reader = null;
        try
        {
            // リーダー作成
            reader = new PdfReader(sSourceFilePath);
            // 1つめ分割ファイル作成
            fnCreateSplitPdfFile(reader, sDestFilePath1, 1, iSplitPageNumber - 1);
            // 2つめ分割ファイル作成
            fnCreateSplitPdfFile(reader, sDestFilePath2, iSplitPageNumber, reader.NumberOfPages);
        }
        finally
        {
            // 終了処理
            if (reader != null)
                reader.Close();
        }
    }
    /// <summary>
    /// 分割PDFファイル作成
    /// </summary>
    /// <param name="reader">リーダー</param>
    /// <param name="sDestFilePath">出力ファイルパス</param>
    /// <param name="iStartPageNumber">読込開始位置</param>
    /// <param name="iEndPageNumber">読込終了位置</param>
    private void fnCreateSplitPdfFile(PdfReader reader, string sDestFilePath, int iStartPageNumber, int iEndPageNumber)
    {
        Document doc = null;
        try
        {
            // ドキュメント作成
            doc = new Document(reader.GetPageSizeWithRotation(iStartPageNumber));
            // writer作成
            PdfWriter writer = PdfWriter.GetInstance(doc, new FileStream(sDestFilePath, FileMode.Create));
            // ドキュメントオープン
            doc.Open();
            // ContentByte作成
            PdfContentByte cb = writer.DirectContent;
            // readerから指定ページのPDFファイルをコピー→出力ドキュメントに出力
            for (int i = iStartPageNumber; i <= iEndPageNumber; i++)
            {
                // ページサイズ設定
                doc.SetPageSize(reader.GetPageSizeWithRotation(i));
                // ページ作成
                doc.NewPage();
                // ページ取得
                PdfImportedPage page = writer.GetImportedPage(reader, i);
                // ページ向きに合わせてページ追加
                int iRotation = reader.GetPageRotation(i);
                if (iRotation == 90 || iRotation == 270)
                {
                    cb.AddTemplate(page, 0, -1f, 1f, 0, 0, reader.GetPageSizeWithRotation(i).Height);
                }
                else
                {
                    cb.AddTemplate(page, 1f, 0, 0, 1f, 0, 0);
                }
            }
        }
        finally
        {
            // オブジェクト破棄
            if (doc != null)
                doc.Close();
        }
    }
    
    


    ■関連ページ

    C#で7zの圧縮・解凍

    C#で7z(セブンゼット)の圧縮・解凍を行うサンプルを紹介する。
    本サンプルでは7-zip32.dllというフリーライブラリー(C言語DLL)を利用。


    ■DLLの準備方法
    7-zip32.dllは統合アーカイバプロジェクトからダウンロードできる。

    DLL 説明 備考
    7-zip32.dll 7z圧縮・解凍用ライブラリ。 GNU Lesser General Public License準拠
    7-zip32.dllファイルをC:\Windows等パスの通ったフォルダに配置すればOK。


    ■C#サンプル
    7-zip32.dllの利用メソッドは以下の通り。
    C言語ライブラリのためDllImport宣言が必要。

    
    ■7z圧縮・解凍
    [DllImport("7-zip32.dll", CharSet = CharSet.Ansi)]
    int SevenZip(
        IntPtr hwnd,            // ウィンドウハンドル
        string szCmdLine,       // コマンドライン
        StringBuilder szOutput, // 処理結果文字列
        int dwSize);            // 引数szOutputの文字列サイズ
    
    
    第2引数のコマンドラインの様式は7-zip32.dll付属の「7-zipCMD.txt」によるとこんな感じ。

    <command> [<switch>...] <archive_name> [<base_dir>\] [<arguments>...]
    
    command      : コマンド。
    switch       : スイッチ。(<'/' | '-'><switch_characters>[<option>])
    archive_name : 基本となる書庫名。
    base_dir     : 基準ディレクトリ。\ で終わる必要があります。
    arguments    : switch、wildcard、filename、list_file。
    
    wildcard     : ワイルドカード。
    filename     : ファイル名。
    list_file    : ファイルのリストを書いたファイル。(@{filename})
    
    [ ] は省略可能。
    ... は複数指定可能。
    

    本サンプル内で利用したコマンドラインは以下の通り。
    コマンドライン 説明
    a -t7z -hide -mmt=on -y archive.7z <ファイルパス1> <ファイルパス2>... ファイルパス1、ファイルパス2...をarchive.7zファイルに圧縮。
    a -t7z -hide -mmt=on -y -p{パスワード} archive.7z <ファイルパス1> <ファイルパス2>... ファイルパス1、ファイルパス2...をarchive.7zファイルにパスワード指定で圧縮。
    a -t7z -hide -mmt=on -y -r archive.7z <フォルダパス> サブフォルダごとarchive.7zファイルに圧縮。
    a -t7z -hide -mmt=on -y -r -p{パスワード} archive.7z <フォルダパス> サブフォルダごとパスワード指定でarchive.7zファイルに圧縮。
    x -aoa -hide -y -r archive.7z -o{出力フォルダパス}\* archive.7zファイルを圧縮時と同じフォルダ構成で出力フォルダパスに解凍。
    x -aoa -hide -y -r archive.7z -o{dust_folder}\* -p{パスワード} パスワードの掛ったarchive.7zファイルを圧縮時と同じフォルダ構成で出力フォルダパスに解凍。

    コマンド
    a:書庫にファイルを追加
    x:フォルダ付きで書庫を解凍。

    スイッチ
    -t7z:7形式を指定
    -hide:処理状況ダイアログ表示の抑止
    -mmt=on:マルチスレッドモードの設定
    -y:全ての質問に yes を仮定
    -r:サブディレクトリの再帰的検索
    -aoa:確認なしで上書き
    -p{password}:パスワードの設定
    -o{dir_path}:出力先ディレクトリの設定

    詳細についてはDLL付属の7-zipCMD.txt、あるいはサンプルソース内のコメントを参照。


    サンプルソースの7z操作クラス(SevenZManager)の公開機能は以下の通り
    機能概要 メソッド名
    7z圧縮[複数ファイル] fnCompressFiles()
    7z圧縮[複数ファイル・パスワード指定] fnCompressFilesWithPassword()
    7z圧縮[フォルダ指定] fnCompressFolder()
    7z圧縮[フォルダ指定・パスワード指定] fnCompressFolderWithPassword()
    7z解凍 fnExtract()
    7z解凍[パスワード指定] fnExtractWithPassword()
    Oncemail LabからASP.NETから利用したときの実行結果及びソースを確認できる。
    ※Oncemail Labでは「7z圧縮[複数ファイル]」サンプルのみ実行可能。

    
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.Text;
    
    /// <summary>
    /// 7z操作クラス(7-zip32.dllのラッパークラス)
    /// </summary>
    /// <remarks>
    /// 【注意】
    /// 7-zip32.dllをC:\Windows\などパスの通った場所に保存していること!!
    ///  DLL取得元:統合アーカイバプロジェクト
    /// http://www.madobe.net/archiver/index.html
    /// </remarks>
    public static class SevenZManager
    {
        /// <summary>
        /// [DLL Import] SevenZipのコマンドラインメソッド
        /// </summary>
        /// <param name="hwnd">ウィンドウハンドル(=0)</param>
        /// <param name="szCmdLine">コマンドライン</param>
        /// <param name="szOutput">実行結果文字列</param>
        /// <param name="dwSize">実行結果文字列格納サイズ</param>
        /// <returns>
        /// 0:正常、0以外:異常終了
        /// </returns>
        [DllImport("7-zip32.dll", CharSet = CharSet.Ansi)]
        private static extern int SevenZip(IntPtr hwnd, string szCmdLine, StringBuilder szOutput, int dwSize);
    
        /// <summary>
        /// 7z圧縮[複数ファイル]
        /// </summary>
        /// <param name="aryFilePath">圧縮対象ファイル一覧(フルパス指定)</param>
        /// <param name="s7zFilePath">7zファイル名</param>
        public static void fnCompressFiles(List<string> aryFilePath, string s7zFilePath)
        {
            lock (typeof(SevenZManager))
            {
                StringBuilder sbCmdLine = new StringBuilder(1024);   // コマンドライン文字列
                StringBuilder sbOutput = new StringBuilder(1024);    // 7-zip32.dll出力文字
                //---------------------------------------------------------------------------------
                // コマンドライン文字列の作成
                //---------------------------------------------------------------------------------
                // a:書庫にファイルを追加
                // -t7z:7形式を指定
                // -hide:処理状況ダイアログ表示の抑止
                // -mmt=on:マルチスレッドモードの設定
                // -y:全ての質問に yes を仮定
                sbCmdLine.AppendFormat("a -t7z -hide -mmt=on -y \"{0}\"", s7zFilePath);
                // 圧縮対象ファイルをコマンドライン化
                foreach (string sFilePath in aryFilePath)
                {
                    sbCmdLine.AppendFormat(" \"{0}\"", sFilePath);
                }
                string sCmdLine = sbCmdLine.ToString();
                //---------------------------------------------------------------------------------
                // 圧縮実行
                //---------------------------------------------------------------------------------
                int iSevenZipRtn = SevenZip((IntPtr)0, sCmdLine, sbOutput, sbOutput.Capacity);
                //---------------------------------------------------------------------------------
                // 成功判定
                //---------------------------------------------------------------------------------
                fnCheckProc(iSevenZipRtn, sbOutput);
            }
        }
        /// <summary>
        /// ZIP圧縮[複数ファイル・パスワード指定]
        /// </summary>
        /// <param name="aryFilePath">圧縮対象ファイル一覧(フルパス指定)</param>
        /// <param name="s7zFilePath">7zファイル名</param>
        /// <param name="sPassword">パスワード(半角英数字)</param>
        public static void fnCompressFilesWithPassword(List<string> aryFilePath, string s7zFilePath, string sPassword)
        {
            lock (typeof(SevenZManager))
            {
                StringBuilder sbCmdLine = new StringBuilder(1024);   // コマンドライン文字列
                StringBuilder sbOutput = new StringBuilder(1024);    // 7-zip32.dll出力文字
                //---------------------------------------------------------------------------------
                // コマンドライン文字列の作成
                //---------------------------------------------------------------------------------
                // a:書庫にファイルを追加
                // -t7z:7形式を指定
                // -hide:処理状況ダイアログ表示の抑止
                // -mmt=on:マルチスレッドモードの設定
                // -y:全ての質問に yes を仮定
                // -p{password}:パスワードの設定
                sbCmdLine.AppendFormat(
                    "a -t7z -hide -mmt=on -y -p{1} \"{0}\"", s7zFilePath, sPassword);
                // 圧縮対象ファイルをコマンドライン化
                foreach (string sFilePath in aryFilePath)
                {
                    sbCmdLine.AppendFormat(" \"{0}\"", sFilePath);
                }
                string sCmdLine = sbCmdLine.ToString();
                //---------------------------------------------------------------------------------
                // 圧縮実行
                //---------------------------------------------------------------------------------
                int iSevenZipRtn = SevenZip((IntPtr)0, sCmdLine, sbOutput, sbOutput.Capacity);
                //---------------------------------------------------------------------------------
                // 成功判定
                //---------------------------------------------------------------------------------
                fnCheckProc(iSevenZipRtn, sbOutput);
            }
        }
        /// <summary>
        /// 7z圧縮[フォルダ指定]
        /// </summary>
        /// <param name="sFolderPath">圧縮対象フォルダ(フルパス指定)</param>
        /// <param name="s7zFilePath">7zファイル名</param>
        public static void fnCompressFolder(string sFolderPath, string s7zFilePath)
        {
            lock (typeof(SevenZManager))
            {
                StringBuilder sbOutput = new StringBuilder(1024);   // 7-zip32.dll出力文字
                //---------------------------------------------------------------------------------
                // sFolderPathの最後が\の場合、\を取り払う
                //---------------------------------------------------------------------------------
                while (sFolderPath[sFolderPath.Length - 1] == '\\')
                {
                    sFolderPath = sFolderPath.Substring(0, sFolderPath.Length - 1);
                }
                //---------------------------------------------------------------------------------
                // コマンドライン文字列の作成
                //---------------------------------------------------------------------------------
                // a:書庫にファイルを追加
                // -t7z:7形式を指定
                // -hide:処理状況ダイアログ表示の抑止
                // -mmt=on:マルチスレッドモードの設定
                // -y:全ての質問に yes を仮定
                // -r:サブディレクトリの再帰的検索
                string sCmdLine = string.Format(
                    "a -t7z -hide -mmt=on -y -r \"{0}\" \"{1}\\*\"", s7zFilePath, sFolderPath);
                //---------------------------------------------------------------------------------
                // 圧縮実行
                //---------------------------------------------------------------------------------
                int iSevenZipRtn = SevenZip((IntPtr)0, sCmdLine, sbOutput, sbOutput.Capacity);
                //---------------------------------------------------------------------------------
                // 成功判定
                //---------------------------------------------------------------------------------
                fnCheckProc(iSevenZipRtn, sbOutput);
            }
        }
        /// <summary>
        /// 7z圧縮[フォルダ指定・パスワード指定]
        /// </summary>
        /// <param name="sFolderPath">圧縮対象フォルダ(フルパス指定)</param>
        /// <param name="s7zFilePath">7zファイル名</param>
        /// <param name="sPassword">パスワード(半角英数字)</param>
        public static void fnCompressFolderWithPassword(string sFolderPath, string s7zFilePath, string sPassword)
        {
            lock (typeof(SevenZManager))
            {
                StringBuilder sbOutput = new StringBuilder(1024);   // 7-zip32.dll出力文字
                //---------------------------------------------------------------------------------
                // sFolderPathの最後が\の場合、\を取り払う
                //---------------------------------------------------------------------------------
                while (sFolderPath[sFolderPath.Length - 1] == '\\')
                {
                    sFolderPath = sFolderPath.Substring(0, sFolderPath.Length - 1);
                }
                //---------------------------------------------------------------------------------
                // コマンドライン文字列の作成
                //---------------------------------------------------------------------------------
                // a:書庫にファイルを追加
                // -t7z:7形式を指定
                // -hide:処理状況ダイアログ表示の抑止
                // -mmt=on:マルチスレッドモードの設定
                // -y:全ての質問に yes を仮定
                // -r:サブディレクトリの再帰的検索
                // -p{password}:パスワードの設定
                string sCmdLine = string.Format(
                    "a -t7z -hide -mmt=on -y -r -p{2} \"{0}\" \"{1}\\*\"",
                    s7zFilePath, sFolderPath, sPassword);
                //---------------------------------------------------------------------------------
                // 圧縮実行
                //---------------------------------------------------------------------------------
                int iSevenZipRtn = SevenZip((IntPtr)0, sCmdLine, sbOutput, sbOutput.Capacity);
                //---------------------------------------------------------------------------------
                // 成功判定
                //---------------------------------------------------------------------------------
                fnCheckProc(iSevenZipRtn, sbOutput);
            }
        }
        /// <summary>
        /// 7z解凍
        /// </summary>
        /// <param name="s7zFilePath">7zファイル名</param>
        /// <param name="sDustFolder">出力先フォルダ</param>
        public static void fnExtract(string s7zFilePath, string sDustFolder)
        {
            lock (typeof(SevenZManager))
            {
                StringBuilder sbOutput = new StringBuilder(1024);   // 7-zip32.dll出力文字
                //---------------------------------------------------------------------------------
                // sDustFolderの最後が\の場合、\を取り払う
                //---------------------------------------------------------------------------------
                while (sDustFolder[sDustFolder.Length - 1] == '\\')
                {
                    sDustFolder = sDustFolder.Substring(0, sDustFolder.Length - 1);
                }
                ////---------------------------------------------------------------------------------
                //// 出力先フォルダが存在しなければ作成
                ////---------------------------------------------------------------------------------
                //if (!Directory.Exists(sDustFolder))
                //{
                //    Directory.CreateDirectory(sDustFolder);
                //}
                //---------------------------------------------------------------------------------
                // コマンドライン文字列の作成
                //---------------------------------------------------------------------------------
                // x:解凍
                // -aoa:確認なしで上書き
                // -hide:処理状況ダイアログ表示の抑止
                // -y:全ての質問に yes を仮定
                // -r:サブディレクトリの再帰的検索
                // -o{dir_path}:出力先ディレクトリの設定
                string sCmdLine = string.Format(
                    "x -aoa -hide -y -r \"{0}\" -o\"{1}\"\\*", s7zFilePath, sDustFolder);
                //---------------------------------------------------------------------------------
                // 解凍実行
                //---------------------------------------------------------------------------------
                int iSevenZipRtn = SevenZip((IntPtr)0, sCmdLine, sbOutput, sbOutput.Capacity);
                //---------------------------------------------------------------------------------
                // 成功判定
                //---------------------------------------------------------------------------------
                fnCheckProc(iSevenZipRtn, sbOutput);
            }
        }
        /// <summary>
        /// 7z解凍[パスワード指定]
        /// </summary>
        /// <param name="s7zFilePath">7zファイル名</param>
        /// <param name="sDustFolder">出力先フォルダ</param>
        /// <param name="sPassword">パスワード(半角英数字)</param>
        public static void fnExtractWithPassword(string s7zFilePath, string sDustFolder, string sPassword)
        {
            lock (typeof(SevenZManager))
            {
                StringBuilder sbOutput = new StringBuilder(1024);   // 7-zip32.dll出力文字
                //---------------------------------------------------------------------------------
                // sDustFolderの最後が\の場合、\を取り払う
                //---------------------------------------------------------------------------------
                while (sDustFolder[sDustFolder.Length - 1] == '\\')
                {
                    sDustFolder = sDustFolder.Substring(0, sDustFolder.Length - 1);
                }
                //---------------------------------------------------------------------------------
                // コマンドライン文字列の作成
                //---------------------------------------------------------------------------------
                // x:解凍
                // -aoa:確認なしで上書き
                // -hide:処理状況ダイアログ表示の抑止
                // -y:全ての質問に yes を仮定
                // -r:サブディレクトリの再帰的検索
                // -o{dir_path}:出力先ディレクトリの設定
                // -p{password}:パスワードの設定
                string sCmdLine = string.Format(
                    "x -aoa -hide -y -r \"{0}\" -o\"{1}\"\\* -p{2}",
                    s7zFilePath, sDustFolder, sPassword);
                //---------------------------------------------------------------------------------
                // 解凍実行
                //---------------------------------------------------------------------------------
                int iSevenZipRtn = SevenZip((IntPtr)0, sCmdLine, sbOutput, sbOutput.Capacity);
                //---------------------------------------------------------------------------------
                // 成功判定
                //---------------------------------------------------------------------------------
                fnCheckProc(iSevenZipRtn, sbOutput);
            }
        }
        /// <summary>
        /// SevenZipメソッド成功判定
        /// </summary>
        /// <param name="iSevenZipRtn">SevenZipメソッドの戻り値</param>
        /// <param name="sbLzhOutputString">SevenZipメソッドの第3引数</param>
        private static void fnCheckProc(int iSevenZipRtn, StringBuilder sbLzhOutputString)
        {
            //-------------------------------------------------------------------------------------
            // メソッドの戻り値=0なら正常終了
            //-------------------------------------------------------------------------------------
            if (iSevenZipRtn == 0)
                return;
            //-------------------------------------------------------------------------------------
            // 例外スロー
            //-------------------------------------------------------------------------------------
            string sMsg = string.Format(
                "7z圧縮/解凍処理に失敗:\nエラーコード={0}:\n{1}", iSevenZipRtn, sbLzhOutputString);
            throw new ApplicationException(sMsg);
        }
    }
    
    

    
    using System;
    using System.IO;
    using System.Collections.Generic;
    using System.Text;
    
    class Program
    {
        static void Main(string[] args)
        {
            //-------------------------------------------------------------------------------------
            // ファイル圧縮
            //-------------------------------------------------------------------------------------
            List<string> aryFiles1 = new List<string>();
            aryFiles1.Add(@"c:\temp\file1.gif");
            aryFiles1.Add(@"c:\temp\file1.txt");
            string s7zFilePath1 = 
                string.Format(@"c:\temp\{0:yyyyMMddHHmmss}files.7z", DateTime.Now);
            SevenZManager.fnCompressFiles(aryFiles1, s7zFilePath1);
            //-------------------------------------------------------------------------------------
            // ファイル圧縮(パスワード付)
            //-------------------------------------------------------------------------------------
            List<string> aryFiles2 = new List<string>();
            aryFiles2.Add(@"c:\temp\file2.png");
            aryFiles2.Add(@"c:\temp\file2.xls");
            string s7zFilePath2 =
                string.Format(@"c:\temp\{0:yyyyMMddHHmmss}filesP.7z", DateTime.Now);
            string sPassword2 = "abc";
            SevenZManager.fnCompressFilesWithPassword(aryFiles2, s7zFilePath2, sPassword2);
            //-------------------------------------------------------------------------------------
            // フォルダ圧縮
            //-------------------------------------------------------------------------------------
            string sFolder3 = @"c:\temp\arc";
            string s7zFilePath3 =
                string.Format(@"c:\temp\{0:yyyyMMddHHmmss}folders.7z", DateTime.Now);
            SevenZManager.fnCompressFolder(sFolder3, s7zFilePath3);
            //-------------------------------------------------------------------------------------
            // フォルダ圧縮(パスワード付)
            //-------------------------------------------------------------------------------------
            string sFolder4 = @"c:\temp\arc";
            string s7zFilePath4 =
                string.Format(@"c:\temp\{0:yyyyMMddHHmmss}foldersP.7z", DateTime.Now);
            string sPassword4 = "123";
            SevenZManager.fnCompressFolderWithPassword(sFolder4, s7zFilePath4, sPassword4);
            //-------------------------------------------------------------------------------------
            // 解凍
            //-------------------------------------------------------------------------------------
            string sDustDir5 = @"c:\temp\7zDust";
            SevenZManager.fnExtract(s7zFilePath1, sDustDir5);
            //-------------------------------------------------------------------------------------
            // 解凍(パスワード付)
            //-------------------------------------------------------------------------------------
            string sDustDir6 = @"c:\temp\7zDustP";
            SevenZManager.fnExtractWithPassword(s7zFilePath2, sDustDir6, sPassword2);
        }
    }
    
    

    ■注意点
    7-zip32.dllを使う際に気がついた注意点は以下の通り。

    • 7-zip32.dll利用時にはGNU Lesser General Public Licenseについて良く調べること。
    • 64BitOSで動かす際にはアプリケーションを32Bitモードで動かさないとDLLリンクでエラーになる。(.NETアプリのデフォルトはAnyCPUなので設定変更しないとアウト)

    7zについては7-Zip - Wikipediaおよび7z - Wikipediaを参照。
    GNU Lesser General Public License(LGPL)についてはGNU General Public License - WikipediaおよびGNU Lesser General Public License - Wikipediaを参照。

    64bitOSで動かしたい場合、NET Frameworkアプリケーションを64bit OS上で32bitモードで動かす方法を参照。

    ■クレジット
    念のため...

    • 7-zip ファイルの圧縮、解凍の基本部分の著作権は Igor Pavlov 氏にあります。
    • 7z 圧縮方式の BZip2 アルゴリズムはJulian Seward 氏が作成し、 PPMD アルゴリズムは Dmitry Shkarin 氏が作成しています。
    • 7-zip32.dll つまり統合アーカイバ仕様の DLL 作成部分に関しては秋田 稔 氏が著作権を保有します。
    • 本ページサンプルソースはページ管理者が著作権を保有します。


    ■関連ページ

    C#でCAPCHA画像作成

    C#でCAPCHA(キャプチャ)画像を作成する方法を紹介する。



    ※本サンプルは、CodeProject: CAPTCHA Image. Free source code and programming helpのサンプルソースをベースに作成している。
    ※本サンプルではCAPCHAに表示する文字は固定の値を設定している。 ランダムな文字列を利用する場合、C#でパスワード文字列作成も併せて検討してほしい。


    ■C#サンプル
    サンプルソース「キャプチャジェネレータクラス(CaptchaGenerator)」の公開機能は以下の通り

    No. 機能概要 メソッド名
    1 イメージの保存(Stream出力) fnSaveImage()
    2 イメージの保存(ファイル出力) fnSaveImage()

    本ソースはOncemail LabからASP.NETから利用したときの実行結果が確認できる。

    
    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.IO;
    
    /// <summary>
    /// CAPCHAジェネレータ
    /// </summary>
    public static class CaptchaGenerator
    {
        /// <summary>
        /// フォントファミリー名のデフォルト値
        /// </summary>
        const string CST_DEFALUT_FONT_FAMILY_NAME = "MS ゴシック";
    
        /// <summary>
        /// イメージの保存
        /// </summary>
        /// <param name="sText"></param>
        /// <param name="sText">テキスト</param>
        /// <param name="iWidth">幅</param>
        /// <param name="iHeight">高さ</param>
        /// <param name="imgFormat">イメージタイプ</param>
        public static void fnSaveImage(Stream stream, string sText, int iWidth, int iHeight, ImageFormat imgFormat)
        {
            // 枠作成
            Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
            // CAPCHA作成
            using (Bitmap bmpImage = fnGenerateImage(sText, rect, CST_DEFALUT_FONT_FAMILY_NAME))
            {
                bmpImage.Save(stream, imgFormat);
            }
        }    
        /// <summary>
        /// イメージの保存
        /// </summary>
        /// <param name="sOutputFilePath">出力ファイルパス</param>
        /// <param name="sText">テキスト</param>
        /// <param name="iWidth">幅</param>
        /// <param name="iHeight">高さ</param>
        public static void fnSaveImage(string sOutputFilePath, string sText, int iWidth, int iHeight)
        {
            ImageFormat imgFormat = ImageFormat.Jpeg;
            // イメージフォーマットの作成
            switch (Path.GetExtension(sOutputFilePath).ToLower())
            {
                case ".bmp": imgFormat = ImageFormat.Bmp; break;
                case ".gif": imgFormat = ImageFormat.Gif; break;
                case ".png": imgFormat = ImageFormat.Png; break;
            }
            // 枠作成
            Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
            // CAPCHA作成
            using (FileStream fs = new FileStream(sOutputFilePath, FileMode.CreateNew))
            using (Bitmap bmpImage = fnGenerateImage(sText, rect, CST_DEFALUT_FONT_FAMILY_NAME))
            {
                bmpImage.Save(fs, imgFormat);
                fs.Flush();
                fs.Close();
            }
        }
        /// <summary>
        /// CAPTCHAイメージ作成
        /// </summary>
        /// <param name="sText">CAPCHAテキスト</param>
        /// <param name="rect">枠サイズ</param>
        /// <param name="sFamilyName">フォント名</param>
        /// <returns>Bitmapオブジェクト</returns>
        private static Bitmap fnGenerateImage(string sText, Rectangle rect, string sFamilyName)
        {
            Random rnd = new Random();                          // 乱数オブジェクト
            HatchStyle hsBackground = HatchStyle.SmallConfetti; // 背景ハッチパターン
            HatchStyle hsText = HatchStyle.LargeConfetti;       // テキストハッチパターン
            Color colBackgroundForeColor = Color.White;         // 背景色
            Color colTextForeColor = Color.White;               // テキスト色
            //---------------------------------------------------------------------------------
            // ランダムなハッチパターンを取得
            //---------------------------------------------------------------------------------
            string[] aryHatchStyleName = Enum.GetNames(typeof(HatchStyle));
            // 背景ハッチパターン取得
            int iHatchStyleIndex = rnd.Next(aryHatchStyleName.Length);
            hsBackground = (HatchStyle)Enum.Parse(typeof(HatchStyle), aryHatchStyleName[iHatchStyleIndex]);
            // テキストハッチパターン取得
            iHatchStyleIndex = rnd.Next(aryHatchStyleName.Length);
            hsText = (HatchStyle)Enum.Parse(typeof(HatchStyle), aryHatchStyleName[iHatchStyleIndex]);
            // 背景色ハッチパターンの前景色取得
            int iRed, iGreen, iBlue;
            iRed = 0xFF - rnd.Next(2);
            iGreen = 0xFF - rnd.Next(2);
            iBlue = 0xFF - rnd.Next(2);
            colBackgroundForeColor = Color.FromArgb(iRed, iGreen, iBlue);
            // テキストハッチパターンの前景色取得
            do
            {
                iRed = rnd.Next(0xFF);
                iGreen = rnd.Next(0xFF);
                iBlue = rnd.Next(0xFF);
                colTextForeColor = Color.FromArgb(iRed, iGreen, iBlue);
            } while (iRed > 0xD0 && iGreen > 0xD0 && iBlue > 0xD0);
            //---------------------------------------------------------------------------------
            // Bitmapイメージ作成
            //---------------------------------------------------------------------------------
            Bitmap bitmap = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
            // グラフィックオブジェクト
            using (Graphics g = Graphics.FromImage(bitmap))
            // 背景ハッチブラシ
            using (HatchBrush hbBackground = new HatchBrush(hsBackground, Color.LightGray, colBackgroundForeColor))
            // テキストハッチブラシ
            using (HatchBrush hbText = new HatchBrush(hsText, Color.LightGray, colTextForeColor))
            using (StringFormat format = new StringFormat())
            using (GraphicsPath path = new GraphicsPath())
            {
                int iFontSize;  // フォントサイズ
                SizeF size;     // サイズ
                //-----------------------------------------------------------------------------
                // グラフィックの初期化
                //-----------------------------------------------------------------------------
                // アンチエイリアスモード
                g.SmoothingMode = SmoothingMode.AntiAlias;
                //-----------------------------------------------------------------------------
                // 背景イメージ作成
                //-----------------------------------------------------------------------------
                // グラフィックオブジェクトを紙吹雪背景でハッチング
                g.FillRectangle(hbBackground, rect);
                //-----------------------------------------------------------------------------
                // テキストイメージ作成
                //-----------------------------------------------------------------------------
                // イメージに収まるフォントサイズ算出
                iFontSize = rect.Height + 1;
                do
                {
                    iFontSize--;
                    using (Font f = new Font(sFamilyName, iFontSize, FontStyle.Bold))
                    {
                        size = g.MeasureString(sText, f);
                    }
                } while (size.Width > rect.Width);
                using (Font font = new Font(sFamilyName, iFontSize, FontStyle.Bold))
                {
                    //-------------------------------------------------------------------------
                    // ゆがんだテキストのグラフィックパス作成
                    //-------------------------------------------------------------------------
                    // テキストフォーマット設定
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;
                    // パスにテキスト情報を設定、ワープ変換を利用しランダムにゆがめる。
                    path.AddString(sText, font.FontFamily, (int)font.Style, font.Size, rect, format);
                    // ワープ変換
                    float v = 4F;
                    PointF[] points =
                    {
                        new PointF(rnd.Next(rect.Width) / v, rnd.Next(rect.Height) / v),
                        new PointF(rect.Width - rnd.Next(rect.Width) / v, rnd.Next(rect.Height) / v),
                        new PointF(rnd.Next(rect.Width) / v, rect.Height - rnd.Next(rect.Height) / v),
                        new PointF(rect.Width - rnd.Next(rect.Width) / v, rect.Height - rnd.Next(rect.Height) / v)
                    };
                    Matrix matrix = new Matrix();
                    matrix.Translate(0F, 0F);
                    path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);
                }
                // テキスト描画
                g.FillPath(hbText, path);
                //-----------------------------------------------------------------------------
                // ランダムなノイズ作成
                //-----------------------------------------------------------------------------
                int m = Math.Max(rect.Width, rect.Height);
                for (int i = 0; i < (int)(rect.Width * rect.Height / 30F); i++)
                {
                    int x = rnd.Next(rect.Width);
                    int y = rnd.Next(rect.Height);
                    int w = rnd.Next(m / 80);
                    int h = rnd.Next(m / 80);
                    g.FillEllipse(hbBackground, x, y, w, h);
                }
            }
            return bitmap;
        }
    }
    
    

    
    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main(string[] args)
        {
            string sJpegFilePath = string.Format(@"c:\temp\{0:yyyyMMddHHmmss}.jpg", DateTime.Now);
            string sGifFilePath = string.Format(@"c:\temp\{0:yyyyMMddHHmmss}.gif", DateTime.Now);
            string sPngFilePath = string.Format(@"c:\temp\{0:yyyyMMddHHmmss}.png", DateTime.Now);
            // JPEGでCAPCHAイメージファイル作成
            CaptchaGenerator.fnSaveImage(sJpegFilePath, "CAPCHA IMAGE JPEG", 300, 50);
            // GIFでCAPCHAイメージファイル作成
            CaptchaGenerator.fnSaveImage(sGifFilePath, "CAPCHA IMAGE GIF", 300, 50);
            // PNGでCAPCHAイメージファイル作成
            CaptchaGenerator.fnSaveImage(sPngFilePath, "CAPCHA IMAGE PNG", 300, 50);
        }
    }
    
    【作成結果】
    JEPEGイメージ

    GIFイメージ

    PNGイメージ


    ※本サンプルではCAPCHAに表示する文字は固定の値を設定している。 ランダムな文字列を利用する場合、C#でパスワード文字列作成も併せて検討してほしい。



    ■関連ページ

    Google AdSense


    • ---

    Amazon ウィジェット

    • ウィジェット

    @niyo_naのツイート

    無料ブログはココログ

    Google Analytics