2024年10月4日金曜日

NPOI ~ブック全体のフォント設定~

NPOI


C#プログラムからEXCELファイルに出力する場合、以前は、ActivatorでEXCELのインスタンスを作る、いわゆるOLEオートメーション(懐かしい...)の方法を採っていた。もっと昔のC++の時代にも、同じOLEとしてオブジェクトを作成する方法だった。

この方法の良いところは、ライブラリなどを参照する必要がないことと、EXCELアプリケーションが持つOLE用のIFに直接アクセスすることで、確実に処理を完了することができることである。

しかし、以下のような悪い点もある。

  • EXCELがインストールされていないと動かない。(サーバー上のバッチ処理でEXCELファイルを作成するようなことが出来ない。)
  • 重い。遅い。
  • 他にEXCELが開いていると、コピー操作で干渉してしまい、たまに落ちることがある。
  • C#なら、try ~ finally でインスタンスを確実にkillしないと、Windowsが不安定になる。

この悪い点が、良い点を上回っているので、仕方なく使っていた感があった。
(他に、以前Microsoftが作って、後に放棄した、Office SDKみたいな(名前忘れた...)ものもあったが、基本的には上記OLEと変わらず、ライブラリ化しただけだ。)

だがNPOIが登場してから状況が一変した。(Apache POIの存在は知っていたが、WindowsデスクトップアプリでJava?と敬遠していた。)
他にも似たようなライブラリがいくつかあるが、毛の抜けたSEは断然NPOIを推す。

前置きが長くなったが、ここから本題に入る。

ブック全体のフォント設定


NPOIの情報は検索すればたくさん出てくるので基本操作は割愛する。
そのたくさんある情報の中で、ブック全体の書式設定、特にブック全体のフォントの設定はなかなか見つからなかったので投稿しておく。

// ブックの作成
var book = new XSSFWorkbook();
// 既定のフォントを取得
var font = book.GetFontAt(0);
// フォントサイズ
font.FontHeightInPoints = 8;
// フォント名
font.FontName = "メイリオ";
// フォントファミリーの設定(これがミソ)
((XSSFFont)font).SetFamily(FontFamily.MODERN);
((XSSFFont)font).SetScheme(FontScheme.NONE);
// フォント字体(設定しないなら書かなくてもよい)
font.IsItalic = false;
font.IsBold = false;

// 既定の書式設定を取得(おまけ。不要なら書かなくてもよい)
var style = book.GetCellStyleAt(0);
style.VerticalAlignment = VerticalAlignment.Center;
style.WrapText = false;

// シートの作成(ブックを作成したら、最低1つはシートを作成する)
var sheet = book.CreateSheet();

// ブックの保存
using (var fs = new FileStream(filePath, FileMode.Create))
{
    book.Write(fs);
}

フォントファミリーを設定するのがミソっぽく、フォントスキームもセットで。
試しに、上記コードからミソの2行をコメントアウトして実行すると、フォントサイズや字体などは設定通りになっているが、フォントは変わっていないはず。
(ちなみに欧文フォントにしても変わらなかった。)

ここで作成されるEXCELファイルは、エクスプローラーの右クリックから、新規作成→Microsoft Excel ワークシート と選択したときと同じ、以下のファイルをベースに作成される。
C:\Program Files\Microsoft Office\root\vfs\Windows\SHELLNEW\EXCEL12.XLSX
デフォルトではこのファイルのフォントは「游ゴシック」になっている。

上記コードで作成したファイルは、後からシートを追加しても、フォントは設定したフォントになっているはずだ。

2023年10月23日月曜日

SQLite の妙と Dapper のシンプルさ

.NETでSQLite

System.Data.SQLite と Microsoft.Data.SQLite

 
.NET で SQLite データベースを扱う場合、通常は ADO.NET プロバイダーの  System.Data.SQLite を使うと思う。
だが、NuGet パッケージマネージャーで「Sqlite」を検索すると、もう一つ Microsoft.Data.SQLite というのが出てくる。
これは、Entity Framework のチームが .NET Core に合わせて作った、新しい SQLite の ADO.NET プロバイダーだそうだ。
この両者の違いは、以下のページに詳しい。


そしてご存じの通り、SQLite はデータ型の扱いが他の DBMS のような厳格なものではなく、かなりユルユルなのだが、.NET 側に変換されるのは、long (Int64)、double、string くらいだ。あっ DateTime があった。SQL 文で指定するときは文字列としてだが、.NET 側には  DateTime に変換される。
上で long と書いたが、そう、CREATE TABLE でデータ型を INTEGER として定義しても、.NET 側には long に変換される。
ところが!
上記で紹介したページにも書いてあるが、System.Data.SQLite の方は「追加のセマンティクスが列の型に適用され、」らしい。

これはどういうことかと言うと、INTEGER で定義した列のデータは long に変換されるが、INT で定義した列のデータは int に変換される。
論より証拠、サンプルプログラムを作成してみた。

テスト データベースの作成


まず適当なフォルダにテスト用データベース「test.db」を作成し、続けて以下の SQL 文を発行して「test1」テーブルを作成する。


3行目の列名「id_int」の型を INTEGER ではなく、INT にする。

※5行目の列名「name_nvarchar」の型 nvarchar は、.NET での照合順が正常に行われると、どこかで見た。。。けど忘れた。誰か知ってたら教えて。
ちなみに、SQLite は内部では UNICODE なので、nvarchar の「n」はあってもなくても同じこと。
※7行目の「value_float」の型 floatは、.NET に変換されるのは double で float に変換してくれないというテスト用。
※8行目の「value_double」の型 double は double を指定できるよ、というテスト用。

続けて、作成したテーブルにテスト データを追加しておく。


テスト プロジェクトの作成


C# コンソール プロジェクトを作成し、NuGet から System.Data.SQLite をインストールする。
テーブル「test1」に対応したクラス「Test1.cs」を作成する。


続けて「Program.cs」を以下のように書き換える。


これで実行すると、エラーなく結果が出力される。


このように、テーブル カラムを INTEGER として定義すると long に、INT として定義すると int に変換してくれる。そしてもちろんだが、列「id」の受け側のクラス メンバーを int にすると、エラーになる。
また、FLOAT として定義しても float にはしてくれず、double になってしまう。しかしこれは、REAL が SINGLE だということを考えれば、通常の動作ということになる。
ちなみに、float へ変換する際、上記のソースでは、(float)(double)dr["value_float"] としているが、エラーになる可能性がある。その場合は、以下のようにするとよろしい。


でも、この記事の主旨は、SQLite テーブル定義で、INT として列を定義すると、受け側の型を int にしておいても大丈夫、というものだが、果たしてどれだけの需要があるのか。。。
実際の業務では、このような曖昧なことをしてはいけない。後から(改修など)このテーブルを再定義する必要がある場合、「あっ!『INT』にしてるよ、前の担当者。直しとこ。」って INTEGER に直されたら大変なことになる。

実は、Dapper はこの辺りも吸収してくれて、尚且つシンプルになるので、かなりいい感じなんだなぁ。

ADO.NET から Dapper へ


上記では、Main 関数内で、foreach で行ごとにクラスへ変換しているが、Dapper を使うとシンプルになる。
また、DTO としてのデータ クラスのデータ型もある程度吸収してくれる。

テスト プロジェクトに NuGet から Dapper をインストールする。

Program.cs を以下のように書き換える。


