日記緑ログ

2006/04/01 ~ 2006/04/30

目次

戻る

Blog

NIFTY-Serve サービス終了

[Create: 2006/04/01 19:00] [LastUpdate: 2006/04/01 19:08]

ニフティのパソコン通信サービスがすべて終了

これで大手は全滅か。そういった感慨を持つ方も多いでしょう。

むしろ今まで良くがんばってくれましたとそういう気持ちですな。

古くなったBlogをどうするか

[Create: 2006/04/03 21:30] [LastUpdate: 2006/04/04 10:21]

Doblogはログをエクスポートできないような気がする。

いや(イン/エクス)ポートできるようなやつがどういうところなのかは知らないけど、あることはあるようだ。使ったことは無いのだが。

しかし、私はDoblogはプログラムなんかのコードを載せるのには不向きだと感じるが乗り換える気持ちもあまり無いので目的はそういうことではない。

純 粋にBlogの数が増えると鬱陶しいだけだ。書き込みが可能な状態というのはそれだけでなんとなくリソースの無駄という気がする。そんな理由で以前にもロ グをばっさり削除した。削除したが当時はなんもしなかったもんで消えっぱなしである。どうせたいしたこと無いだろうと思ったもの(くだらないニュースへの 突込みとかどうでもいいネタ、等)なのでさして害は無かったかと思う。思うだけでもしかするとあったのかもしれないが。

リソースの無駄だ といって消しっぱなしでは無く、どうせならノーマルなWEBベースで閲覧できたほうが便利ではないかというが主旨である。主旨ではあるがリンク関連は全滅 である。そのあたりどうしたものか迷うのだがWEBは常に流動的で、3年前に見たあのページは今どこに?あれ?消えてる?というのも結構頻繁にある。移転 先がある場合はまだかわいいがまったくの消滅もままあるわけだ。それからすると実にかわいいことをしようとしているのが分かると思う。つまりは情報を残し ておくためのネタである。

ついでに付け加えておくとこういうBlogスペースのようなページは負荷が高い。それもサーバーサイドとクライ アントサイドの双方で高いのである。Doblog等はきっちりと動的生成している(と思う)。恐らくJavaを使っていると思うがこのあたりはサーバーに かかる負荷は高い。いくらスレッドプールでほにゃららとか言っても重いものは重いのである。

なぜか引き合いに出す2chはread.cgiはCだ がbbs.cgiはPerlである。Cはまあ軽いだろ?という通説は置いておくことにしてbbs.cgiはPerlで重い上に以前はプールせずに1書き込 みごとにプロセスが起動して死んでを繰り返していた。それでも板のトップあたりはhtmlを吐いて書き込みがなければ静的なブツを返せるようになってい た。今ではSpeedyCGIを使用してプロセスを有効活用している。さらにサーバーにやたらリッチなスペックのマシンを投入しているようだ。ディスクア クセスを抑えるためにメモリディスクも導入している。2chはそれでも負荷がきつくてきつくて大変のようだが。

脱線したがDoblogも夜9時ぐらいから12時ぐらいまでは結構負荷がきつきつのようである。

次 はクライアント側の負荷になるが、こちらは微妙な話になる。まず間違いないのは取得時だ。If-Modified-Sinceが使えないので必然的に絶対 にリロードになる。これはサーバーにもより負担をかけるがBlogの形態を考えるとどうしようもないかもしれない。RSS等は静的に取得できると思うが。

ち まちました話だがgzipによる圧縮転送という仕組みがある。これを動的に行うとサーバーの処理に負担をかける。動的ページ生成はそれだけで結構ヘビーな のでgzipは省略したい場合があるだろう。するとサーバーとクライアント双方で帯域に負担がかかる。昨今の高速回線化で気にならなくなっているようだが サーバーはちりも積もればというやつで莫大である。するとレスポンスが低下してクライアントにも影響が出るのだ。付け加えておくとまだ低速な回線は現役な のだ。日本でもモデム通信がまだ無いわけではない。出先ではあまり高速でないかもしれない(最近ではどこでも通信の雄AirH”も高速になったが)。無線 LANのホットスポット混みすぎ。とか。さらには海外ではまだまだな場合が多いのだ。

ロードに時間がかかるとブラウザのレンダリング処理が大変に なる。メジャーどころのブラウザはSlowな環境でも(Load中でも)テキストぐらいは少しは読めるようにという実装になっている。要するに頭から例え ばある一定時間読み込んだ分までレンダリングを繰り返す。全部読み込んで最終的にレンダリングを行う。レンダリングの処理は重い。テキストサイズが大きけ れば大きいほど重い。あるいはレイアウトによって文章構造によって等、各ブラウザに得手不得手があるようだが構造が複雑化するとより重い(そう単純ではな いようだが)。昔、感じたのだがテキストベースで見て長く改行がないhtmlをレンダリングするのが重かったような気もした。とにかく長く長く、日記 1000日分を1ページにとかいう状態になると悲惨なのだ。ロードは時間がかかるしレンダリングも重いしで、高負荷になるとデータ取得速度にも影響が出て と悪の秘密結社なのかという事態によくなるのである。そんな場合には1日1ページは分割しすぎかもしれないけれど分割はするべきだと思う。大抵1月に1 ページぐらいで分割すると(本当に低スペック低速回線だときついが)いいぐらいだろう。文章量やその他画像等の量にもよるだろうが。

