四方山話 ( 2011/02/01 - 2011/02/28 )


このページは、私が思いついた時に(だから毎日ではない)、思 いついたことを(だからコンピュータだけの話ではない)、思いついたなりに (だから日本語むちゃくちゃ)徒然と書き綴るページです。



2011/02/16 (Wed)
雑駁に。そのうちにメモすると思うけど、Windowsプログラムでは、 Windowサイズを変更したとか、ボタンを押したとか、そういった場合には「 ウィン ドウプロシージャ」に処理が渡される。 ウィンドウプロシージャの引数は「HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam」となっており、第二引数 uMsg のメッセージ識別子により処理 を振り分ける。この メッセージ識別子には WM_CREATE / WM_COMMAND / WM_DESTROY / WM_LBUTTONDOWN / WM_MOVE / WM_PAINT / WM_QUIT / WM_SIZE / WM_TIMER な どなど、とても書ききれないくらいいろいろな種類がある。今回はそのうちの WM_COMMAND について。MSDN によれば WM_COMMAND の第三引数 wParam お よび第四引数 lParam の内容は以下のとおりである。
Message Source wParam (high word) wParam (low word) lParam
Menu 0 Menu identifier (IDM_*)0
Accelerator1 Accelerator identifier (IDM_*)0
Control Control-defined notification code Control identifierHandle to the control window

で、次は、wParam および lParam で処理を振り分けないといけないわけで す。上表から wParam (high word)、つまり HIWORD( wParam ) で処理を振り分 ければいいと思ったのだが、Control-defined notification code には 0 や 1 のものもあり(多分 BN_CLICKED は 0、CBN_SELCHANGE は 1)、そうすると Menu や Accelerator と区別をするのが難しい場合がある。じゃあどうするの か、wParam (low word)、つまり LOWORD( wParam ) ですることになるのかなぁ ?と思うわけです。この LOWORD( wParam ) の Menu / Accelerator / Control identifier は(多分)プログラマが勝手に(自由に)定義するものだと思う ので、これらを unique なものにすれば問題は解決する...というのが現在の当 方の公式見解(間違っていたらごめんなさい)。そんなわけで、まずは MSDN の「 Symbols: Resource Identifiers」とか「 Symbol Name Restrictions」とかを見て、Menu / Accelerator / Control identifier の命名に関する「マイルール」を検討してみた が、なかなか納得のいくものができず。そして「もう少し勉強を進めて、幅広 い知識がついた段階で再度検討してみよう」という結論に達したのでした。

2011/02/15 (Tue)
スラ ドより。自分のWindowsパソコンの性別を調べる方法(笑)。以下の WSH を 実行し、聞こえてきた声から判断するというもの。ちなみに我が家の Win7(64bit) は(あまり可愛くない)女性の声でした。
CreateObject("SAPI.SpVoice").Speak"I love you"
2011/02/08 (Tue)
このところの Windowsプログラミングで、ソースを載せる際に & を &amp; に、" を &quot; に、< を &lt;に、> を &gt; に置換するのが非常に面倒で面倒で、ものすごく辟易していた。で、 いまさらながらに行番号を付ける WSH catn.vbs を改造してみた。よしよし、これでまた一つ楽ができる。あと、 一つネタができた(笑)
プログラム catn2.vbs
 1: If WScript.Arguments.Count <> 1 Then
 2:    WScript.Echo "Usage: catn2.vbs [filename]"
 3:    WScript.Quit 1
 4: End If
 5:
 6: Set oWSHShell   = WScript.CreateObject( "WScript.Shell" )
 7: Set oFileSystem = CreateObject( "Scripting.FileSystemObject" )
 8:
 9: If oFileSystem.FileExists( WScript.Arguments(0) ) = False Then