実行時引数で引数なし、または 0 のときは、ADO.NET を、1 のときは、Dapper を使った処理になるようにした。
テーブルの行数を 10 万行、100 万行単位にして処理速度の比較テストを行う場合は、出力(コンソールやテキスト ファイルなど)をせず、最終形態であるデータ クラスのリストになるところまでで終了として、System.Diagnostics.Stopwatch で計測する。

見ての通り、Dapper は 47 行目の1行だけである。これで、列「id_int」も「value_float」もちゃんと変換してくれる。
あっぱれ!

2017年8月18日金曜日

Visual Studio での開発バージョン

Visual Studio での開発バージョン


長年 Visual Studio で開発をしていると、バージョンが上がっても特に意識することなく開発を続行できる。
もちろん、ここで言う「意識」とは、バージョンを上げるという「意識」は存在するし、参照しているDLLの関係上バージョンを上げられないという「意識」も存在する。それらすべての条件をクリアした状態でのバージョンアップの作業という意味での「意識」だ。

この辺りの使い勝手の良さは、Visual Studio が他を圧倒している。

だが、バージョン物である以上、バージョン間の互換性の問題がつきまとう。
冒頭で述べたバージョンアップに関しては、諸条件をクリアしていれば特に意識することはないが、一旦上げたバージョンを以前のバージョンの Visual Studio で開こうとすると叱られる。叱られるどころか、そもそも開けない状況になることがある。

そんな時はソリューションファイル .sln をテキストエディタで開いて開発バージョンを確認するが、分かりづらいため覚えとして載せておく。

自分の開発環境でこんなソリューションファイルを見つけた。

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2010




このソリューションファイルを最後に開いた Visual Studio バージョンは 2012 だ。
つまり、Format Version 12.00 の部分が最後に開いたバージョンで、# から始まる行はコメントで、このソリューションファイルを作成したバージョンということだろう。

分かりづらい。

Format Version に記述されているバージョン番号の対応を記すと、

Format Version 10.00 → Visual Studio 2008
Format Version 11.00 → Visual Studio 2010
Format Version 12.00 → Visual Studio 2012

となり、2012 で作成したソリューションファイルの中身は、

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012

となる。
ここまでは何とか分かるが、2013 から複雑になる。
Visual Studio 2013 で作成したソリューションファイルの中身が以下。

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013

そう、Format Version は 12.00 のままなのだ。
ただし以下の2つの項目が増えている。

VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1

ビルド番号以下は Update の適用によって変わるだろうが、そもそも MinimumVisualStudioVersion とは?
ちなみにこのソリューションファイルは 2010 で開けない。
結局、このソリューションファイルを理解できる最小のバージョンということだろう。
つまり手を加えれば開けますよ、ということか。

分かりづらい。

そして、Visual Studio 2015 はもっと凄いことになる。

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1

Format Version は 12.00 のままだし、2015 って、Visual Studio 14 ていうのね。

分かりづらい。

分かりづらいながらも結論としては、

1.基本的に、Visual Studio のバージョンは下げない。

どうしても下げる必要があるときは、下位のバージョンでソリューションを作成し、既存のプロジェクトにリンクするか、ソースファイルなどの構成ファイルを新しいソリューションフォルダまたはプロジェクトフォルダにごっそりコピーして、各プロジェクトに追加していく。その際、元のプロジェクトファイルをテキストエディタで開いて、構成ファイルや参照を確認しながら追加する。(この作業をやったことがある人は分かると思うが、本当にめんどくさい!)

2.開発バージョンを確認するときは、ソリューションファイルをテキストエディタで開き、以下の規則にしたがって判別する。

・VisualStudioVersion = xx.x.xxxxx.x の記述がない場合は上で書いたバージョン番号対応で確認する。
・VisualStudioVersion = xx.x.xxxxx.x の記述がある場合は、
 VisualStudioVersion = 12.0.xxxxx.x は Visual Studio 2013、
 VisualStudioVersion = 14.0.xxxxx.x は Visual Studio 2015
 とする。

Visual Studio 2017 はどうなってるんだろう。

※間違いがあれば指摘してください。

2012年12月7日金曜日


Windows の unicode と MAX_PATH

3年前だったか、仕事で付き合いのある人で変わった癖(?)のある人に出会った。
その人、自分でフォルダ名(ファイル名も)を付ける際に異常なほど長い名前を付けていた。
理由は、

「だってほら、一目で何のフォルダ(ファイル)か分かるでしょ?」

一目でっつっても、ファイル名が日本語の summary になっていた。
しかもご丁寧にどのファイルにも作成日が先頭に付いている。
もっと厄介なのは、どのファイル(フォルダも)も、何処に移動しても重複しないように意図されていたこと。
例えば年度フォルダなど、「2012」や「H24」といった簡素なものはまず無く、その親フォルダ名が頭に付いていたりする。
そら長くなるわ。
て言うか、階層の意味ねーじゃん、て言いたくなった。

だが人それぞれ。
毛の抜けたSEもいれば、毛の抜けたアンガス・ヤングもいる。

先日その人から連絡があった。
「毛の抜けたSEさんに影響されて、ちょっと前にプログラミングの勉強を始めた。。。」
ふむふむ。で?、まさか教えろとか言わねーよな。

「MAX_PATH って260バイトですよね?
 おかしいんですよ、400バイト超えてるんですけど平気なんです。」

別に私は貴方のファイルパスが260バイトを超えようが、3バイトに満たなくても平気ですけど。
っつーか、MAX_PATH ってまさか「C」やってるって言わねーよな!

「ご多分に漏れず C# です」

ブライアン・ジョンソンのように高音のしわがれ声で
「#?@! You!!!」
電話を切りたくなったが、ちょっと待てよ。

400バイトってどういう勘定してるんだ?

「だってほら、私が付けるファイル名は殆ど日本語の全角文字だから、
 単純に×2すると400バイトをゆうに超えるんですよ。」

ここにも居たか、shift-jis にしがみつく過去の遺物が。
かくいう私も、しがみつかざるを得ない状況なのだが。。。


今の Windows は内部では UNICODE (UTF-16) を使用している。

これは、全ての文字を2バイトで表現しようとするものだが、
世界中の文字が、たった 65,536 通りで表現できるはずもない。
よって、通常これだけあれば大丈夫だろうと思われる文字群を、
基本多言語面(BMP)という領域に収め、それ以外をサロゲートペアという
2つの2バイト域(計4バイト)で表現する。
つまり、通常2バイトだが、文字によっては4バイト使用する文字もあるということ。

もちろんこの人は、UNICODE の話をしているのではない。
shift-jis の話をしているのだ。だから「全角」という言葉が出てくる。

だが、ちょっと怖くなったので、今回改めてネットで検索してみたら、

やっぱり!

昔の shift-jis の頃に作成されたであろうページがわんさかヒットする。
それらのページの多くには、

「Windows の最大ファイルパス長は 260バイト

だが、新設なページも多数あった。

「Windows の最大ファイルパス長は 260バイト(UNICODE は260文字)」

そう、UNICODE では、206文字なのだ。
だから、

「あいうえお」と「aiueo」は同じ文字数の5文字だ。

shift-jis の世界では「あいうえお」は確かに10バイトだが、
UNICODE でも「あいうえお」はおそらく10バイトだろう。
ただし、「aiueo」は shift-jis では5バイトだが、
UNICODE では10バイトだ。

UNICODE は先にも言ったように、通常2バイトだから、変な漢字(失礼)や特殊な文字を使用していなければ、バイト数計算は楽だ。
だが、最大パス長を示す _MAX_PATH 定数が持つ「260」という数字は、