古い テキストは過去ログ化するのがいいだろう。いいはずだ。いいんだよ。ということだ。これはどちらかといえば私のような者特有の思考であり嗜好であるだろ う。そんなもんリッチな現代では気にしなくてもいいかもしれない。ただそれだけではなくて古い日記が突然消えたら悲しいなという意味もある(アナウンスぐ らいはあるか……)。

もう一度きちんと結論を言っておくと私は古くなった記事は何らかの静的な形式で保存する、場合によってはWEBスペースにUPするのがベストだと考えている。異論はあるだろうがそう考えている。

次回に続くが重いサイトの代表をあと2つ。

wikipediaは有名になったせいか重い。いつも重い。wikiは古くなってきたからポイとかhtmlに落としてさーらさらという使い方には不向きなので難しいなと。

souceforge.netはなんだかいつも重い。昔から重くてダイヤルアップ泣かせの動的生成WEBサイトである。

どちらも動的にページを生成している。レイアウトの変更やらなんやらは楽チンなのだがそのあたりどうよと。

と書いていてDoblogが今重いときなのに気づく。ずいぶん長くなった。ごめんなさい。

----2006-04-04追記

FC2 はカスタマイズ性が高く、自分で<html>やDOCTYPEから書けるようだ。テンプレートによって<?xmlのXML宣言から始 まっていたりとばらばらであるのだがやりようによってはstrictなドキュメントにもできるのかもしれない。</html>

古いBlogを抜き出し保存

[Create: 2006/04/04 13:52] [LastUpdate: 2006/04/04 14:23]

DoblogのHTMLはHTMLであってXHTMLでもなんでもない。というかHTMLとしてすらStrictではないような気がする。

そ れはさておき、ローカルに適当に保存したHTMLから記事を抜き出す処理だ。実際は一度編集モードで開いて出てきたテキストをコピーすればいいのだろうが 記事数が増えてくると大変である。その点HTMLから抜き出す処理はDoblogだと一度に30件まで表示可能だから手間が省けるということでこういう処 理にした。

HTML Parserを使うことも考えたのだが、今回はパスし、正規表現でごり押しした。適当にXML形式に書き換えると同時にDoblog側の著作物っぽい部分を削ぎ落とす。

抽出したものは以下のとおり

タイトル

作成日時

更新日時

テキスト

イメージのURI

記事のURI

トラックバックURI

イメージは別に保存しているからどうでもいいのだがそこに画像があったという事を記録することが重要だと思ってとっておいた。

記事URIとトラックバックURIのうちトラックバックURIは到底使い道がないような気がするが一応。記事URIは『元は~でした』という表記に使えそうなので取得しておいた。

作成日時のうち、作成日は構造上違う場所にあるので移動&コピーする処理を書いておいた。今のところ一日一記事を超えないようにしているのでコピーは未テスト。

作成日時にするのは更新日時との対応を取るのが一番の目的。

XMLにしてしまえばXML Parserが利用できるので処理は正規表現でのそれよりも安全で確実なものになる。

またXSLTでの変換もできるのでHTML形式で公開も容易だ。ちなみにHTMLにすると逆変換が面倒そうなのでXHTML形式にする。

スタイルシートは書き下すことにした。ただBlogから落としたものだと分かりやすいようにとりあえずDoblogの表示に近いものにしてみた。色はままで構造は想像で適当にしてある。

Doblogスタッフブログ/[ 2004/1/21 ] 著作権に関してを考慮に入れてクリーンになるように心がけたつもり。

ログ表示例

一応これが以上のやっつけ仕事の成果物である。

Doblogの本物とはなんだかサイズやらなんやら違うようだが、Doblogのやつは色以外は見ていないからである。色は適当にスクリーンショットからピックアップしただけだ。

Doblogオリジナル表示

これがDoblogオリジナルのブツ。

IE6で表示してキャプチャしている。

どうもDoblogでは文字のサイズを小さく指定しているようだ。

しかし私はそういう指定は気に食わないのでしていない。

はてさてしかしてこういうものができたわけだがこんなもんの需要は一般には無いかもしれない。欲しかったら言って欲しい。

とはいうもののDoblog固定でしか使えない上に問題点は結構ある。

太字斜体下線そして絵文字等にはまったく対応していない。していないったらしていない。

あ とこうやってシーンを追加したり画像を使ったものはテストしていない。画像に至っては手で直すようにしている。これは自動で落とすのは面倒なのとサーバー に負担がかかるのとローカルにももちろん負担がかかるからだがそれが人に負担をかけているのでどうなのか。私は画像なんてめったにつけないからこういうこ とにしてみた。

さらに本当ならば過去の記事をアレしてXMLを複数はいてそれをマージしたいのだがそれはいつやるか分からない。

もっというとごてっとHTMLを吐くので一ページが巨大になる。これを適当な単位で分割したいという気持ちがこれまたあるのでそのうちなんとかしてみたいものだ。

さて、問題はいつどのぐらいの記事を移動するかということだがまだ未定である。

サムネイル画像の作成

[Create: 2006/04/06 23:24] [LastUpdate: 2006/04/06 23:23]

サムネイルを作るのは面倒です。面倒なのでコマンドラインでさっくりできるやつが欲しいと考えました。考えましたがイマイチしっくりくるものが探せませんでした。

どうせたいした手間でもないだろうと考えそういったツールを作ってみました。

JPEGやPNGを読み書きできないといけない

リサイズしなければならない

という条件があります。

Susie plug-inを使えば読み込みは可能です。しかし書き込みは面倒です。ABC(A to B Converter)のプラグインで出力が可能です。これはSusie plug-inのExportの部分を埋めるようになっているので親和性も高い。

