諸君,私は WindowsMe が好きだ。「ある意味世界最強」と名を冠する OS が好きだ。インストールする毎に,インストーラの挙動からして少しずつ違ってくる OS が大好きだ。ブルースクリーンで「このまま続けても構いません」などとヌかしながら全然続けられない OS がたまらなく好きだ。
いや,でも実際,あまり大きなトラブルには出会わないんですよね。運がいいだけなのか何なのか・・・。
というわけで昨日,WindowsMe を再インストールしました。そして WindowsUpdate で必要な更新をしていたら案の定デスクトップが「鈍感モード」に。具体的には,デスクトップにファイルを生成しても即座には表示されなくなったのです。しかしそこは手馴れたもので,デスクトップをアクティブにし,F5 キーを押すことでようやく画面の再描画が起こります。十中八九アップデートが何かやらかしたに違いありません。仕様とも考えられますが,これってバグにしか見えないっすよねえ?
日付に関するエキスパートとか呼ばせてもらっていいですか。普段はぜんっぜん気にしていない「暦」なんだけど,史学云々を語る上では「ユリウス暦」とかもかなーり重要っぽいですよね。プログラマたるものキチンとこういう部分も押さえて,必要に応じてコードを使い分けられるべきだと思いました。
ところで「2038 年問題」ですが・・2038 年というと私は 60 歳くらいですか。そろそろ前線からは引退しているはずなので問題ありませんとも。
まず Windows 上の解決法ならあまり難しくはありません。GetLocalTime() 関数を使っておけば 2038 年を超えても正常に日付を取得できます。実験中に桜時計が誤動作して ( それとも NTP あたりの仕様? ) 時計が 1930 年に遡ってしまったのですが,とにかく全然気にしなくてよいってことです。ちなみに時計を 2039 年以降に進めると,一瞬にして UD が落ちちゃいました。おいおい・・・
UNIX / Linux 上ではやはり ANSI 標準ということもあり,至る所で time(time_t *) や gmtime(),localtime() が使われています。もっとも 2038 年までに存在するすべてのマシンと OS が 64bit になってしまえば問題が解決するうえに,実際に 64bit 化はかなり進んでるんですよね? time() 関数が 64bit の値を返してくれさえすれば,人類はきっと次の問題に遭う前に滅亡しちゃうんで安心。
ちなみに 2038 年を超えると 32bit の time() 関数の戻り値は 0 に戻ってしまうのではなくマイナスの値を返すようになるようです。というのも time_t がほとんどの実装において signed long 型に等しいからですね。わりと誤解されている方も少なくなかったりして。
日付を 2037 年以降にすると「桜時計」のような NTP クライアントが日付を思いっきり過去に戻してしまうのは,NTP の仕様っぽいですね。2037 年 1 月 1 日以降,現在の NTP では正常な通信や時刻合わせができないようです。このあたりはまだ全然勉強する余裕がないんだけど,いつか完全にマスターしたいですねー。
それから追記のついでに・・・gmtime() や localtime() はマイナスの値を入力されると失敗し (tm*)NULL を返します。だからアプリケーションは「む,今は 1902 年 ( 1970 年の 68 年前 ) なのな?」と判断してしまうのではなく,NULL ポインタの使用によりアクセス違反で落ちてしまうってことですね。これは UD が落ちちゃった原因だと思います。
なんと,これから Windows を再インストールするのです。というのも ── この前インストールしたのは 19 日だったかな? ── どうも TV アプリケーションかサウンドドライバか,どちらかのインストールに失敗した模様なのです。TV アプリケーションでビデオを観ようとすると,スピーカーの右側からしか音が出なくなってしまいます。
というわけで,覚悟しろ自分。
そこでふと思いついた事なんですが,なんていうのかな,Windows の "やり方" にガチガチに頼ったアプリケーションはこういう時に困ったものです。例えばアプリケーションが自分の設定をレジストリに書き込んでいる場合,その設定を引き継ぐにはバックアップをとる必要が出てきます。しかし普通のアプリケーションは使用するレジストリなどは公開していないため,必要なものだけをピンポイントでバックアップできるわけではありません。だから各レジストリの software あたりについて根こそぎバックアップを取るわけですが,Windows の再インストール後,安易にそれをマージしちゃってよいものでしょうか? というか往々にして悪影響が出るっぽいです。
レジストリに限らず,例えばあなたがお使いのメールソフトでは,受信したメールがどこに保存されているかご存知ですか? 必要な時に必要なメールについてきちんとバックアップを取る事ができますか? きっと半分以上の人は No なんじゃないかなと邪推。
ソフトを作るにあたって「必要な時に必要なものがバックアップできる」という設計は非常に重要だと思いました。
ソフトウェア製作の作法について研究するサイト。実際,ファイル名を表示するのにプロポーショナルフォントを使っている例は非常に多く見られます。というか,99% のソフトはそんな感じです。
Mozilla たんさえも URL をプロポーショナルフォントで表示させやがるんで萎え。
見にくさ度:384
ひとまずプリンタ印刷処理のネタはこのあたりで区切りをつけようと思いました。でもプリンタ周辺のネタはいろいろあるんで,ぼちぼち小出しして行こうと思ってます。
今日は今までのまとめとして,実際に何かを印刷できるコードを書いてみようと思います。何かをと言ってもどうせ文字くらいしか出力しないっすけどね。
#include <windows.h>
#include <string.h>
const char *printerName = "K-M Printer MkII";
HANDLE GetDevMode(HANDLE hPrinter, const char *cpszPrinter);
int main() {
char *_printerName = const_cast<char *>(printerName);
HANDLE hPrinter;
PRINTER_DEFAULTS pdef = {0};
pdef.DesiredAccess = PRINTER_ACCESS_USE;
::OpenPrinter(_printerName, &hPrinter, &pdef);
HANDLE hDevMode = GetDevMode(hPrinter, printerName);
DEVMODE *pDevMode = (DEVMODE*)::GlobalLock(hDevMode);
pDevMode->dmDuplex = DMDUP_HORIZONTAL;
pDevMode->dmCollate = DMCOLLATE_TRUE;
pDevMode->dmFields |= DM_DUPLEX | DM_COLLATE;
::DocumentProperties(NULL, hPrinter, _printerName,
pDevMode, pDevMode, DM_IN_BUFFER | DM_OUT_BUFFER );
HDC dc = ::CreateDC(NULL, printerName, NULL, pDevMode);
::GlobalUnlock(hDevMode);
DOCINFO di = {0};
di.cbSize = sizeof(DOCINFO);
di.lpszDocName = "Test Document (*'-')";
::StartDoc(dc, &di);
::StartPage(dc);
// フォント生成
LOGFONT font = {0};
font.lfHeight = ::GetDeviceCaps(dc, HORZRES) / 60;
font.lfCharSet = DEFAULT_CHARSET;
font.lfOutPrecision = OUT_DEFAULT_PRECIS;
font.lfClipPrecision = CLIP_DEFAULT_PRECIS;
font.lfQuality = DEFAULT_QUALITY;
font.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
strcpy(font.lfFaceName, "MS ゴシック");
HFONT hFont = ::CreateFontIndirect( &font );
HFONT oldFont = (HFONT)::SelectObject(dc, hFont);
COLORREF oldColor = ::SetTextColor(dc, RGB(0,0,0) );
int oldBkMode = ::SetBkMode(dc, TRANSPARENT);
const char right_side[] = "こっちはオモテ面です";
const char wrong_side[] = "こっちはウラ面です";
::TextOut(dc, ::GetDeviveCaps(dc, PHYSICALOFFSETX),
::GetDeviveCaps(dc, PHYSICALOFFSETY),
right_side, sizeof(right_side)-1 );
::EndPage(dc);
::StartPage(dc);
::TextOut(dc, ::GetDeviveCaps(dc, PHYSICALOFFSETX),
::GetDeviveCaps(dc, PHYSICALOFFSETY),
wrong_side, sizeof(wrong_side)-1 );
::SetBkMode(dc, oldBkMode);
::SetTextColor(dc, oldColor);
::SelectObject(dc, oldFont);
::DeleteObject(hFont);
::EndPage(dc);
::EndDoc(dc);
::DeleteDC(dc);
::GlobalFree(hDevMode);
::ClosePrinter(hPrinter);
return 0;
}
うあー,随分長くなってしまいました。これでも一般にコードの半分を占めると言われる「エラーチェック」を行っていないので笑っちゃいます。そうそう,変数 printerName はそれぞれの環境に合わせて書き直して下さい。
ん,よい加減で疲れてきたっぽいので,今日はこれにて終了っ。
疲労度:8258592345
こういうコードは,(特に業務では) あまりほめられたもんじゃありませんが……。
TextOut() で文字列を出力する際,上記コードでは sizeof 演算子を使って文字列の長さを測ってみました。普通,文字列の長さを調べるとすれば strlen() を使いますよね。でも上記のように sizeof が使える場面では,こちらの方がコンパイル時に文字列の長さをあらかじめ計算できるため,実行が高速なのです:対して strlen() は実行するたびにいちいち文字列の長さを調べてしまいます。
今日び strlen() ごときの実行速度を気にするのは生産的ではありません。でも,こういう知識は,邪魔にはなりませんもんね。
ただし,こんな感じで sizeof 演算子を使うのは間違いですから。残念。 (←アレ風)
// sizeofの間違った使い方
const char *text = "string";
size_t size = sizeof(text)-1;
文字列リテラル "string" は,要するに 's','t', 'r','i','n','g','\0' の 7 バイト。sizeof の結果は 7 になって,そこからマイナス 1 をしているので,変数 size の結果は 6 ? いいえ。実行してみると,4 になったり 8 になったりするはずです。間違えた人は,sizeof 演算子がナニを測るモノなのかをよく勉強し直してみましょう。
あ,「ほめられたもんじゃありませんが」とは書きましたが,結構多くの場面で使われています。mozilla では実に効果的に使われているし,(仕事上の守秘義務で,どこまで詳しく解説していいのか分かんないけど)某携帯機の OS の SDK でだって。……もっとも,マクロによって見えなくされているので,目にする事は少ないのですが。
そうだ,大事なサイトの紹介を忘れていました。初めてプリンタを弄るにも関わらずなんとか納期までに私の仕事が終わりそうなのは,この方たちのおかげ。
「中村の里」( 中村拓男さん ) では 「TNkPrinter」という高性能なプリンタ制御クラス ( Delphi 用なんで「コンポーネント」と呼ぶべき? ) を配布しています。また中村さんご自身もいろんな ML で Delphi のディープな部分を解説したりと,非常に積極的な活動をされています。
そして「Win32サブルーチンズ」( 常岡伸二さん ) は,そのままの名を持つ「Win32サブルーチンズ」という本の紹介をしています。しかしただの紹介ではありません。実はその「Win32サブルーチンズ」は絶版になったのですが,出版社と話し合いながらなんと Web 上で復活しました。
最近はこのように深い部分を勉強する機会がすっかりなくなりました。「車輪の再発明」とか言って非難されるのがオチ。しかしあれだ,その「車輪」さえ作れない技術者には用も未来もありません。・・・ですよね?
で,今日は今までのまとめとして上手に印刷できるようなコードを実際に書いて行きたかったのですが,ちょっとボク気力が残ってません。とりあえず 2002-08-03 の GetDevMode() 関数を,2002-08-24 でツッコミを入れた通りに修正することにします。
// 改良済みコード
HANDLE GetDevMode(HANDLE hPrinter, LPCTSTR pcszPrinter) {
char *pszPrinter = const_cast<char *>(pcszPrinter);
// Get the size needed for the DEVMODE structure
DWORD dwSize = ::DocumentProperties(NULL, hPrinter, pszPrinter,
NULL, NULL, 0);
if (!dwSize) return NULL;
// Allocate a DEVMODE buffer
HANDLE hDevMode = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE, dwSize);
if (hDevMode == NULL) return NULL;
// Lock memory (sheesh ... )
LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(hDevMode);
if (!pDevMode) {
::GlobalFree(hDevMode);
return NULL;
}
// DEVMODE を取得する
::DocumentProperties(NULL, hPrinter, pszPrinter,
pDevMode, NULL, DM_OUT_BUFFER);
::GlobalUnlock(hDevMode);
return hDevMode;
}
エラー時の処理で ClosePrinter() がなくなった分,若干見やすくなった気がしますね。
疲労度:210847923
プリンタ制御の解説もいよいよ終盤です。今日はもうちょっと注意しないといけないことや,ネット上でなかなか見つけられない情報をまとめてみました。
なんだかんだ言いながら,DEVMODE 構造体の中で一番重要なメンバがこれ。ここに適切なフラグを立てなければ,いくら CreateDC() や ResetDC() に DEVMODE を渡したって無駄です。
既に 2002-08-24 でも取り上げた事です。DEVMODE 構造体を適切に取得して CreateDC() に渡すために,DocumentProperties() は 3 度呼ばれます。1 度目は DEVMODE 構造体のサイズを得るため,2 度目は実際に DEVMODE 構造体を得るためですね。そして 3 度目,一体何の目的で DocumentProperties() を呼ぶのかが謎だ謎だと悩んできました。
でも本当は悩むまでもなく,DocumentProperties() を呼び出すべきなんでしょうね。何よりも MSDN に「呼べ」と書いているし,呼んでも呼ばなくても同じに見えるならば呼んで損はないはずです。
普通にウィンドウアプリケーションを作成している時,DC には「表」だの「裏」だのという概念がありません。しかし印刷する場合には突然「裏」「表」なんかが出てきたりして仰天。一体 DC のどこに描画すれば「裏」に描けるんだろう?
正解は,改ページ処理後の DC に描画すればそこが「裏」なんですよね。改ページを行うには EndPage() で現在のページの印刷を終了し,直ちに StartPage() で新しいページの印刷を始めます。冷静に考えればすぐに分かりますが,しかし「ひょっとしたら意外な結末が・・」と疑うのも問題を克服するためには重要なことだし。
ResetDC() は DC に DEVMODE 構造体データを関連付けます。もしくは関連付け直します。これに関して注意すべき事の一つは 2002-08-25 に。
さらにツッコミを入れるならば ( 実は 2002-08-25 でもこっそりと触れていますが ) MSDN を紐解いてみると,MFC,CDC::ResetDC の解説にこうあります。
このメンバ関数を呼び出す前に、デバイス コンテキストが選択したストック オブジェクト以外のすべてのオブジェクトの選択を解除します。
私が今扱っているのは MFC ではなく素の SDK ですが,もしかしたら SDK でも同様にオブジェクトの選択を開放すべき? とりあえず,この事は考慮しておくに越した事はありません。
んー,だいたい注意すべきなのはこれくらいかな。明日は今までの事を踏まえて,簡単な「良い」コードを書いてみたいんですが・・・時間はあるかな? もうそろそろ仕事の納期が迫っているので大変です。
疲労度:57612487
佳境の予感です。・・・いえ,プリンタってのはわりと奥が深いし,そういえばまだ PrinterProperties() を弄ったりとかもしてないんスけどね。だから本当の意味でのクライマックスとは言えません。でも,今回の件で私がみんなに喚起したかった注意はまさにこれ。
「両面印刷」に注意しなければなりません。おととい,昨日と,たびたびその話題を出しているんですが。
恒例の注意:ここに書いてある事は,実際に私が会社で出会った問題です。しかし皆さんの場合はもしかしたら何の苦労もなくやりたい事が実現できるかも知れません。それはプリンタの機能や性能の違いによるものです。( ですよね? )
まず,両面印刷を行うには DEVMODE 構造体のメンバ dmDuplex を適当な値にし,dmFields に DM_DUPLEX ビットを立ててやります。これは基本ですね。そしてデバイスコンテキストを生成し,2 ページ以上何か描いてやります。このあたりの注意事項は昨日の雑記でまとめていますが,CreateDC() に DEVMODE 構造体を渡してやるべきです。ちょっとひねくれて ResetDC() を使おうとは考えない方が無難です。
pDevMode->dmDuplex = DMDUP_HORIZONTAL;
pDevMode->dmFields |= DM_DUPLEX;
if (persist) // 本当に必要なのかな?
::DocumentProperties(...);
HDC dc = ::CreateDC(NULL, "K-M Printer MkII",
NULL, pDevMode );
::StartDoc(dc); // 印刷処理の始まり
::StartPage(dc); // 1ページ目の始まり
/* 何か描く */
::EndPage(dc); // 1ページ目終わり
::StartPage(dc); // 2ページ目の始まり
/* 何か描く */
::EndPage(dc); // 2ページ目終わり
::EndDoc(dc); // 印刷終わり
::DeleteDC(dc);
さて,ここで何気なく Microsoft WORD を起動し, 2 ページほど適当に何か書きます。そして「両面印刷」・・・
で き ま せ ん で し た 。
表ページを印刷すると紙が完全に排出され,プリンタが「紙をよこせ」とお怒りになります。おかしいなあ・・・。会社のプリンタは両面印刷を行った時,紙が排出される寸前で止まります。インクを乾燥させているんですね ( ちなみに,大抵,この乾燥時間はプリンタの設定により調整が可能です )。そして再び紙がとりこまれ,裏に印字が行われて排出されるはずなのです。
しかし Microsoft WORD でさえも両面印刷が機能してくれないとは,かなり重症です。何か特別なアクセス権でも必要なのかな? ネットワークプリンタでは両面印刷できないのかな? それとも実験のしすぎで壊れちゃったのかなあ?
印刷設定のダイアログを適当にいじりながら印刷すると,
できましたー♪ ( アレ風 )
すなわち,原因は「部単位で印刷する」設定でした。「両面印刷」を「部単位で」印刷する場合,まず表のページを部数だけワッシワッシと印刷し終えてから,然る後に裏のページを印刷していきます。会社のプリンタの場合,表のページを 1 枚印刷し終わる毎にその紙を完全に排出しなければ,次の紙に印字できません。そして紙を完全に排出するということは,プリンタのコントロールから離れてしまうということ。覆水盆に還らず。必然的に「部単位で印刷」する場合は「両面印刷」は自動ではできない,という事になってしまいます。
実を言うと,私の書いたコードではこの事を気にせずとも両面印刷には成功しました。きっとデフォルトで DEVMODE の「部単位で印刷」が無効になっていたのでしょう。しかし世の中,何が起こるか分からないのです。特に今回の仕事で私はまだ一度も本番用のプリンタを触っていません。コード中の DEVMODE も,きちんと「部単位で印刷」の設定を無効に設定し直さなければならない事もあるはずです。その時に備えて次のようにするべきです。
if (flag) { // もしも必要ならば
pDevMode->dmCollate = DMCOLLATE_TRUE;
pDevMode->dmFields |= DM_COLLATE;
}
コレを突き止めるのにちょっと苦労しました。ネット上でこの事に触れた記事が皆無なんですよね。・・・まだまだこのような「罠」が,どこかに潜んでいるような気が・・・。
疲労度:54265793
今現在,まだこの記事を書いている最中です。しかしあらかじめこのような追記スペースを用意するのは大事な事。それは・・・「部単位で印刷」を無効にする値は DMCOLLATE_TRUE だったのか DMCOLLATE_FALSE だったのかを忘れたからです。語感からすれば DMCOLLATE_FALSE なんだけど,MSDN を見る限り,どうもそれは「部単位で印刷」を有効にするように読めるんですよね。家では実験すらできないので,明日,会社であらためて実験しなければなりません。
はい,追記です。「部単位で印刷」を無効にするには dmCollate = DMCOLLATE_TRUE; でよいようです。逆に部単位で印刷をしたいなら,次のようなコードを書きます。
pDevMode->dmCollate = DMCOLLATE_FALSE;
pDevMode->dmCopies = 10; // これだけ印刷
pDevMode->dmFields |= DM_COLLATE | DM_COPIES;
なんか複雑なんだけど,仕方がないのかな。
ここ 1 週間は ( 台風の影響で? ) 比較的涼しかったので,「光化学スモッグ注意報」は発令されませんでした。しかし今日は久しぶりに発令。曇り空のくせに暑いんですよね。多少鬱が入ります。
昨日に引き続いて,2002-08-23 のコードにツッコミを入れます。昨日は DEVMODE 構造体の設定の仕方について概略を解説してみました。今日の文章もやはり間違っている事を書いている可能性もあるので注意?
あちこちのサンプルを見ていると,DEVMODE に設定した情報を用いて実際に印刷処理をするには,昨日の解説のように ( ・・昨日の件について,未だになんだか散々悩んで「追記」とかを繰り返してるけど ) DocumentProperties() で「設定する」ような気がしてしまうんですよね。でもそれはちょっとハズレで,正解は,我らが「DC」すなわち「デバイスコンテキスト」に対して DEVMODE を与えてやらなければなりません。むしろ,なんだか DocumentProperties() は無くてもよいような結論が出そうなんですが・・・最終的な結論を出すにはもうしばらくかかるでしょう。
DC に DEVMODE を与えるには,2 通りの方法があります。すなわち CreateDC() の第 4 引数に与えるか,もしくは ResetDC() に与えるかですね。DC を生成する時なら,ついでだから CreateDC() に与えるべきです。ResetDC() が活躍するのは改ページ処理時,つまり EndPage()〜StartPage() の間で設定を変える場合です。
// 改ページ処理
void nextPage() {
::EndPage(dc);
// 日本語 MSDN,MFC の CDC クラスの解説によると
// ResetDC の前には,DC に関連付けられている
// ストックオブジェクト以外のオブジェクトを
// 全て破棄しないといけない・・・らしい・・・
::SelectObject(dc, hFontOld); ::DeleteObject(hFont);
::SelectObject(dc, hBrush); ::DeleteObject(hBrush);
/* オブジェクトの破棄は以下略 */
// トレイを変更してみる
pDevMode->dmDefaultSource = DMBIN_TRACTOR;
pDevMode->dmFields |= DM_DEFAULTSOURCE;
if (persist) // ていうか,必要なのかなあ??
::DocumentProperties( ... ); // 引数は略
::ResetDC(dc, pDevMode);
// 次のページ開始
::StartPage(dc);
}
私はこれで「プチ嵌まり」を起こしてしまいましたが:CreateDC() には DEVMODE を渡さず,その直後に ResetDC() を呼び出す事で DC に DEVMODE を与える事も可能です ( それが必要となる理由が想像できないけど )。
HDC dc = ::CreateDC(NULL, "K-M Printer MkII", NULL, NULL);
::ResetDC(dc, pDevMode);
この場合,例えばトレイの設定ならば,StartPage() の前までに ResetDC() を済ませてしまえば無事にトレイが交換されます。つまり StartDoc() の後でも構わないってことですね。
HDC dc = ::CreateDC(...); // 引数省略
// ::ResetDC(dc, pDevMode); // ここでリセットしてもいいし
::StartDoc(dc);
::ResetDC(dc, pDevMode); // ここでリセットしてもよい
::StartPage(dc);
改ページ処理の最中にも,トレイを別物に交換することが可能です。
// 改ページ処理中でもトレイを交換できる
::EndPage(dc);
pDevMode->dmDefaultSource = DMBIN_SMALLFMT;
pDevMode->dmFields |= DM_DEFAULTSOURCE;
if (persist) // 必要なのかなあ。。。
::DocumentProperties( ... ); // 引数は略
::ResetDC(dc, pDevMode);
::StartPage(dc);
しかし,例えば両面印刷の設定を行った時は必ず StartDoc() の前までに ResetDC() を済ませなければなりません。
HDC dc = ::CreateDC(...); // 引数省略
::ResetDC(dc, pDevMode); // ここでリセット
::StartDoc(dc);
// ::ResetDC(dc, pDevMode); // ここではダメ
::StartPage(dc);
これはどう解釈すればよいのかなー。つまり一つの DEVMODE 構造体の中でも,一回の印刷の間中ずっと変更できない情報と,1ページ毎に変更の可能な情報があるってことですよね。前者のような情報は StartDoc() が必要としていて,後者の情報は StartPage() が必要としているんだ,と考えればスッキリします。そのあたりをしっかり調べられればいいんだけど,なんだか難しそうですね。。。
まぁ何にしても「CreateDC() に DEVMODE を渡さなかったくせにその直後に ResetDC() を実行する」なんて奇妙な行動をとる理由はどこにもない,て事でいいっすかね。
疲労度:1049