shift-jis の世界ならバイト数
UNICODE の世界なら文字数

という切り分けをしないといけない。
が、現在ではあまり意識しなくても良い環境になっている。

というのも、Java も C# も VB.NET も、て言うか Visual Studio も、
C++Builder も Delphi もすべて内部では UNICODE になっている。
Windows も UNICODE なんだから何も考えずに「文字数」でいけばいい。

ただし、ファイルを開いたり、保存したりするときは、文字コードに気をつけるのは言わずもがな、である。

あっ!あともう一つ。
USBメモリは良く使うが、NTFS でフォーマットできなかったりする。
確か、Windows日本語版は FAT には shift-jis に変換して送信するはず。
これはファイルの中身の話ではない。ファイル名の話だ。
だから上記の400バイト(実際は200文字以下だろう)を超えるパス長の状態そのままで、USBメモリにコピーするとエラーになる可能性がある。


しかし今や「全角」「半角」という言葉自体が消滅しつつあるのを、先に勉強した方が良い。
それを踏まえて「全角」「半角」という言葉を使って欲しい。

それと、「260」と言ったが、正確には「259」文字だ。
文字列の最後に null が付くので。

また、パス文字列の前に接頭辞として "\\?\" を付けると、約32,000文字までいけるらしいが、
そもそも最大パス長付近をウロウロしているようでは、まだまだフサフサだな。

2012年12月6日木曜日

C# タイプ別コントロール配列の取得(と、ジェネリックスを使った構造体の比較)

C# タイプ別コントロール配列の取得(と、ジェネリックス)


例えば、ある親コントロール内の子コントロール配列は、以下のようにすれば簡単に取得できる。


Control[] childControl = parentControl.Controls;

通常は以下のような使い方になるか。(フォームのコントロール一覧)

foreach (Control child in this.Controls)
{
  listBox1.Items.Add(child.Name);
}


この Control.Controls は Control.ControlCollection を返す。
普通 VisualStudio などで Windows フォームアプリケーションを開発していると、フォームのデザインはフォームデザイナで行うと思う。
その場合、あまり気づきにくいのだが、Form1.Designer.cs 内に、デザイナで行ったコントロールの追加や、位置およびプロパティの変更値などが書かれている。
例えば、フォームへのコントロールの追加は、この Controls プロパティの Add メソッドを使用している。

this.button1 = new System.Windows.Forms.Button();
this.Controls.Add(this.button1);

また、ある親コントロール内で、指定された名前のコントロールを探すには、上記の Name プロパティで探しても良いが、Controls.Find メソッドを使うと簡単だ。

Control[] controls = parentControl.Find("button1", true);

これは、例えば大量のボタンやラベル、テキストボックスを扱う時に、コントロールの名前を例えば、

button_01, button_02, button_03, ...

などのようにしておけば、プログラム内でシーケンシャルにアクセスできるだろう。

また、当然だが、コントロール名ではなくコントロールそのものが親コントロール内に存在するかどうかの確認も行える。

if (this.Controls.Contains(button1))
{
  MessageBox.Show("いやいや、当然でしょ");
}

しかしこれは、コントロールを動的に作成するような場合でなければ、あまり使うことはないだろう。

そして本題なのだが、コントロール名やコントロールそのもので検索するのではなく、タイプ別にコントロール一覧を取得したい場合がある。(えっ?俺だけ?)

namespace NantokaKantoka
{
  public class Honyalala
  {
    /// <summary>
    /// タイプ別にコントロールの一覧を取得
    /// </summary>
    /// <typeparam name="T">
    /// コントロールのタイプ(型)
    /// </typeparam>
    /// <param name="parent">
    /// 親コントロール
    /// </param>
    /// <param name="searchAllChildren">
    /// parent以下の全てのコントロールを検索するかどうか
    /// </param>
    /// <returns>
    /// 指定された型のコントロール一覧
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// parentがnullの場合
    /// </exception>
    /// <exception cref="ArgumentException">
    /// parentがControlではない場合
    /// </exception>
    /// <remarks>
    /// あまり使うことはないか...
    /// しかし例えば、大量のチェックボックスがあって、
    /// その全てのCheckedプロパティをfalseにするとか。
    /// いちいちコントロール名を書いていると疲れません?
    /// シーケンシャルな名前にして検索するのも一手だが、
    /// コントロール名には分かりやすい名前を付けたいもの。
    /// </remarks>
    /// <example>
    /// フォーム内の全てのCheckBoxのチェックをオフにする
    /// <code>
    /// foreach (CheckBox cb in
    ///   GetControlsByType&lt;CheckBox&gt;(this, true))
    /// {
    ///   cb.Checked = false;
    /// }
    /// </code>
    /// </example>
    public static T[] GetControlsByType<T>(
      Control parent, bool searchAllChildren)
    {
      if (parent == null)
        throw new ArgumentNullException();
      if (!(parent is Control))
        throw new ArgumentException();
      
      ArrayList list = new ArrayList();
      
      foreach (Control child in parent.Controls)
      {
        if (child is T) list.Add(child);
        if (searchAllChildren)
          list.AddRange(
            GetTypeControls<T>(
              parent, searchAllChildren));
      }
      
      return (list.Count == 0) ? null : (T[])list.ToArray(typeof(T));
    }
  }
}

使い方は example の通り、コントロールの型を指定する。

foreach (CheckBox cb in GetControlsByType<CheckBox>(this, true))
{
  cb.Checked = false;
}

C++ にもテンプレートがあって結構重宝していたが、C# のジェネリックスには敵わない。
C++ の場合はやっぱり後付け感満載で、インライン展開されるヘッダ内の巨大なマクロに変身するが、C# の場合まるで手足のように当然感満載なわけだ。
もちろん C# のジェネリックスには、C++ テンプレートに比べ不便なところもあるが、「型推論」を根底の考えに持つジェネリックスでは当然のことと言えよう。
ただ、上記のメソッドは普通のジェネリックメソッドと違い、いわゆる型引数がない(ControlCollection の中から目的の Type に変換可能なものだけを Type[] として返すため、引数で Type を指定するのではない。)
なので、

GetControlsByType<CheckBox>

の <CheckBox> を取ると、型推論できないと叱られる。
しかしこれは明示的に指定するのが正当なやり方のような気がする。
逆に、取り出す先を var にするのはもちろん可能だ。

foreach (var cb in GetControlsByType<CheckBox>(this, true))
{
  cb.Checked = false;
}

これは以前に紹介した var についてでも触れたが、ここでの var は CheckBox という型が決定している。

C# ジェネリックスの良いところは、コンパイル時に型推論が行われ、型を厳密にチェックしてくれる(リフレクションで型の判別ができない Java とは違う)のは言うに及ばず、VisualStudio では、コーディング時に知らせてくれるので非常に助かる。

ついでにジェネリックスを利用したメソッドを2つほど紹介する。
どちらも比較メソッドで、List (ジェネリックリスト)と配列の比較。

using System.Collections;

public static bool ListEquals<T>(List<T> list1, List<T> list2)
{
  // 型は<T>で統一しているので、
  // 型の比較は不要(コンパイルエラーとなる)
  
  // null のチェックとリストのアイテム数のチェック
  // null の引数を以下に通すとエラーになるため
  
  // どちらも null 参照の場合は同じものとする
  if (list1 == null && list2 == null) return true;
  // 片方のみ null 参照は違うものとする
  if (list1 == null || list2 == null) return false;
  // 両方とも空のリストは同じものとする
  if (list1.Count == 0 && list2.Count == 0) return true;
  
  IStructuralEquatable st1 = list1.ToArray();
  IStructuralEquatable st2 = list2.ToArray();
  
  return st1.Equals(st2, StructuralComprisons.StructuralEqualityComparer);
}