10:    WScript.Echo "ファイル " & WScript.Arguments(0) & " が見つかりません"
11:    WScript.Quit 1
12: End If
13:
14: iLine = 1
15: Set oDataFile  = oFileSystem.OpenTextFile( WScript.Arguments(0) )
16: Do While oDataFile.AtEndOfStream <> True
17:    sData = Replace( oDataFile.ReadLine, "&", "&amp;" )
18:    sData2 = Replace( sData, """", "&quot;" )
19:    sData = Replace( sData2, "<", "&lt;" )
20:    sData2 = Replace( sData, ">", "&gt;" )
21:    WScript.Echo iLine & ": " & sData2
22:    iLine = iLine + 1
23: Loop
24: oDataFile.Close
2011/02/07 (Mon)
本日のネタは「一度に複数のフィールド値を指定してレコードを追加する 方法(配列編)」です。別に「AddNew → フ ィールド値設定 → Update」方法で事足りており、いまさら配列編をする 必要もないと思うのですが... 皆さんの熱い要望で、すみません 、そんな要望は全くありませんでした、お詫びして訂正します。そんなわけで、 折角苦労して作ったんだし、非常にもったいないので備忘録としてここに載せ ておくことにしました。きっと何かの際には役に立つ...と信じたい。
サンプルプログラムNo.d05
 1: #import "C:\Program Files\Common Files\System\ADO\msado15.dll" \
 2:         no_namespace rename( "EOF", "EndOfFile" )
 3: #define  UNICODE
 4: #define  _UNICODE
 5: #include <ole2.h>
 6: #include <Objbase.h>
 7:
 8: #define  FIELD_NUM  2
 9:
10: int wmain( int argc, wchar_t *argv[] )
11: {
12:    if( FAILED( ::CoInitialize( NULL ) ) ) return 1;
13:
14:    _ConnectionPtr pCnn( L"ADODB.Connection" );
15:    _RecordsetPtr  pRst( L"ADODB.Recordset" );
16:
17:    try {
18:       pCnn->Open( L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\\home\\data.mdb;",
19:                   L"", L"", adConnectUnspecified );
20:       pRst->Open( L"data2", _variant_t((IDispatch *) pCnn, true),
21:                   adOpenStatic, adLockOptimistic, adCmdUnspecified );
22:
23:       VARIANT   *vp;
24:       SAFEARRAY *saField, *saData;
25:
26:       saField = SafeArrayCreateVector( VT_VARIANT, 0, FIELD_NUM );
27:       SafeArrayAccessData( saField, (void **)&vp );
28:       vp[0].vt      = VT_BSTR;
29:       vp[0].bstrVal = _bstr_t( L"ID" );
30:       vp[1].vt      = VT_BSTR;
31:       vp[1].bstrVal = _bstr_t( L"field1" );
32:       SafeArrayUnaccessData( saField );
33:
34:       saData  = SafeArrayCreateVector( VT_VARIANT, 0, FIELD_NUM );
35:       SafeArrayAccessData( saData, (void **)&vp );
36:       vp[0].vt   = VT_I4;
37:       vp[0].lVal = 12;
38:       vp[1].vt   = VT_I4;
39:       vp[1].lVal = 65;
40:       SafeArrayUnaccessData( saData );
41:
42:       VARIANT  vField, vData;
43:       vField.vt     = VT_ARRAY | VT_VARIANT;
44:       vData.vt      = VT_ARRAY | VT_VARIANT;
45:       vField.parray = saField;
46:       vData.parray  = saData;
47:
48:       pRst->AddNew( vField, vData );
49:    } catch( _com_error &e ) {
50:       printf( "Error: %s\n", e.ErrorMessage() );
51:       printf( "警告:%s\n", (char*)e.Description() );
52:    }
53:    if( pRst )
54:       if( pRst->State == adStateOpen )
55:          pRst->Close();
56:    pCnn->Close();
57:    ::CoUninitialize();
58:    return 0;
59: }

少しだけ解説。AddNewメソッドについては先の解説通り、配列を引数とす ることで複数フィールドを一度に指定してレコードを追加することができる。 ということだったので「_variant_t vField[2], vData[2];」としてみたが、 うまくいかなかった、というのが前回までのあらすじ。あと、VARIANT型とい うのは様々なデータ型を扱えるというのも前回までに説明済み。で、この VARIANT型は「配列(VT_ARRAY)」も扱えるということにデータ型を表す定数 の表を作っていて気が付いた。なるほど、じゃあ「vParam1.parray = vField[2]」 みたいな感じでいいのかな?とおもったんだが、そうは問屋が卸さない、この 場合 SAFEARRAY型を使うんだそうだ。また、なんか聞いたことない言葉が出て きたなぁ... 仕方がなく、いろいろ調べてみたところ、SAFEARRAY型は SafeArrayCreate関数で作成するものらしい。ただ、今回必要な配列は一 次元だから、 SafeArrayCreateVector関数で作成してみた。で、コンパイルして、実行 (実行結果 [ PNG file, 4KB ])して、特段 問題なし。よかった、よかった。しかし、見ていただいた通り、この方法でレ コードを追加するのは非常に面倒くさい。正直、「AddNew → フィールド値設 定 → Update」方法の方が、分かり易いし、多分、実行速度も速いと思う(未 実験だが、SafeArrayAccessData関数と SafeArrayUnaccessData関数を呼び出 すオーバヘッドを考えれば、上記方法の方が遅いと思われる)。ということで 、この方法を使うメリットがよくわからないので、今後、この方法を使うこと はないでしょう。ただ、SAFEARRAY型は使うかもしれないので、頭の隅には残 しておこう...

2011/02/06 (Sun)
さて、このところの iniファイル関係、DB関係、Network関係のサンプルプ ログラム作成で、ある程度のアプリを作成するのに必要な知識がついた...と思 う。そんなわけで少し前から本格的なプログラムの作成を始めているのだが、 作成を進めていくと Window関係、UI関係の知識が不足していることに気がつい た。というのも debug に MessageBox関数を使うといちいち OK ボタンを押す のが面倒だし、もう少しプログラムを作っていくとユーザになんらかの情報を 提供したり、ユーザから情報をもらったりということもしないといけなくなる だろうし...ということで、CreateWindow関数( 日本語 / 英語)、CreateWindowEx関数( 日本語 / 英語)で使える(と思われる)Control Library の一覧をまとめてみた。 と言っても MSDN へのリンクなんだけど。このへんが一通り使えるとそれなり の見栄えがするプログラムができる...はず。で、使い方とかはこれからゆっく り(というほど時間があるわけじゃないけど)サンプルプログラムでも作りま す。が、これが意外と難しいんだよな...そういわれればこっち方面は Window開いたところで終わってるんだよな...
(追記)ちょっと毛色は違うと思うけど、「 Windows Ribbon Framework」というのもある。ま、私は嫌いなので、使う ことはないと思うけど。(2011/02/09)

2011/02/05 (Sat)
週末は引き籠ってプログラム作成。そんなんでいいのか>俺 ま、貧乏人 には無駄な出費が抑えられて、skill up にもなって一石二鳥か。そんなわけ で次回更新は三ヶ月後を予定していたのだが、とりあえず先月のまとめてとし て DB サンプルプログラムをちょこっとだけ改造してみた。とはいっても、ん なたいしたものじゃないんだけど...ここ数年、(多分)若年性 痴ほう症により記憶力が極端に低下しているから、ここにちゃんと書いておか ないと、すぐに忘れちゃうんですよね...
サンプルプログラムNo.d05
 1: #import "C:\Program Files\Common Files\System\ADO\msado15.dll" \
 2:         no_namespace rename( "EOF", "EndOfFile" )
 3: #define  UNICODE
 4: #define  _UNICODE
 5: #include <ole2.h>
 6: #include <Objbase.h>
 7:
 8: int wmain( int argc, wchar_t *argv[] )
 9: {
10:    if( FAILED( ::CoInitialize(NULL) ) ) return 1;
11:
12:    _ConnectionPtr pCnn( L"ADODB.Connection" );
13:    _RecordsetPtr  pRst( L"ADODB.Recordset" );
14:
15:    wchar_t *DatabaseName = L"D:\\home\\data.mdb";
16:    _bstr_t sCnn( L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" );
17:
18:    try {
19:       pCnn->Open( sCnn + _bstr_t( DatabaseName ), L"", L"", adConnectUnspecified );
20:       pRst->Open( L"min", _variant_t((IDispatch *)pCnn, true),
21:                   adOpenStatic, adLockOptimistic, adCmdUnspecified );
22:
23:       _variant_t vDate;
24:       SYSTEMTIME stDate;
25:       GetLocalTime( &stDate );
26:       SystemTimeToVariantTime( &stDate, &(vDate.date) );
27:       vDate.vt = VT_DATE;
28:
29:       long LIndex;
30:       pRst->AddNew();
31:       LIndex = (long)0;
32:       pRst->Fields->Item[LIndex]->Value = vDate;
33:       pRst->Update();
34:    } catch( _com_error &e ) {
35:       printf( "Error:%s\n", e.ErrorMessage() );
36:       printf( "警告:%s\n", (char*)e.Description() );
37:    }
38:    if( pRst )
39:       if( pRst->State == adStateOpen )
40:          pRst->Close();
41:    pCnn->Close();
42:    ::CoUninitialize();
43:    return 0;
44: }
コンパイル結果および実行結果
[D:\home] cl /EHsc sampleD5.cpp
((省略))
[D:\home] sampleD5.exe
Error:IDispatch error #3092
警告:SQL ステートメントが正しくありません。 'DELETE'、'INSERT'、'PROCEDURE'、'S
ELECT'、または 'UPDATE' を使用してください。

[D:\home]

改造点。本格的なプログラムを考えると DBファイル名は定数ではなく、変 数で与えられるんじゃないかと。そんなわけで DB ファイル の path をわざわ ざ wchar_t * 変数にしてみました(15〜19行)。次は、日付データを DB に保 存してみた(23〜32行目)。あと、もう少し詳しいエラーメッセージを出して くれる e.Description() を追加(36行目)。ちなみに返り値は MBCSなので、Unicode として 表示したい場合には、「 mbstowcs、_mbstowcs_l関数」とか「 mbstowcs_s、_mbstowcs_s_l関数」とか「 MultiByteToWideChar関数」なんかで変換してやる必要がある。最後に pCnn をクローズ(41行目)。で、実行結果ですが、エラーになっていたんじゃ意味 がない。で、その原因が「table 名」にあることに気が付くのにえらく時間が かかりました。min でも駄目、minute でも駄目、最後はやけくそで「分データ 」にしたらちゃんと動いた orz。そんなわけで、MS-Access で table 名を「分 データ」に直すとともに、20 行目を「pRst->Open( L"分データ" (省略)」にすることで無事動きました。ちなみに Provider を「 Microsoft.ACE.OLEDB.12.0」に変えてみたら、実行時にエラー「 Unknown error 0x800A0E7A」「プロバイダーが見つかりません。正しくインス トールされていない可能性があります。」が出た。んー、おかしいなぁ。WSH じゃうまくいったのに。なんでだろう ...といろいろ調べてみたところ、install したのは Access2010 の 64bit 版 で、となると install された ACE.OLEDB の DLL (多分 C:\Program Files\ Common Files\Microsoft Shared\OFFICE14\ACEOLEDB.DLL)も 64bit、プログラ ムは 32bit。32bit プログラムから 64bit DLL は利用できない、ということで うまく動かないらしい。じゃあ 64bit プログラムだとちゃんと動くのか?とい う疑問が生じたが、普通の VC++ 2010 Express では 64bitプログラムは作成で きないみたいなので、この件の検証はまた時間ができたら。



Copyright(C) 2011 H.Fujiuchi inserted by FC2 system