それはいいのですがリサイズで問題にあたります。線形補間ぐらいならさして手間もかかりませんがGDIのDIBに合わせるのは面倒だなと。

と りあえず上のようなものを利用するのをすっぱりあきらめ、GDI+を使うことに決定しました。まああとから機能を追加できないこともありませんし、リサイ ズだけに使ってもいいし、段々追い出してもいいし、選択式でどっちかを使うようにもできないことも無いのでとりあえずの措置です。そもそも自分だけツール というスタンスなので。

GDI+は

#include <gdiplus.h>

してやるとnamespace Gdiplus に色々とクラスが詰め込まれますからmainでスタートアップ時に

GdiplusStartupInput gdiplusStartupInput;

ULONG_PTR gdiplusToken;

GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

して終わりに

GdiplusShutdown(gdiplusToken);

とすればその間で機能が使えます。

Image image(L"abc.jpg");

等とすればjpegでもpngでもbmpでもロード可能です。ただし渡すファイル名はWideStringです。他にもコンストラクタがありますがばっさり割愛。MSDNに詳しいことが載っています。

サムネイルは簡単に作るなら

Image* pThumbnail = image.GetThumbnailImage(width, height, NULL, NULL);

でOK。

Imageは使い終わったら

delete pThumbnail;

とすること。3番目と4番目はCallBack用なのでここでは無視。

リサイズを高精度にとかいう場合には面倒な操作が必要になりますがサムネイルなので品質はさほど重視しなくてもいいでしょう。

品質の指定をしたいのであればGraphicsをかませる必要があります。GraphicsオブジェクトのInterpolationModeにHighQualityBicubic等のプロパティを設定してあげれば品質を指定できます。

Bitmap newimage = new Bitmap(newwidth, newheight);

Graphics g = Graphics.FromImage(newimage);

g.InterpolationMode = InterpolationMode.HighQualityBicubic;

g.DrawImage(image, 0, 0, newwidth, newheight);

等とすればいいはず。未検証ですが。ちなみにこれはC#を想定しています。

画像の保存ですがC++ではCLSIDを拾ってきて渡す必要があります。

Retrieving the Class Identifier for an EncoderにGetEncoderClsidという関数が載っていますので参考にするといいかと思います。

ただしこれはMIMEのContent-Type形式で指定することになります。

拡張子で欲しければImageCodecInfo.FilenameExtensionを取得するといいのですが拡張子はJPEGなどは複数の種類があるので注意しましょう。*.JPG;*.JPEGのような形式になっています。

無事にCLSIDも取得したら保存は簡単です。

pThumbnail->Save(ファイル名, &Clsid, NULL);

ただこれだと保存時の品質は設定できません(3番目のパラメータがNULLなので)。

そういった細かいことをしたいときはSetting JPEG Compression Levelを参考にしてください。

まあそういう感じで楽に作れます。

ログサイト

[Create: 2006/04/07 19:37] [LastUpdate: 2006/04/07 19:43]

日記緑ログを実験的に公開。

なつかしの記事も一応含まれています。

懐かしいのはじぶんだけかもしれません。

変換に使用したもののうちそこそこ汎用性がありそうなサムネイル作成ツールを置いておきました。

makethumimg 最大幅 最小幅 入力ファイル 出力ファイル名

と指定します。激怪しげ仕様なので何があっても責任はもてませんが問題はそう起きないのではないかと。

出力ファイルの拡張子で出力形式は自動で選択されます。

GDI +を利用しているのでBmp、Jpeg、PNGあたりはつかえます。GIFとかTIFFも使えるような気がしますが未テスト。そもそもGIFは256色ま でですし、TIFFは形式が多すぎるのできちんとしたソフトを使うべきだと思います。入力にするのは否定する意味もありませんので仕方が無いことですが。

Ajaxの実例

[Create: 2006/04/09 19:20] [LastUpdate: 2006/04/09 19:37]

ぜーんぜん関係の無い話だがAjaxという名前がJesse James Garrettの記事でつけられたのは2005年2月18日のことだとか。

ちょっとまって欲しい。Ajaxというものはそれ以前からあったのは間違いない。それもかなりでかいサイトでわれわれ(?)がよく目にしており、かつちょくちょくお世話になっていたところだ。

そう。MSDN Libraryだ。

え? という気もするかもしれないが(思わないほうが多いと思うが)、左側のツリーはXMLを使っている。ツリーの展開は動的に行われているはずだ。少なくとも IEではそうなる。とするとこれはまるっきりAjaxの定義に合っているような気がする。ただしサーバーで工作してデータをやり取りはせずGETのみなの だが今Ajaxなどと言っているものにはHTMLを拾ってきてinnerHTMLで流しているものが多かったりするのでいいとしよう。

いつ頃から今のインターフェイスになったかは記憶に無いのだが絶対に間違いないのは2005年2月18日より前だということだ。

以前MSDN Libraryを読んでくる怪しいツールの話をしたのだが(MSDN Library Online - 日記緑ログを 参照)、そのときの手法というのがXMLを直接読んできてツリーを構成するというものだった。このときの公開DateTimeが2005/01/16 19:33だからAjax宣言前だというのが容易に分かる。実際はMSDNのサイト自体は間違いなく数年前から使っていたわけだ。

どうってことは無い話なのだが……

XnViewのプラグインを使おうとして失敗する

[Create: 2006/04/14 20:40] [LastUpdate: 2006/04/14 21:09]