public static bool ArrayEquals<T>(T[] array1, T[] array2)
{
  // 型は<T>で統一しているので、
  // 型の比較は不要(コンパイルエラーとなる)
  
  // null のチェックとリストのアイテム数のチェック
  // null の引数を以下に通すとエラーになるため
  
  // どちらも null 参照の場合は同じものとする
  if (array1 == null && array2 == null) return true;
  // 片方のみ null 参照は違うものとする
  if (array1 == null || array2 == null) return false;
  // 両方とも空のリストは同じものとする
  if (array1.Length == 0 && array2.Length == 0) return true;
  
  IStructuralEquatable st1 = array1;
  IStructuralEquatable st2 = array2;
  
  return st1.Equals(st2, StructuralComprisons.StructuralEqualityComparer);
}

使い方はこんな感じ。

List<string> list1 = new List<string>();
List<string> list2 = new List<string>();

bool ret = ListEquals(list1, list2);

string[] str1 = new string[] { "abc", "def", "ghi" };
string[] str2 = new string[] { "abc", "def", "gigii" };

ret = ArrayEquals(str1, str2);

2つともやっていることは殆ど同じで、比較メソッドをサポートする IStructuralEquatable 型へ代入している。
List の方は、ToArray() メソッドを使用して配列に変換している。
「うん?」と思った方も多いだろう。
そう、List の比較でも以下のようにすれば ArrayEquals を使用できる。

bool ret = ArrayEquals(list1.ToArray(), list2.ToArray());

しかし、メソッド呼び出し時点で、list1 が null だったら、この呼び出し時点でエラーとなる。
なぜなら、null 参照の List で ToArray() メソッドは使えないからだ。

そうすると、以下のように事前にチェックが必要となる。

if (list1 != null && list2 != null)
  ret = ArrayEquals(list1.ToArray(), list2.ToArray());

実際のコードではこんなに単純ではないだろう。こういう煩わしいチェックを極力なくすのがオブジェクト指向の一端とも言える。
だからメソッド内でチェックできる環境を作る。
もちろん、これ以前に List 型変数の null 参照がコード内に存在しても良いかどうかは別問題となるが。

また、よく見かけるジェネリックメソッドの典型として、以下のように型特有のメンバを使用するための、型の型決めみたいなもの(条件とも言える)を指定する where なるものがある。

public static bool Comp<T>(T t1, T t2)
  where T : IComparable
{
  return (t1.CompareTo(t2) == 0) ? true : false;
}

これを制約条件というらしい。
こうしておけば、IComparable を実装している型しか与えることはできない。
逆に言うと where 以下を取り去ると、「CompareTo って何?」と言われる。
またこの制約条件にはインターフェイスなどの他に、「値型ですよ」という struct や、「参照型ですよ」という class などもある。
また、変わったところでは「引数無しのコンストラクタを有する」という new() なんかもあったりする。(複数指定する場合は最後に指定する必要がある)

何故上記の2つを紹介したかというと、元々構造体のメンバの等価比較が簡単にできないかとあれこれ考えていて、ジェネリックスを使うことにしたんだが、例えば以下のようなジェネリックメソッドを考える。

public static bool CompareStruct<T>(T st1, T st2)
  where T : struct
{
  return st1.Equals(st2);
}

上記で説明したように、制約条件には struct を指定する。
現在の C# の構造体は IEquatable を実装しているので、Equals メソッドが使える。
これで構造体の等価比較ができる。

ところが!

構造体のメンバが値型だけなら(String 型も OK)これで良いのだが、配列やクラスなどのメンバは対応できないんだなぁこれが!

論より証拠、以下のような構造体を作る。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
}

これを作成したジェネリックメソッドでチェックする

TypeA typeA1 = new TypeA();
TypeA typeA2 = new TypeA();

typeA1.a = 1;
typeA1.b = 1.0;
typeA1.c = "one";
typeA1.d = new DateTime(1968, 10, 16);

typeA2.a = 1;
typeA2.b = 1.0;
typeA2.c = "one";
typeA2.d = new DateTime(1968, 10, 16);

bool ret = CompareStruct(typeA1, typeA2);

みごとに true が返る。
ところが、この TypeA にメンバを追加して以下のようにする。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
  public int[] e;
  public double[] f;
  public string[] g;
  public List<string> l;
}

この状態で CompareStruct でチェックするとバッチリ false が返る!
これら値型以外のチェックは独自に行う必要がある。(めんどくさっ)

実はこれ以前に、私は C# で構造体を定義する時は、Equals と GetHashCode メソッドをオーバーライドしていた。
この時に使用していたのが上記2つの等価比較メソッドである。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
  public int[] e;
  public double[] f;
  public string[] g;
  public List<string> l;
  
  public bool Equals(TypeA other)
  {
    return (this.a == other.a) &&
      (this.b == other.b) &&
      (this.c == other.c) &&
      (ArrayEquals(this.e, other.e)) &&
      (ArrayEquals(this.f, other.f)) &&
      (ArrayEquals(this.g, other.g)) &&
      (ListEquals(this.l, other.l));
  }
  public override bool Equals(object obj)
  {
    if (obj == null ||
      this.GetType() != obj.GetType())
      return false;
    
    return base.Equals((TypeA)obj);
  }
  public override int GetHashCode()
  {
    int retX = 0;
    
    retX ^= a;
    retX ^= b.GetHashCode();
    retX ^= c.GetHashCode();
    retX ^= d.GetHashCode();

    foreach (int val in this.e)
      retX ^= val;
    foreach (double val in this.f)
      retX ^= val.GetHashCode();
    foreach (string val in this.g)
      retX ^= val.GetHashCode();
    foreach (string val in this.l)
      retX ^= val.GetHashCode();
    
    return retX;
  }
}

実はこれ、IEquatable を実装しているのと同じで、定義する際に、

struct TypeA : IEquatable<TypeA>
{
  ...
}

と IEquatable<TypeA> と明示する。
そして、CompareStruct の制約条件に struct と IEquatable を付ける。

public static bool CompareStruct<T>(T st1, T st2)
  where T : struct, IEquatable<T>
{
  return st1.Equals(st2);
}

この状態で、ジェネリックメソッド CompareStruct でチェックすると、

bool ret = CompareStruct(st1, st2);

見事に true が返る!メデタシ、メデタシだが、元々構造体のみで等価比較ができるように Equals と GetHashCode をオーバーライドしたので、ジェネリックメソッドを使わなくても以下のように簡単に比較できる。

bool ret = typeA1.Equals(typeA2);

こちらも一行で済んでしまうので、今更ジェネリックもなぁ。
しかし、ジェネリックスを勉強してて思ったんだが、ますます持って Java を使うのが怖くなったなぁ。
C# は 2.0 からガラッと変わったが、Java も後方互換性ばかり意識せずに、Python のように「えいっ!」と変わったら?

2012年2月16日木曜日

C 系のビット操作と C# の enum

最近のプログラマはビット操作をやらないそうだ。
というより、毛の抜けた SE も今は滅多にやらなくなった。
そういう時代なのだろう。

だが、ビットをフラグとして扱うビット判定は今でもちょくちょく使う。
代表的な使用例が enum だ。以下 C# の例。

[FlagsAttribute]
enum AruFlags
{
    None   = 0x00, // 0000 0000
    One     = 0x01, // 0000 0001
    Two    = 0x02, // 0000 0010
    Three = 0x04, // 0000 0100
    Four  = 0x08, // 0000 1000
    All     = 0x0F // 0000 1111
};

enum は便利なので良く使う。
この enum にフラグ属性を持たせる場合は、上記のように宣言の前部に [FlagsAttribute] を加える。
それだけではダメで、中身の各 ID に対してビットフラグを定義する。
「うん?」
と思った人はプログラマだろう。
そう、何も [FlagsAttribute] を付けなくても、enum は中身は int なのだから、各 ID に対して 2 の y 乗を定義すれば良いんじゃない?
と思うだろう。実際、毛の抜けた SE は以前はそうしていた。
論より証拠、上記のコードから [FlagsAttribute] を取り去って、以下のようにビット判定しても何も問題は起きない。

AruFlags sonota = AruFlgas.One | AruFlags.Three;

if ((sonota & AruFlags.One) != 0) Console.WriteLine("One が入ってます");
if ((sonota & AruFlags.Two) != 0) Console.WriteLine("Two が入ってます");
if ((sonota & AruFlags.Three) != 0) Console.WriteLine("Three が入ってます");
if ((sonota & AruFlags.Four) != 0) Console.WriteLine("Four が入ってます");

/* 結果はもちろん以下のようになる
One が入ってます
Three が入ってます
*/

では何故、[FlagsAttribute] を加えるかというと、
MSDN リファレンスでは、
「列挙体をビット フィールド、つまりフラグのセットとして扱えることを示します。」
と要約していて、下部の解説で長々と説明しているが、あたかも、これを付けないとフラグ判定できませんよ、とも読める。
が、要するに、作り手(プログラマ)の礼儀作法を教育しているように思われる。
これは何も [FlagsAttribute] に限ったことではなく、最近の(以前から?) Microsoft の方針のようだ。
リファレンスの下部の解説部分でもそういう「ビットフィールドの作成方法」的な部分が大半を占める。

ということは、

「ビットフィールドとは何か」、
「ビットフィールドはどういう状況で使うのか」、
「ビットフィールドを作成・使用する際の注意点は何か」

という、昔は先輩のプログラマに教わったような初歩的な事柄が、

System.FlagsAttribute

という、enum でしか使わない特異で何だか怪しいものの解説ページに一緒に載っているのである。
もとい、大半を占めているのである。
異様な感じに包まれたのは、毛の抜けた SE だけだろうか。。。

しかし、違いはもちろんだが、ある。
FlagsAttribute のページではなく、列挙型のリファレンスにもあるように、
結果を文字列として表示するような場合、
例えば、

MessageBox.Show(sonota.ToString());

この結果が違ってくる。
[FlagsAttribute] 属性を付けると、「One, Three」と表示されるのに対して、
[FlagsAttribute] 属性を付けないと、「5」と表示される。


また、この [FlagsAttribute] 属性について、こんなコードを見つけた。(まんまではない)

[FlagsAttribute]
enum FileFlags
{
    None = 0,
    Read = 1,
    Write = 1 << 1,
    Create = 1 << 2,
    Delete = 1 << 3,
    All = 1 | 1 << 1 | 1 << 2 | 1 << 3
};

ビットフィールドを定義する際に、直に値を定義するのではなく、左シフト演算子を使って桁を上げていって、2 の y 乗を実現している。
懐かしくて、何だか心地よい気分になったもんだが、毛がフサフサなヒヨコにはどうか。
むしろ初心者は、2 のべき乗の値そのものを覚えておくべきだ。(せめて 1024 までは) というのが毛の抜けた SE の教育方針だ。



この enum 便利でよく使うが、あくまでも静的定義なので、VCL の 集合体 Set<> なんかみたいに、enum を組み込んで、Contains みたいなメソッドが使えないかなと思ってあれやこれや調べてたら、無い(笑)。
結局、Enum のクラスメソッドを使って実現するしか手はなさそう。
そして、作った関数がこれ。ID を文字列として検索して値を出力するのと、値を受け取って ID を文字列として返すもの。

public bool Search_Enum<TEnum>(string str, out int num)
{
    num = 0;
    try
    {
      var etype = typeof(TEnum);
      if (!etype.IsEnum) return false;
      if (Enum.GetUnderlyingType(etype) != typeof(int)) return false;
      foreach (int val in Enum.GetValues(etype))
      {
        if (str == Enum.GetName(etype, val))
        {
          num = val;
          return true;
        }
      }
      return false;
    }
    catch (Exception)
    {
      return false;
    }
}

public bool Get_Enum_Value<TEnum>(int num, out string str)
{
    str = "";
    try
    {
      var etype = typeof(TEnum);
      if (!etype.IsEnum) return false;
      if (Enum.GetUnderlyingType(etype) != typeof(int)) return false;
      if (!Enum.IsDefined(etype, num)) return false;
      str = Enum.GetName(etype, num);
      return true;
    }
    catch (Exception)
    {
     return false;
    }
}

う~ん、もっとスマートな方法は無いのかぇ?
しかし、これを作ったのにはもうひとつ理由があって、特に2つ目の関数がその目的なのだが、
enum の .ToString() って遅くない?
この関数つかっている部分と、.ToString() 使ってる部分とで、明らかに速度が違う。。。
何だかなぁ。。。

2012年2月15日水曜日

C# の var

C#3.0から使用できるようになった var ついて



MSDN リファレンス使用例あるのでまんまのコード使わせてもらう


例1

string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
          where word[0] == 'g'
          select word;

foreach (string s in wordQuery)
{
    Console.WriteLine(s);
}



例2:
var custQuery = from cust in customers
          where cust.City == "Phoenix"
          select new { cust.Name, cust.Phone };

foreach (var item in custQuery)
{
    Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}





リファレンスにもあるように、例1は string 型しかあり得ないので、var を使う必要は無いが、もちろん使ってもよい。
ところが、例2は型が分からない、というより曖昧にしておいて、コンパイラに判断してもらおうというもの。
当然 foreach で要素を取得する場合の item 変数も明示的に var 宣言しないとダメ。


var は、JavaScript ではご存じローカル変数の一般的な宣言方法なので、C# の var とは全くの別物と理解できるが、言うほど別物でも無かったりするから厄介だ。
※注 Java は厳密な型指定が必要なので JavaScript とは違う。


というのも、今まで Script 系しかやってなくて、C# がおもしろそうだから手を出してみようなんていう輩が、「な~んだ、var あるじゃん」とか言いつつ、なんでもかんでもローカル変数に var 宣言する毛がフサフサなヒヨコが出てくる可能性があるんじゃなかろうか?
もしくは、Java (JavaScript ではない) しかやってなくて、「おっ!?これってあの var かい?」とか言いつつ、(同上)


構造型プログラミング言語である「C」のやり過ぎで毛が抜けてしまった SE としては、頭から湯気が出るような感じなのだ。


C# の var の最大の(?)特徴は、
例えば、int などの単純型(実は System.Int32 のエイリアスだったりする。(だから、new int() などという構文が存在する。これはコンストラクタを呼び出している))以外の、ちょっと型名が複雑になりがちなものを省略できるところにある。
おそらく、そういう意味もあって、例1を掲載しているんだと思う。
リファレンスにもあるように、例1は、IEnumerable<string> という型が決定している。
これが var だけで済むのだからありがたい。と、いうもの。


もちろん、例2での使い方が一般的で、LINQ クエリ操作などで真価を発揮するんだろうが、毛の抜けた SE としては、
「左辺が簡単になる」
というのが1番大きい。