毎度どうしようもない話だが、XnViewという画像ビューアがある。プラグイン形式で対応ファイルを増やせるというヤツで窓の杜でも紹介されたので知っている人も多いだろう(窓の杜自体には2000/05/10のアップデート情報に「XnView」v1.14 (00/05/03)と出ていたり結構前から扱っている)。

プラグインのSDKも出ているしGFL SDKというXnViewで使っているようなライブラリも出ている。

このGFL SDKは非常に楽に画像を読み込める。というよりも画像の加工や保存までまかなうライブラリでサイズもライト版で700k程度と大きい。まあサイズなんて気にしない傾向にある昨今なのでその話は放置するが、プラグインだけ何とか使えないかと思ったのだ。

実はプラグインはマイナー形式に対応するためにあるようなもので、メジャーな形式は本体側で対応しているのであんまり意味が無いのだがなんとなくというやつだ。

プ ラグインSDKが出ているから楽勝だろうと思ったのだが実際は大間違いだった。SDKのソースを見ると「put it in PluginsEx folder」なるコメントがある。エクスポート関数が幾つかあるのだが、これは本体の Plugins directory にあるプラグインとは違う形式なのだ。

このExプラグインは数も出ていないと思われるし、そうなると意味がますます薄れるので断念したのだが素直にGFL SDKを利用すればまあまず間違く便利で楽チンなのでこれにて終了である。

ちなみにGFL SDKにはきっちりReferenceも付属しているので(英語だが難しくないと思う)なにかあった時のために記憶の片隅に置いておくと使えるかもしれない。

スパムメールに辟易

[Create: 2006/04/18 20:20] [LastUpdate: 2006/04/29 05:46]

最近私のメインとしているメールアドレスにもスパムメールが激しく到来するようになった。

大体日に20~40件ぐらいで、いい加減に捨ててしまいたいのだが結構な重要連絡用に使っていたりするし、知人との連絡で使うためのアドレスでもあるので(いっしょにするほうが悪いのだが)変更はやむを得ないと考えられるようになるまで避けたい。

ついでに言えばこのアドレスは容量が今時5Mでちょっと悲しい。

平成電電の再生が絶望的でそっちのアドレスについても心配したいのだが実はそっちは段階的に利用を減らしていたのでダメージはあまり大きくない。広告が本文に入らないアドレスが減るのは悲しいのだが。

話は戻るのだが、アドレスに来るスパムの対策を考えたい。

メールヘッダにあるReceivedをちょろっと見てみる。このメインのアドレスはホスト名を逆引きしてくれるので

Received: from Ahost (逆引Ahost [AIPaddr]) by B for mailaddr

のようになる。この逆引Ahostを見てやると面白いことができるのだが詳しいそして真面目な対策はS25Rの解説を読んで頂くとしよう。

S25Rは本来はメールサーバーで運用するための技術だがこのような逆引きヘッダを持っているメールに対してフィルタする用途にもつかえる。それも詳しいことは上記のページにある。

ここまではアドレスと対策についての話だが、手元での処理の具体的な話について少しだけ触れておく。

私の利用しているメーラーは何度も書いているが電信八号で ある。電信八号は正規表現でメールの振り分けが可能なのでそれを利用すると結構便利で強力にフィルタリングができる。最近流行りのベイズフィルタも有用な のだが現段階ではそちらの技術に頼らずともほとんどのスパムがごみ箱に消え行く運命にある(環境が変化すると対策についても変化するだろうが)。

さて、今使っている電信八号のバージョンは"Version 32.1.5.3"である。

電信八号の正規表現は微妙な癖があるがそれは感で乗り切ることにして一番引っかかるのは括弧についてだろうか。丸括弧は正規表現ではグループを扱うのに使うのだが電信八号ではそういったことはしていないのか扱いはいつも微妙である。

とりあえず逆引きできないホストにマッチする条件を書こう。

逆引きできないホストはこのアドレスの場合

Received: A ([0.0.0.0]) ~

のような表記になる(Unknownなどはつかない)。とりあえずこれは