Java も最近では色々と試しているようだが、何せほら、あのクラスの量(継承・多態)と何でもかんでもクラスっていうのは半ば辟易するし、コード補完機能が無い(またはレスポンスが悪い)エディタでプログラミングする気にならないし、何だかよく分からないし。。。


要するに、益々持って C# の方が優位性が高くなるなぁ と思う。
じきに、Android アプリが C# で普通に開発できるようになる はず。

2012年1月17日火曜日

.NET で dBASE (古っ!) テーブルの操作

MySQL を語ったので、ついでに懐かしい dBASE も。

DataReader 等の使い方はあちこちにチラホラしてるので割愛する。

て言うか、dBASE なんて今の人は知ってるんだろうか?
そもそも有名なバージョン IV は1988年つまり、Windows 以前に発売されていたとんでもない代物なのだ!
しかもバグが多く、なぜ Borland が買ったのかよく判らないが、Borland が買ったおかげで、BDE (ボーランド・データベース・エンジン) が生まれた。
その BDE を昔は本まで購入して使ってたなぁ...

C++Builder 3/4 の頃には、BDE 関数群の直接の使用は推奨されなくなっていた。(通常は VCL を通して使う。TDateSet や TTable で。)
もちろん私だけでなく、世界中にこの BDE 関数を直接使用していた人も数多く存在していただろうし、その後も当分の間は使っていた。何故かは後述。

dBASE の良さは、
小規模なスタンドアロン使用ではおそらく今でも最上のデータベースであること。
システムの復旧がし易いという理由で、テキストを好む SE にはうってつけのツールだ。
つまり、社内データベースとしては、比較的経験が浅い SE でも充分扱えるもので、同時に勉強にもなる。MySQL や SQL Server、Oracle など、そもそもバックアップってどうやるの?会社と家の両方で常に同じ状態にしたいけどどうやるの?などなど、dBASE なら簡単明瞭!何せ dBASE はテーブルがファイル単位だから、ファイルやディレクトリ単位でコピーすれば済む。それに、フォーマットが公開されているので、自分でテキストから dBASE テーブルに変換できれば、膨大な資産をテキストでバックアップしてあるような場合でも、あまり深く考えずにデータベース化できると思う。
そして実はこれが最大の利点なのだが、大昔のデータベースであるにもかかわらず、検索スピードは異常な程速いこと。
まぁ今でこそ、データベースを語る上でセキュリティは離せなくなったが、昔はデータベースといえばまずは検索スピードだった。いや、それだけだった。
だからこそ、MySQL はここまでもてはやされるのだ。MySQL の最大の売りはその速さにあるのだから。トランザクションな InnoDB の登場以前からデフォルト・ストレージエンジンとして MyISAM があることがそれを証明している。

BDE 関数を直接使用すると、この速さを享受できる。だからプログラマは皆これを使った。もちろん推奨されなくなったことでも分かるように、この関数群が一筋縄ではいかない(笑)
まぁそれが楽しかったとも言えるが、ホントに苦労するんです、dBASE は。
昔の(15年ほど?)、何とかシステム、何たら情報システムの数パーセントは dBASE を使ってるんじゃないだろうかと思わせるほど有名だったが、大量のデータに対応できない構造になっているので(フィールドの構造にもよるが、10万件を超えるとヒーヒー言うようになり、100万件を超えると今の CPU とメモリでも結構きつくなる)、あっという間に廃れていった感じ?

BDE 関数の直接使用は興味がある人はメールください。教えます(笑)。

しかし、日曜プログラミング的な感じで、「なんちゃってシステム」みたいなものを作るとき、殆どの素人に毛が生えた程度のプログラマなら、ACCESS なんかで作ったりしてたよね?(今、書いただけで鳥肌が立った)
でも、dBASE の方が断然速いし、「なんちゃってシステム」はそんなにセキュアじゃなくて良いでしょ?

何はともあれ、こんな感じで。

using System.Data.OleDb;

namespace やら class やら
{
    string conStr =
        "Provider=Microsoft.Jet.OLEDB.4.0;" +    // お約束
        "Data Source=C:\\dBASE_Test;" +          // テーブルがあるフォルダ
        "Extended Properties=dBase IV;";            // dBase IV しか知らない (III はダメ)

    OleDbConnection dBaseCon = new OleDbConnection(conStr);
    OleDbCommand sql = dBaseCon.CreateCommand();
    OleDbDataAdapter adapter = new OleDbDataAdapter(sql);
    OleDbCommandBuilder = new OleDbCommandBuilder(adapter);

    // DataTable を使う場合
    DataTable dt = new DataTable();
    sql.CommandText = "select * from table_name.dbf";
    adapter.Fill(dt);
    // dt を使って Grid 表示したり、あれやこれや...

    dBaseCon.Open();

    // 編集も上記のように SQL 文を発行する (DataTable の機能は使わない)
    // これを身につける
    sql.CommandText = "insert into table_name.dbf values (" +
        "\"" + field1_value + "\", " +    // フィールド値が文字列の場合は「"」で囲む

        "\"" + field2_value + "\", " +
        "\"" + field3_value + "\")";

    sql.ExecuteNonQuery();

    dBaseCon.Close();
}

結局、他のテーブル操作と何ら変わることはない。
ただ若干 dBASE 仕様が所々出ているが、それだけだ。
ちなみに、DataTable は、テーブルのレコードを読み込んで、データを表示したりするのには便利だが、書き込みはイマイチ信用できない(あくまで個人的な意見だが)。
なので、
DataTable でグリッドにレコードを表示して、ユーザに編集させるような場合、Update の段階で、DataTable の各レコードのステータスを確認して、SQL 文を生成するようにした方が、面倒だが確実にテーブルを編集できる。

以前、MySQL の所でも言ったが、SQL 文のベタ書きが一番良い。プログラムで自動生成するようにするのがベスト。Builder の VCL や、.NET の DataTable のようなものは便利だが、他に移植するのが辛くなり、結局移植どころか移行もできないといった状況になる。特に上記のような編集作業は必ず SQL 文で行うようにすれば、移植が大変楽になる。
「オレは移植なんかしないよ!一生このままさ!ざまぁみやがれ!」
と言いたくなるのは、まだまだ毛がフサフサなヒヨコだ。
今の時代は、開発プラットフォームが乱立する、まさに混沌とした時代だ。
こんな状況で、未だに「VB なら出来ます」「Java を勉強して1年経ちました」などと平然と答えている若きプログラマを見ると、”何とフサフサな” と思ってしまう。
VB なら VB.NET だし、VB6 までとはまるで別物だし、.NET 繋がりで C# にもスムーズとはいかないまでも、勉強する価値はあるし、チャンスだと思う。
また、最近は Android の影響でまた「Java」ブームが再燃しているが、私も Java を勉強したので良いところは分かっているつもりだが、それでも Java は遅い、ということを認識しなくてはならない、何でもいいから取り敢えず Java で開発だ、と言うのは愚の骨頂だ。
ちなみに、Java はオブジェクト指向型だが、「オブジェクト指向」が目的となってしまっている。「オブジェクト指向」はあくまでもプログラミングの手段に過ぎない。これを言い出すと長くなるのでやめます。あっ、どこかのサイトに Java と C# を比較してたな、あのサイトは面白かった。また確認できたら載せます。

2011年12月16日金曜日

いまさらな C# string クラス

ご存じだろうか?
C# の string (String のエイリアスなので同じもの) 型の変数に関するあれこれ。