/^RECEIVED:.*(\[[0-9]+\.[[0-9]+\.[[0-9]+\.[[0-9]+\])/ TRASH.CAN

のようにしてはじけるようだ。()がグループ化の括弧だとIPアドレスがあるヤツ(つまり全て)はじかれるはずだが使っている様子ではそういったことにはなっていないのでグループ化はされないのかもしれない(SEDは\(\)でグループ化していたような気がするが)。

実際に一つのアドレスのみ利用しているのならばこれもいいが一応byのホスト判定とforのアドレス判定を追加しておくと若干安心感がある。

とすると、実際にfolders.defに書くのは

/^RECEIVED:.*(\[[0-9]+\.[[0-9]+\.[[0-9]+\.[[0-9]+\]).*by mail\.hoge\.ne\.jp.*for <a@hoge\.ne\.jp>/ TRASH.CAN

のようになるだろう。実際、今の私に来るスパムの半分は逆引きできないホストからきているのでこれだけで結構効果がある。捨てちゃってたらごめんねとそういう話にもなるかもしれないのでできるだけこういうことはやりたくないのだが。

----追記

ちょっと試してみたのだがグループ化の括弧は\(\)のようだ。あんまり自信が無いが間違いないと思う。これだけ見るとgrep的な正規表現ではなくsed的な正規表現のような気がする。後方参照についてはわかりませんが。

アーカイバとUNICODEとDLL

[Create: 2006/04/21 20:47] [LastUpdate: 2006/04/22 19:12]

ネタとしては不完全でリサーチする気持ちもそれほど無いのでそりゃあ記事の内容が薄いせいでくだらなくなっているのだろうというそういうネタです。でも重要項目ですよ。

統合アーカイバプロジェクト は日本のスペシャルなプロジェクトで昔々から数々の恩恵を授けてくれました。

これの恩恵にあずかっていない人もまあいるとは思いますが、アーカイバ所事を語るときにはずせない重要なプロジェクトでしょう。

な にぶん古いので今時のUnicodeパスについては想定されていないAPIセットになっていますが最近(Mar.28,2006)事件がおきました。常に アクティブなUNLHA32.DLLがUnicode版のAPIを実装しました。記事を書いている最中はまだベータ版ですがそれまでのAPIセットの大変 革と言っても良い変更で互換性に懸念が残りますがこれでLHaもUnicodeパスが使えるようになりそうです。

統合アーカイバプロジェクトの中で他にUnicodeを扱えるのは7-zip32.dllがあります。こちらのほうがUnicode対応は早く2004/9/18に追加してあります。

両 者の対応を比較すると(ヘッダレベルのような段階では私は見ていませんのでそのへんは知りませんぜ)、UNLHA32.DLLはMS-Windowsのネ イティブAPIっぽく作ってあります。要するに~Aと~Wがあるタイプです。対する7-zip32.dllはSevenZipSetUnicodeで APIに渡すLPSTRをUTF-8にすることで対応しています。ちょっとスマートかもしれない。

統合アーカイバではありませんがunrar.dll(official)はUnicode対応していたはず。あとは知りませんが。

2006年04月23日の雑記

[Create: 2006/04/23 21:33] [LastUpdate: 2006/04/29 05:45]

Susie Plug-in を使ってViXやLinarのようなものを作れないか? などと訳のわからないことを思い立ち遊んでいる。

上記の二つはVectorでも公開されており、高い人気を誇っている(更新は結構途絶えている気がするが)。Susie自体もまだまだ人気があるがGVもかなりの現役であるようだ。

ちらちら見てみると結構人気の高いところにコミック等に特化した見開きがたのビューアが多いような気がする。

コミックビューアとして有名(だと思う)のはマンガミーヤだろう(多分)。かなりやらかしているような気がするが面白いね。と。

で、そんなものたちに戦いを挑むと痛い目にあうと思うけれども興味本位で遊んでみようかと。やっているうちにモチベーションはかなり減退するだろうけれども気にしない。

フ レームワーク(?)にWTLを使ってみる。MFCよりもLightでいいというあれな思想だけでなく、WTL7.5にはWTLExplorerっちゅうサ ンプルがくっついている。これで手間削減という思惑は結構崩れるのだが何かと心強い気がするので。ただしばらくやっているとDelphiかC++ Builderでやりたい作業だとつくづく感じた。WTLはMBCSビルドとUNICODEビルドを手軽に作成できる上にバイナリサイズもVCLと比べて (スタティックリンクで比べたときに)小さいという利点もあるのだが。

その後適当にやっていたらコードはぐちょんぐちょんである。Viewを用意するとインターフェイスが面倒そうだと思ってしまったのがいけないと思うのだが。

やる気が無いことばかり書いていて困るだけなので一つだけネタを置いておこう。

Susie Plug-inでは書庫ファイルも扱える。まあ他のMemberについては言及しないがファイルのタイムスタンプはtime_tで返されることになってい る。こいつを日時の文字列に変換したいとする。しかもロケールに沿って。asctimeやらctimeで出したもので満足すればいいのだがそうは問屋がお ろさないと思い、Windowsなのでそれらしい形で変換してやることにする。

Windows APIで時刻を文字列に変換するにはFILETIMEかSYSTEMTIME構造体に時刻を変換する必要がある。これらの構造体は相互変換が FileTimeToSystemTimeやSystemTimeToFileTimeの各APIで簡単に可能なのでどちらに変換してもいい。 Windowsにはロケールに沿ってSYSTEMTIME構造体を文字列に変換してくれるGetDateFormatとGetTimeFormatという APIがあり、最終的にはそれで文字列に変換することになる。

gmtimeでstruct tmに変換してSYSTEMTIMEにぽちぽち入れてやってもいいのだが演算一発でFILETIMEにできないことも無い。

time_tは普通UNIXTIMEで、1970年 1月 1日 0時 0分 0秒からの通算秒であり、FILETIMEは1601年 1月 1日 0時 0分 0秒からの 通算100ナノ秒だ。

time_tを100ナノ秒単位にして、それに両者の差の日付を加えてやればいい。桁あふれに気をつけないとまずいが。

time_t time;
FILETIME FileTime;
unsigned __int64 t =
    UInt32x32To64(time, 10000000) +
    UInt32x32To64(116444736, 1000000000);
FileTime.dwHighDateTime = (DWORD)(t >> 32);
FileTime.dwLowDateTime = (DWORD)t;

と、これだけだ。64ビット整数が使えないと面倒なのだが。

最終的にこれを文字列に変換する。

TCHAR szLocalDate[256], szLocalTime[256];
ZeroMemory(szLocalDate, sizeof(szLocalDate));
ZeroMemory(szLocalTime, sizeof(szLocalTime));

GetDateFormat(LOCALE_USER_DEFAULT,
    DATE_SHORTDATE,    // DATE_LONGDATE,
    systemTime, NULL, szLocalDate, 256);

GetTimeFormat(LOCALE_USER_DEFAULT, 0,
    systemTime, NULL, szLocalTime, 256);

ロ ケールはMAKELCIDでほにゃららしてもいいのだが普通はLOCALE_SYSTEM_DEFAULTかLOCALE_USER_DEFAULTを使 うことになるだろう。特別の理由が無い限りLOCALE_USER_DEFAULTを使うことをオススメする。ユーザーが何らかの理由でロケールの設定を 変えている場合にLOCALE_SYSTEM_DEFAULTだとそれが反映されないのでエゴイストっぽいプログラムになってしまう。おとなしくユーザー ロケールに従うのが普通の道だと思う。

拡張子に関連付けられたアイコンを取得する

[Create: 2006/04/24 19:28] [LastUpdate: 2006/04/29 05:44]

Windows のエクスプローラなどを見ると.txtはああいったアイコン。.bmpはそういったアイコンと言った形で拡張子別にアイコンが関連付けられています。この アイコンを取得してListView だとか TreeView に表示するという要望は結構良くあるようなきがします。

こういう作業に はシェル系のWin32 API(一般にWindows APIかもしれない)を使用することになりますがシェル系のAPIの解説はあんまり数が無いような感じもします。Win95が出て十年も経っているのです からもう少し資料を下さいよといいたい気がしますが、インターフェイスを取得してほにゃららみたいな形が面倒だからでしょうか、それともCだと面倒だから でしょうか、とにかくあんまり日本語の資料は出てこない気がします。

ただ、こういうFAQ並の一般的作業では資料はそこそこ豊富な様子。SHGetFileInfo で検索すると結構ぞくぞく出てきます。

手 法としてはTreeViewやListViewを普通に作成し、ListView_SetImageList でImageListをセットするだけです(ListView_SetImageListはマクロなので実際にはLVM_SETIMAGELISTを SendMessageする)。

ImageListは自分で作成することもできるので自由なアイコンで表示することもできますが標準シェル(=エクスプローラー)と同様の表示にしたいときはシェルで使用しているImageListを取得する必要があります。

SHFILEINFO sfi;
HIMAGELIST hImageList = (HIMAGELIST)::SHGetFileInfo(_T("C:\\"), 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX | SHGFI_ICON);

とでもすれば取得できますがCドライブ固定指定が邪道だと思います。

より一般的にエレガントにするには

LPITEMIDLIST spidl;
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &spidl);
SHFILEINFO sfi;
HIMAGELIST
hImageList = (HIMAGELIST)::SHGetFileInfo(spidl, 0, &sfi,
sizeof(sfi), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_ICON);

としたほうがいいと思います。spidlはCoTaskMemFreeか、IMallocインターフェイス経由で解放する必要がありますが、hImageList はシェルの所有物なので勝手に解放するとえらいめにあいます。

その後

ListView_SetImageList(hwnd, hImageList, LVSIL_NORMAL);

とすれば一応アイコンを設定することができます。場合によって小さいアイコンをつけるかもしれませんがSHGFI_ICONのかわりにSHGFI_SMALLICONを指定してSHGetFileInfoを呼び出し、

ListView_SetImageList(hwnd, hImageList, LVSIL_SMALL);

とすればいいのです。

実 際にListViewで使用するときはLVITEM構造体のiImageメンバーにインデックスを指定します。そのインデックスの取得はやはり SHGetFileInfoあたりを使えばできます。IShellFolderあたりを使うとファイルやディレクトリなどはITEMIDLISTの形で取 得できますから、

LPITEMIDLIST lpi; // 取得しておく
SHFILEINFO sfi;
SHGetFileInfo((LPCTSTR)lpi, 0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);

とすれば sfi.iIcon にインデックス値が入ってきます。

ただしこの場合は実際のファイルでしかインデックスを取得できませんので、アーカイバを作成してアイコンを表示するなどといった用途では使えません。とはいっても渡すパラメータを少し変えてやるだけで拡張子を含むファイル名でアイコンのインデックスを取得できます。

SHGetFileInfo(ファイル名,
        FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO),
        SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES);

と、これだけで拡張子からアイコンが取得できます。

FAQになりやすいと思う項目の割には日本語の情報が少ないのが気がかりです。いや皆無ではなくてそれなりにまとまってあるのですが、当のMSDNもシェル系のAPIは日本語版を提供していないこともあってかKernel系のAPIと比べると未だに非常に少ない状況です。

ListViewだけでなくてTreeViewでも似たり寄ったりな作業でアイコンをつけられますからエクスプローラのフォルダツリー等も再現で着ちゃうわけです。

そ りゃあいいのですがSHGetFileInfoをMSDNで見たときに突っ込みを入れたくなった部分。dwFileAttributesの解説文中に (FILE_ATTRIBUTE_ values as defined in Winnt.h)とありました。ちょっと待って。あれ?全部解説してくれないの?あれ?丸投げ?

FILE_ATTRIBUTE_* の定数群はもともとその名の通りファイルの属性をあらわすものですから丸投げもかまいませんが、このときFILE_ATTRIBUTE_NORMALを渡 せば(実はFILE_ATTRIBUTE_NORMALではなく0でも同じように取得できてしまいますが)パスはファイル名、あるいはデバイスのドライブ として扱われます。FILE_ATTRIBUTE_DIRECTORYを渡せばディレクトリのアイコンが取得できます。これだけ特殊化。

strsafe.hのススメ

[Create: 2006/04/26 20:04] [LastUpdate: 2006/04/29 05:42]

Cでの文字列操作関数群(str*)は容易にバッファーオーバーランを起こします。

注意深く操作していれば問題ないはずですが、いちいちstrlenしてcallocしてstrcpy等の作業をするのは苦痛でしょう。

どうも変なネタですがWinnyにバッファオーバーフローの脆弱性があるという報道が先日ありました。

どうも脆弱性の正体はstrcpyか何かのようです。不正なパケットでなるとかならないとか。

MS はこういったバッファオーバーランに対してより安全なCRT関数群のstrcpy_s、strlen_s等の標準規格化を狙って動いていたり活発ですが、 CRT系のものはVS 2005あたりでないとつかえません。替わりにといっては何ですがPlatform SDKにstrsafe.hという新しい文字列操作関数群を定義しています。

詳しい解説はUsing the Strsafe.h Functions

strsafe.h : C 言語での安全な文字列処理を見ていただくとしましょう。

さ て、Windows APIにはlstrcpyとかlstrlen等のCRTっぽい名称の文字列操作関数があります。旧来のそれらのAPIを使用することでOSに依存せずに済 ませようという考えもありますが、strsafe.hはそもそもヘッダのみ、あるいは小さいライブラリのみの独立した関数群なのでOSには依存しません。 これがPlatform SDKに収められているのは若干場違いな気がしますが、無料で使えますし便利ですしAPIとの相性もいいしといい事尽くめでlstr~系のDLLを介した 呼び出しよりも早くなる(何もせずに使うとインライン展開される可能性が大)ので、ぜひ使いましょう。

で、気になるバイナリの膨れを見てみようというのが主旨になります。

今回はVC++ 6でMicrosoft Platform SDK for Windows Server 2003 R2を使って作成しました。

/opt:nowin98 をつけてバイナリが縮むようにして比較します。

ベースになるソースはこちら

INT WINAPI _tWinMain(
        HINSTANCE hInstance,
        HINSTANCE hPrevInst,
        LPTSTR lpszCmdLine,
        INT nCmdShow){
    TCHAR buf[256];
    lstrcpy(buf, TEXT("teststr"));
    return 0;
}

はい。これを最適化無効の/nodefaultlibして/entry:"WinMain"した状態でビルドするとバイナリサイズは2,560 バイトになりました。

lstrcpyをStringCchCopy(buf, 256, TEXT("teststr"));に換えて同様にすると1,536 バイトになりました。

StringCchCopyのほうがlstrcpyよりも小さいバイナリになりましたがこれはkernel32.dllをインポートしているせいでしょう。またこの場合StringCchCopyはインラインになっているでしょうからバイナリも縮んだものと思われます。

さて、これは微妙に一般的でない気がするので/nodefaultlibをやめてCRTもリンクしましょう。

すると

lstrcpy(buf, TEXT("teststr"));

26,624 バイト

_tcscpy(buf, TEXT("teststr"));

26,624 バイト

#define STRSAFE_LIB

StringCchCopy(buf, 256, TEXT("teststr"));

30,720 バイト

StringCchCopy(buf, 256, TEXT("teststr"));

27,136 バイト

となりました。

STRSAFE_LIBを定義するとstrsafe.libがリンクされ、インライン展開されなくなります。strsafe.libはvsnprintfに依存するのでCRTをリンクしないと駄目なので上にはありませんでした。

恐らく利用個所が増えると、インライン展開される分コード量が増えることになるでしょうから、あまりにもという場合にはSTRSAFE_LIBを定義したほうがいいでしょう。

shlwapiのススメズ

[Create: 2006/04/27 21:54] [LastUpdate: 2006/04/27 22:32]

strsafeはお勧めの標準的に使えるライブラリでした。しかも独立したものなのでWindowsのバージョンに依存しないのでどんどん使うべきです。

さて、Windowsは機能拡張を繰り返したりしており、便利なライブラリも追加されたりしています。たびたび引き合いに出されるshlwapiというのがその筆頭ではないかと思います。

shlwapiはShell Lightweight APIの略であって、名のとおりShell系の“軽い”APIセットです。このAPI群はおおよそシェル系の関数のラッパーであったり、文字列操作をした り、ファイルパス操作を行ったりするもので実際に手で同じように書いてもできないことは無いけれど良くあるパターンだったりするし、Windows APIにはANSI Cにもあるのに無い文字列操作関数があるね。とかいう状況を緩和してくれます。

shlwapiはNT 4.0 にIE4.0以降をインストールしたもの、95にIE4.0以降をインストールしたもの、98以降、2000以降のWindowsで利用でき、今時分の Windowsならばほぼ使えるはずです。ところが普及は進みませんでした。IE4東條当時、があまりにも革新的なOS拡張プログラムであったために敬遠 された事と、安定した環境を壊したくない事が作用しあってIE4を入れないユーザーも結構いて、依存したりするとreadmeには書くわ、ヘルプには書く わ、サポートは大変だわでIE依存をしないソフト作りが盛んに行われたのですね。

shlwapiはその機能の実現はそれほど難しくは無い ものばかりで(面倒だったりケアレスミスもあるのだが)、便利な反面IE依存が生まれるため利用は思うように進みませんでした。こういった思想はなかなか なくならないものです(でも最近は8.3形式のファイル名も廃れてきていたりはしているが)が、今となってはそれほど気にする必要は無いと思います(普及 は暗黙のうちにかなり進んでいる。95は捨てた人も多いだろうし、IE5以降という選択肢は割と安定していたのでそちらの可能性も大いにある)。

で、shlwapiには便利機能がいっぱいですが手放しでお勧めできるかというと便利なPath系の関数はお勧めしきれません。

PathAddBackslash だとかPathCombineだとかの系統のPath関数セットはMAX_PATHを基準にしています。MAX_PATHの長さを持つバッファを使えば バッファーも溢れずに安全ですし、気楽に使えるのですがNT系になるとUNCパスを使うとMAX_PATHどころじゃなく長いパスを扱えるようになってい て、実際にやたら長大な名前のディレクトリ階層を作ったりして馬鹿なことをやっているとパス文字列長がMAX_PATHを余裕で超えたりします。そうなる とMAX_PATHの縛りにとらわれたPath系関数セットは残念ながら使えない状態になってしまいます。

ということでこのあたりについてはお勧めできないのですが便利な関数がそろっているわけで誤解はしないように。

上 のほうでライブラリに依存することで初期Windows 95で使えない問題が出ると書いたのだが、これが前回のstrsafeと同じようなアプローチで作成されていたのならこのような問題は起きなかったと思う のだ。このあたりMSのミスだと思うかどうかは微妙かもしれないが個人としては残念な気がする。やっぱりshlwapiは便利なので。

ロケールに沿って数値表示

[Create: 2006/04/28 21:39] [LastUpdate: 2006/04/29 05:43]

数値表示は実は面倒。

米国だかの形式と日本で現在流通している形式は同じようなもんで三桁カンマ区切りで小数点はピリオドを使う。

旧来の漢字を使った形式だとちょっと仰々しいが万とか千とか百とか割分厘などとなる。

まあこちらのほうは使わないだろうけれど、日本や米国以外だとヨーロッパ圏でもかなり形式がばらばらなもんで、そいつに合わせてやろうか。ハハン。なぞと考えるとろくでもない目にあう。

WindowsではそういったニーズにこたえるためにGetNumberFormatというAPIが用意されている。

GetNumberFormat(
    LOCALE_USER_DEFAULT,
    0,
    "123456",
    NULL,
    str, cch);

のように適当にやってしまえばカタがつきそうだがそうはいかない時がある。日本の標準状態だと、これは

123,456.00

のような形式に変換されると思う。この小数部は要らないよというときは途端に面倒になる。

小数部分を削れば? とかいわれそうな気もするがじゃあロケールの設定を変更されていたらどう対応するんだい? ということになる。ここでは真面目に正攻法で行くのがいい。

GetLocaleInfo を使うと変換するときの情報を得られる。LOCALE_RETURN_NUMBERを使うと三番目の引数数値型へのポインタを渡すことで数値がママで得ら れますから処理は楽になります。ただしLOCALE_I*の、もともと数値の型限定で、"Windows 98/Me, Windows NT 4.0 and later"ということになっています。Windows 95も視野に入れてそれほど面倒でもないので文字列で全部処理してしまいましょう。

NUMBERFMT format;
TCHAR buf[MAX_PATH];
if(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDIGITS, buf, MAX_PATH)){
    format.NumDigits = _ttoi(buf);
}
if(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILZERO, buf, MAX_PATH)){
    format.LeadingZero = _ttoi(buf);
}
if(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, buf, MAX_PATH)){
    // "3;2" -> 32 support
    LPCTSTR p = buf;
    DWORD data = 0;
    BOOL lastIsZero = FALSE;
    while(_istdigit(*p)){
        data *= 10;
        data += *p - TEXT('0');
        p++;
        if(*p == TEXT('\0'))
            break;
        if(*p == TEXT(';')){
            p++;
            if(*p != TEXT('\0') && *p == TEXT('0')){
                lastIsZero = TRUE;
                break;
            }
        }
    }
    if(!lastIsZero)
        data *= 10;
    format.Grouping = data;
}
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, format.lpDecimalSep, MAX_PATH);
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, format.lpThousandSep, MAX_PATH);
if(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_INEGNUMBER, buf, MAX_PATH)){
    format.NegativeOrder = _ttoi(buf);
}