実は、文字列の終端はヌル文字ではないんです!


「何と!」


ということは文字列の途中ヌル文字座ってたりします。


なので以下のような C ちっくなコードはデンジャラスです。


string str = "あれやこれや";

int lp = 0;
while (true)
{
    if (str[lp] == '\0') break;


    // ...なんやかんや


    lp++;
}


まぁこれはちょっと無理矢理すぎた感があるが...
普通はクラスプロパティの Length 見るだろうからこういう問題には当たることは無いだろう


ただし、C から移行で、ポインタ変数バリバリ使ってた注意必要


おぅおぅ! C# にはポインタないのかえ


なんてほざいて色々調べたら出来ることが分かってわざわざ使ってます
みたいな?
そういう人たち気をつけないとダメです。
というか、C# 以下のようなする意味あんのか


unsafe private void test()
{
    char[] str = "いろはにほへと".ToCharArray();


    fixed (char* pnt = &str[0])
    {
        char* ptr = pnt;
        while (*ptr != '\0')
        {
            // なんやかんや
            ptr++;
        }
    }
}


怖いくらいまんま C なってる...おそろしや...


まぁポインタ使うには上記ように、unsafe やら fixed やらを使って
「今からあっしはポインタを使いまっせー!」
と内外に宣言しないといけないようだし、殆どの人たちは
「めんどくさっ!」
と、今後一切使わなくなるでしょう。


ただ、どの世界にも異端児(変態)はいるもので、そういう人たちのためにあるようだ。


しかし面白いのは、その変態たちと C# の開発者たちとの攻防がくっきりと見えるところか。





2011年10月19日水曜日

C++Builder 4 / 6 で MySQL を直に操る ~その2 コーディング編~

C++Builder 4 / 6 で MySQL を直に操る ~その2 コーディング編~



大層な感じになってしまったが、そんなに詳細に説明しないのでご容赦を。



前回、プロジェクトを作成したので、今回は実際にプログラミングして MySQL を動かす。



インクルード


・ ヘッダーまたはソースファイルに以下を追加

#include <mysql.h>


これは基本で、他にも色々あるので確認すること。

※なお、確認には libmysql.def を閲覧することをお勧めするが、無い場合は impdef.exe を使用して作成できる。

impdef "[適当なディレクトリ]\libmysql.def" "$(MySQL)\bin\libmysql.dll"
上記を実行すると、libmysql.def が出力される。

この def ファイルには libmysql.dll に登録されている関数の一覧が格納される。普通のテキストなのでメモ帳などで開ける。


・ この後のコンパイル時に、関数云々のエラーが出て叱られる場合は、

#include <mysql.h> 宣言の前に、以下を追加してみること。

#include <windows.h> または #include <winsock.h> または両方


ありそうな失敗


ここで、コンパイル時や実行時にありそうな失敗をいくつか。

・ libmysql.dll のエラー
実行すると、libmysql.dll の何とかエラーと表示され、coff2omf を使用してはどうかと勧められる。
大きなお世話なんだが、libmysql.dll にパスが通ってない可能性がある。またバージョン違い(?)の可能性も否定できない。

そもそも、coff2omf は失敗する可能性があって、使用は勧めない (DLL 自体を変換するため注意が必要)

 また、取り敢えずアプリケーションパスに置けばコンパイラを通ることもあるが、根本的な解決にはなってないので、前回の設定編をもう一度チェックされたし。
・ コンパイラは通るが、初期化関数 mysql_init() でアドレス違反のエラーが出る。
先ず最初に、既知の問題である初期化関数呼び出しのコーディングを確認する。
MYSQL *mysql = mysql_init(NULL);
引数は必ず NULL にする。つまり、常に新しいオブジェクトにメモリを割り当てる (既存のオブジェクトを初期化しない) MYSQL 変数をグローバルにしている場合でも、mysql_close(MYSQL*) でメモリを解放してから、再度初期化する。



これ以外では、やはり libmysql.dll のエラーで、バージョン違いの可能性が高い。

MySQL のインストール/アンインストールを繰り返していると、以前のファイルが削除しきれなかったりする。

アンインストールする場合は、必ず先にサービスの停止・削除を実行する (サービスとしてインストールしている場合)

削除方法は、コマンドプロンプトで以下を実行する (もちろんサービスを停止してから)

$(MySQL)\bin> mysqld -remove mysql

その後、アプリケーションの追加と削除で MySQL を削除する。 MySQL 関連のフォルダを全て削除 (もちろんだが、データベースのバックアップはお忘れ無く)

そしてここが一番忘れやすいところで、$(BCB)\lib\libmysql.lib の削除と、Builder で設定しているインクルードパスなどの確認 (libmysql.dll をリンクしている箇所)

それと、libmysql.lib と .dll を他の場所に (アプリフォルダとか) コピーしている場合はそれらの削除。

以上を確実に実行してから再インストールを行う。

その後、前回の設定編をもう一度実行するべし!

・ 初期化関数は通るが、接続関数 mysql_real_connect() で接続できない

先ず最初にコーディングを確認する
if (mysql_real_connect(mysql,host,user,passwd,db,port,unix_socket,client_flg) == NULL)
当然だが、ホスト (localhost ?)、ユーザ (root ?)、パスワード、データベース名、ポート番号が間違っていないかチェックする。(データベース名は NULL でも構わない) あと、最後の2つの引数は NULL と 0 にする

間違っていなければ、ファイアウォールが怪しい。

つまり、ポート番号 3306 (既定値。変更しているならその番号) がブロックされている可能性が高い。


当然だが、MySQL はホストを localhost にしていて、スタンドアロンで動かしていても、ポートは開いていないとダメ。



実際のコーディングでの注意


それでは、実際のコーディングの際の注意点をいくつか。

・ C API の既存の関数を使用するのはなるべく避ける
例えば、mysql_create_db(MYSQL *mysql, const char *db) 関数 リファレンスにもあるように、この関数は現在廃止されている。 何故廃止されたのかを考える。

リファレンスには、「廃止されている」の後にこう続く、

”代わりに、mysql_query() を使用して CREATE DATABASE ステートメントを発行してください”

つまり、通常コンソールで打ち込むクエリをプログラムから発行しろと言っている。


SQL 文をせっせと作っていた昔を思い出すではないか!

後戻りのようだが、実はこれが一番速くて賢いやり方なのだ!

賢いとは、どの言語でも、他のクエリ型データベースでも、同等の事が可能だし移植もそんなに手間は掛からないだろう。

現在私は、PHP を経て Java、C# で MySQL に接続しているが、例えば、Connector/Net でもコマンド発行メソッドがあり、戻り値をチェックしてユーザに提示するといった仕組みは、今回と何ら変わらない。

.NET には DataTable というメモリに保持しておくような便利なものがあったりするが、正に付加価値であって基本的な部分は全くと言って良いほど変わらない。

ただし、このクエリ発行メソッドの他に、レスポンスを受け取るメソッド (仕組み) にはそれぞれの言語間で違いがあるので、そこに留意しなければならない。

C API なら、

mysql_init()、mysql_real_connect()、mysql_close() の初期化、接続、解放関数、

mysql_use_result()、mysql_store_result() のクエリ結果を受け取る関数、

mysql_fetch_row()、mysql_fetch_field() のようなクエリ結果を1つずつ受け取る関数

しかし、これらくらいのものだ...

・ データベース物のプログラミング鉄則を守る
何やら怪しげだが、

これはデータベースに限ったことではないが、例えばループ内には出来るだけ条件文を入れるな!というのがある。