気をつけたいのがgroupingのところです。

3;0    3,000,000,000,000
3;2;0    30,00,00,00,00,000
3    3000000000,000
3;2    30000000,00,000

という対応がMSDNに示されており、これに従わないととんでもないことになります。

し かしNUMBERFMT.GroupingはUINTであり、文字列を直接扱うことはできません。MSDNには0~9までと32が使えるよと書いてありま すが、Windows 2000で試したところ30だと上記の3の表示に、320だと上記の3;2の表示と同じものになるようです。もちろん全てのWindowsで同じように動 作する保証はありませんが、MSは互換性はできる限り捨てない方向で動いていますので他の環境でも動作するような気もします。これに関しては報告をお待ち しています。

で、上記のようにして取得したformatを使って

GetNumberFormat(
        NULL,
        0,
        "123456789",
        &format,
        buffer, 256);

とでもすれば数値表現が取得できるはずです。

これに関しては他のロケールでのテストを十分していないので何かあったら教えていただけると幸いです。

クリックしたときとJavaScript

[Create: 2006/04/30 20:24] [LastUpdate: 2006/04/30 20:32]

a タグとかでonclickになにかを指定してhref="#"とかhref="javascript:;"とかにしてお茶を濁す何がしかがあったり onclickじゃなくてhref="javascript:onclick();"だったりもしますがJavaScriptでそういう事をしたいときは

<script type="text/javascript">
<!--
function disp(url){
    window.open(url, "window_name", "width=350,height=250,scrollbars=yes");
}
//-->
</script>


a href="html.html" target="_blank" onclick="disp('html.html');return false;"

としておくと無難です。別に別窓を開くだけに限りません。onclickでfalseを返すとhrefをたどらないことになっている(JavaScript 1.3)のでバンバン使えばいいと。