例えばどうしてもフィールド値を毎回チェックしないといけないような状況下では仕方ないと思われがちだが、そのような場合は逆に、テーブルの構造を再検討する。

MySQL には、not null、unique、auto_increment などのフィールドオプションの他、enum、set といった列挙体フィールドがある。差し支えなければこれらを使う。

また、フィールド値のチェックは入力時 (insert、update) に厳重に行うようにする。

そうすれば、MySQL の「えっ!?もう?」という驚異的な速さを実感できるだろう。(下ネタじゃないぞ)



実際のコーディング


それでは、クエリを発行してレスポンスを表示する簡単なアプリのソースから核心部分を。

MySQL への接続や切断などは各自でやってみる。

流れ的には、

接続する際に必要なパラメータ (ホストやユーザなど) をユーザから受け取るコントロールがあるのが普通か。

接続するための関数を自前で用意し、その中でこれらのパラメータを事前にチェックしてOKなら、



// mysqlConn はグローバル変数 (MYSQL*)



if (mysqlConn)

{

    mysql_close(mysqlConn);

    mysqlConn = NULL;

}



try

{

    mysqlConn = mysql_init(NULL);

}

catch (...)

{

    _stprintf(buf,_T("initialize error: %s"),mysql_error(mysqlConn));

    ShowMessage(buf);

    mysqlConn = NULL;

    return false;

}



if (mysql_real_connect(mysqlConn,host,user,passwd,db,port,NULL,0) == NULL)

{

    _stprintf(buf,_T("connect error: %s"),mysql_error(mysqlConn));

    ShowMessage(buf);

    mysqlConn = NULL;

    return false;

}



mysql_set_character_set(mysqlConn,charset);

return true;



てな感じで。

これは接続する処理だが、最初の方で MYSQL* 型のグローバル変数をチェックして NULL じゃなかったら mysql_close() で切断しているが、これを関数化して中で NULL にするようにするのが常套か。

ちなみに、C / C++ では、MFC はよく知らないが、上記の場合 mysql_close(mysqlConn); でクローズしても、mysqlConn 変数は自動で NULL になったりしてくれない。(笑)

なので必ずプログラマが NULL にする。また、ここではイニシャライズに失敗したり、コネクションに失敗したら NULL にしている。このやり方がイヤなら、接続関数内で接続したらフラグとしてのグローバル変数を立てて、クローズしたら 0 にするというセマフォチックな方法もあるが、もしあなたが毛がフサフサな素人ならお勧めしない。

なお、接続する際に指定する「db」データベースだが、NULL でも構わない。というより、今回のようなコマンド→レスポンスの流れを再現するようなアカデミック(?)なサンプルアプリの場合は NULL の方が自然だろう。

ただし、実際のアプリケーションでは、接続だけするような行為はダメで、必ず目的を持って (例えば、あるテーブルからフィールド値を検索するみたいな) ロジックを走らせるようにする。そして目的を達成したら切断する。



で、フォームにクエリ入力用の Edit (他でいうところの TextBox) と、クエリ発行用の Button と、レスポンス表示用の Memo (ListBox や .NET では MultiLine の TextBox でも可) を貼り付けといて、



それらのコントロールの名前も一応。

Edit が Command_Edit

Button が Command_Btn

Memo が Memo1

とする。そしてクエリ発行用のボタンクリックイベントで、



if (Command_Edit->Text == _T("")) return;



int lp, len, buf_len;

int rec_cnt, field_cnt;

TCHAR command[280];

TCHAR buf[1050];



MYSQL_RES *res;

MYSQL_ROW row;



// ARRAYSIZE マクロは無かったら自前で #define する

// #ifndef

// #define ARRAYSIZE(s) (sizeof(s) / sizeof(s[0]))

// #endif // ARRAYSIZE



buf_len = ARRAYSIZE(buf);



Memo1->Clear();



if (!mysqlConn)

{

    ShowMessage(_T("MySQL に接続していません  "));

    return;

}



if ((int)_tcslen(Command_Edit->Text.c_str()) >= ARRAYSIZE(command))

{

    ShowMessage(_T("クエリ文が長すぎます  "));

    return;

}



_tcscpy(command,Command_Edit->Text.c_str());



// クエリ発行

if (mysql_query(mysqlConn,command) != 0)

{

    _stprintf(buf,_T("query error: %s"),mysql_error(mysqlConn));

    ShowMessage(buf);

    return;

}



// レスポンスを取得

if ((res = mysql_use_result(mysqlConn)) == NULL)

{

    // エラーではない (レスポンスが返ってこないだけ)

    Memo1->Lines->Add(_T("Query OK!")); // ホンマか?

    goto END_COMMAND;

}



rec_cnt = 0; // レコードカウント用 (必要なら)



Memo1->Lines->BeginUpdate(); // 大きなテーブルだとこれをしないと悲惨な目に...



field_cnt = (int)mysql_num_fields(res);



while ((row = mysql_fetch_row(res)) != NULL)

{

    rec_cnt ++;



    len = 0;

    buf[0] = _T('\0');

    for (lp = 0; lp < field_cnt; lp++)

    {

        if (row[lp]) // C だとこれが通用する

        {

            len += (int)_tcslen(row[lp]);

            if (len < (buf_len-1)) _tcscat(buf,row[lp]);

            else break;

        }

        _tcscat(buf,_T("\t"));

        len = (int)_tcslen(buf);

    }



    ((int)_tcslen(buf) > 0) ? buf[_tcslen(buf)-1] = _T('\0') : buf[0] = _T('\0');

    Memo1->Lines->Add(buf);

}



mysql_free_result(res); // 必ずやる!



Memo1->Lines->EndUpdate(); // ここで描画指示を出す





END_COMMAND: ;



Command_Edit->Clear();

Command_Edit->SetFocus(); // フォーカスをセット



実際は、while 文の中ではあんまりゴチャついてはいけない。今回は何が来るか分からないということで...

ポイントは、Connector (ODBC) を使用した時とあまり変わらないところと、フィールド・ループの中の if (row[lp]) という記述。

「.NET で MySQL を ~」でも説明したように、C# だと、たとえインデックスがフィールドカウント内であっても、アクセス違反だと叱られる。IsDBNUll() というマクロ(?)があるのでそれを使用する。

C でOKだったので、ここで随分悩んだが、そういう時は本家サイトに行くのが一番手っ取り早い。ちゃんと書いてあった!「バカ!ハゲ!」って。



という若干言語による違いはあるが、どうでしょう?特に難しいようなところは無かったと思うが?

実際のアプリケーション開発では、データベース固定だろうし、テーブルの動的生成もテンポラリテーブル以外はあまり無いように思う。

要するに殆ど決め打ちで、ベタ書きも可能だということだ。だが実はデータベース物はこのベタ書きが一番速い。

上記のサンプルのように、ループ内で条件を色々書いたりしていると、こういう塵が積もり積もって「え~まだ~?」となる。

上記のループ内では、バッファ変数のサイズをチェックしているが、desc などで前もってテーブルサイズをチェックしておくのも手だ。

お気づきだと思うが可変長のフィールドは無用の長物で、varchar などは使わないようにするのが常套手段だ。また、TEXT や MEMO 項目など昔からあるが、そもそも長文をテーブルに保存するという考え方は止めた方が良いし、BLOB など以ての外だと思う。

そういうのは昔からテキストファイルに保存したり、マルチメディアファイルそのもののパスをテーブルに保存するやり方。遅くなるかもしれないが、これしかないと思う。