cygwin with mintty

いまさらながら、(個人的に)必要に迫られてcygwinを夏に買ったVAIO Zにインストールした。

もともと vpsのssh接続用にTera Term Proをインストールしていたので、Tera Term Proに入っているCygtermを介して Tera Term Pro 上で使うようにしました。コピペもWindowsのコマンドプロンプトなシェル より遥かに使い勝手がいいし。

で、ちょっと調べてみたら、cygwin の端末エミュレータとして標準で mintty というのがインストールされてて、どうやらこのminttyがcygwin 推奨の?端末ソフトらしい。Tera Term Proでもほとんど不満はないのですが、やはりcygwin 関係はcygwin 内ですべてを完結させたいところ。
使ってみると、日本語もちゃんと表示されるし、最低限の機能を備えているので問題なし。Windows7でウィンドウの透過を”Glass”にしたら、結構かっこよく見える(^^;;;

ただ、一点、不満がありまして・・・、それが・・・環境変数 HOME の扱い。Windowsのアプリですでに環境変数HOMEを設定していて、これは変えたくない。

で、どうやら、mintty を起動すると、cygwinのインストーラが作成した、/etc/passwd(実際にはc:cygwinetcpasswd) を見ているらしく、デフォルトでホームディレクトリが、Windowsの%USERPROFILE%になるみたい。

Tera Term Pro では、「設定 → その他の設定」で、環境変数を設定できる項目があるのでHOMEを /home/username と設定することで解決してたんですが・・・。

環境変数HOMEをバッチファイルに埋め込めばいいんだけど、それだといちいちコンソールウィンドウが立ち上がってブサイク。

・・・というわけで、バッチファイル(コマンドファイル)ではなく、wsh + jscript の力を借りて下記のような起動スクリプトを作ってスタートメニューに登録。

var wshl = WScript.CreateObject("WScript.Shell");
var env = wshl.Environment("PROCESS");

env.item('HOME') = '/home/' + env.item('USERNAME');
env.item('MAKE_MODE') = "unix";

wshl.Run("c:\\cygwin\\bin\\mintty.exe -i /Cygwin-Terminal.ico -");

vpsへのログインも、cygwin + ssh で。 非WIndows環境はすべてcygwinにしてしまいました。

ええ感じ~、と思っていたら、Tera Term Proで操作ログを常に保存していたのを思い出し・・・mintty のオプション ” -l ” を指定しないと・・・んーーーなんか挫折しそう。

(2012/1/2 : 若干記述修正済み)

HTML DOM access with C#

備忘録です。

System.Net.WebRequest / System.Net.WebResponse クラスを使ってWeb上のHTMLを取得するのは、非常に簡単にできます。MSDNドキュメントにもサンプルがありますし、ネット上でも豊富にサンプルがあるので問題はないです。

が!、取得したHTMLファイルを元に、DOMアクセスさせようとすると途端に.NET Frameworkプログラマーの初心者には、行き詰まってしまいます。 その原因として二つの壁にぶち当たりました。

  1. そもそもHTML の DOMパーサーが、標準でクラスライブラリにない。
  2. Web上から取得したHTMLファイル内に使われている文字コードの判別と、文字コード変換を担うクラスライブラリが標準で提供されていない。

このうち(1)のDOMパーサーについては・・・

Internet Explorer で使われている(正確には WebBrowser コントロールだけど)mshtml.dllが提供するパーサーに任せることができます。mshtml.dll のタイプライブラリが登録されているので、参照を追加することでDOMアクセスができます。具体的には・・・

//HTML内のリンクを列挙するサンプル
using Microsoft;

public static void DomAccessSample(string html)
{
  var hd = new mshtml.HTMLDocument();
  var hd2 = hd as mshtml.IHTMLDocument2;
  
  hd2.write(html);
  foreach (mshtml.IHTMLElement link in hd2.links)
    {
      Console.WriteLine((string)link.getAttribute("href", 0));
    }
}

こんな感じでしょうか。Visual StudioのIDEを使わず、コマンドラインからビルドするには、

#アセンブリの位置はあくまで僕の環境です。 
csc /r:C:\Windows\assembly\GAC\Microsoft.mshtml\7.0.3300.0__b03f5f7f11d50a3a\Microsoft.mshtml.dll ソースファイル.cs

のように、GACに登録されているアセンブリ(DLLファイル)を参照に加えてビルド。

さて、次は、(2)なんですが・・・

MSDNでのサンプルなんかでは、System.Net.WebResponse.GetResponseStream() メソッドから得られるストリーム(Webから取得したバイト列)を、適切なSystem.Text.EncodingとともにSystem.IO.StreamReaderクラスのコンストラクタに渡して、ReadLIneメソッドなどで文字列(string)などを得るようなコードが提示されています。↓のような感じ。

http://msdn.microsoft.com/ja-jp/library/system.net.webresponse.getresponsestream(v=VS.90).aspx

しかしながら・・・このMSDNのサンプルコードには不満があります。というか、このコードの実用性は全く無い。

(サンプルなんだから当然っちゃー、当然なんですけどね。)

だって、取得したストリームに使用されている文字コードをUTF8と決めうちしてるんだもんねぇ~。 Web上のテキスト・リソースは、日本に限っただけでも、主にShift_JIS/EUC-JP/ISO-2022-JP/UTF-8 があるし・・・。

Perlで言えばEncode モジュールのような文字コードを判別する処理が必須なんですけども .NET Framework の標準クラスライブラリにはそういったクラスが無い。まぁ、彼ら(.NET Framework の開発者)からしてみれば同じ言語なのに3つも4つも文字コード規格がある、なんてことにいちいち対応してられないんでしょうね。

で、ストリームから得られたバイト配列の文字コードを判別する方法は、検索してみると、下記に詳しく解説されていますね。

文字コードを判別する (DOBON.NET)

この、mlang を使用する方法がお手軽でいいんじゃないかと・・・。Internet Explorer で使われている mlang.dll を利用します。上記サイトで解説されているのように、mlang.idlファイル(WindowsSDKに同梱されています) から、タイプライブラリを生成してレジストリに登録し、Visual C#のプロジェクトに参照を加えることでmlang(MultiLanguage)を利用します。Visual StudioのIDEを使わない場合は、tlbimp.exe で タイプライブラリからプロキシ(ラッパー?)アセンブリ(DLLファイル)を生成して、コンパイルするときにそのDLLを/rオプションで読み込ませます。

//コードの大半は、上記サイトからのコピペ。
//バイト配列から文字コードを推測して、System.Text.Encoding を返す
using System.Text;
using MultiLanguage;

public static Encoding DetectEncoding(byte[] bytes)
{
  sbyte[] sb = (sbyte[])(object)bytes;
  int len = sb.Length;
  var ml = new CMultiLanguageClass() as IMultiLanguage2;
  int scores = 5; //実際には1で十分だけど
  var detects = new tagDetectEncodingInfo[scores];

  //文字コード判別
  ml.DetectInputCodepage(8|4,0,ref sb[0],ref len,out detects[0],ref scores);

  //DetectInputCodepageメソッドから返ると、scoresには実際にdetect配列に格納された候補数が入っている。
  //返された、DetectEncodingInfoとscores(候補数)を見てみる。
  //DetectEncodingInfo.nCodePageがコードページ。
  //Windows(日本語)なら932とかUTF-8なら65001とか入っているはず。
  Console.Error.WriteLine("Scores = {0}",scores);
  for(int i=0;i<scores;i++)
    {
      Console.Error.Write("CodePage[{0}] = {1}; ",i,(int)detects[i].nCodePage);
      Console.Error.Write("Confidence[{0}] = {1}; ",i,(int)detects[i].nConfidence);
      Console.Error.Write("Percentage[{0}] = {1}; ",i,(int)detects[i].nDocPercent);
      Console.Error.WriteLine("");
    }

  //一番初めのtagDetectEncodingInfoのEncodingを取得
  Encoding enc = Encoding.GetEncoding((int)detects[0].nCodePage);

  //後始末らしい。
  System.Runtime.InteropServices.Marshal.ReleaseComObject(ml);

  return enc;
}

COMインターフェイスのIMultiLanguage2::DetectInputCodepage() メソッドは、複数の候補を返すことができ、DetectEncodeInfo 構造体にどのくらい正確かを示すint値が入っているようで、それらから判断するようです。ちなみに、IMultiLanguage2::DetectInputCodepage() メソッドの第一引数には、判別するバイト列がどのようなものかを予め入力できて、僕は、4(MLDETECTCP_DBCS) | 8(MLDETECTCP_HTML) = 12 を指定しました。

ただ、僕の環境~WIndows7 x64版~では、WindowsSDK v7.0 + .NET Framework 3.5 だと、ソースをビルドするとき、ターゲットプラットフォームを X86 (つまり32ビットね) にしないと、メチャクチャなコードページ値が返るか、COM参照の例外(HRESULT: E_FAIL)が発生して落ちてしまいます。
ノートパソコン(VAIO Z)の方は、WindowsSDK v7.1 + .NET Framework 4 なんですが、こちらはエラーが出ず正常。

ちょっと原因分からず。タイプライブラリはちゃんと64bit,32bitとともに登録しているんですが・・・やり方が間違ってんでしょうかねぇ・・・IE9をインストールしているので、古いWindowsSDKだとダメなのかもしれませんね(あくまで推測)

ただ、.NET Framework から COM を利用するあたりのマーシャリングとかの仕組みがいまいち分かってないので、また勉強しないといけないな~。

javascript コードをウェブページに注入

ブックマークレットって、意外に便利ですよね。

でも、ネット(主にWeb)にベッタリ依存している人は別として(笑)、一般的にはあんまり浸透していないんじゃないかと思います。というか、言葉を知ってても、一体何なのか分かっている人は、もっと少ない感じ。

実は僕もブックマークレットの便利さを、つい最近知ったんで(^^;;;

ブックマークレットの実体はブラウザのブックマークに登録された、javascriptなどの極少量のスクリプトコードなんですが・・・、この「極少量」っていうところがミソで、FireFoxやChromeのブラウザのアドオンや拡張機能を実装するほどでもない機能をブックマークレットで・・・ってとこでしょうか。

要は、任意のページにjavascriptコードを注入できる、ということ。

僕がよく使うのはブログなどに表示される広告削除。。。だって読みづらいったらありゃしねー。ブログサイト毎に作ってブックマークに登録しとけば非常に便利。

特に、iPhone のSafariブラウザでのブログ閲覧は、もう最悪。狭い領域にパカパカ広告開いて、ご丁寧にもタッチ操作するたびに違う広告を呼び出すので、3G回線で使うときは固まっちまう。

iPhoneのSafariブラウザも、ブックマークレットは使えるのですが、ブックマークの登録時はURLを変更できないので、一度どこのサイトのどのページでもいいのでブックマークを登録して、そのブックマークを編集・修正することでブックマークレットを登録できます。2度手間なので、なんか一発で登録できるアプリないのかな・・・?

たとえば、アメブロのスマートフォン用ページ(iPhoneでしかアクセスしたことないので他のスマホは知らない)では、↓ のように、広告バー?が表示されます。

画像

アメバさんには悪いけど、これヒジョーにうっとおしい。固定されていれば文句はないんですが、タッチ操作するたびに消えたり・表示されたりするので、動作が遅くなるし、誤ってクリックしようもんなら・・・

ムカーーーーーー!金麦なんて、ぜってー、飲まねーーーー!

とくるんですよね(笑) ちなみに、僕はビール類とかお酒自体飲まないんで。そんなの知ったことねーか(^^;;; とにかく、どんなブログサイトでも、こんなスタイルで表示されてるんじゃないでしょうか? ブックマークレットで消しましょう。

あくまで、アメブロの例にしますが、他でも同じ感じです。 手順は簡単で、

  1. この広告に使用されているDOMノードを特定
  2. DOMノードを削除

これだけ。具体的なコードは・・・

javascript:(function(){var ads=document.getElementsByClassName("adBanner");for(var i=0;i<ads.length;i++){ads.item(i).parentNode.removeChild(ads.item(i));}})();

アメブロの広告は、<div class=”adBanner”>というタグが使われているので、getElementsByClassNameで取得して削除しているだけ。

DOMノードの解析に、僕はPCでGoogle Chromeの「デベロッパー・ツール」を使ってます。Chromeを起ち上げる際、起動オプションとして、

--user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_5 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5"

を付けて起動すると、ブラウザの表示がスマートフォンと同じようになりますので、そこでデベロッパーツールを表示させて広告表示に使用されているDOMノードを特定できます。FireFoxやIE/Operaでも同様のツールが使えると思います。

で、上記コードをコピペして、ブックマークに登録。
ただ問題は、ページ毎にブックマークを選択しないといけないので、PCのブラウザならワンクリックでいけますが、iPhoneだとメンドクサイですね~

ノードを特定したいときにjQueryとか使いたいとか、もっと長いコードを登録したい場合は、document.createElement(“script”)などで外部スクリプトファイルを読み込ます方法があります。

正しく使えば非常に有用なんですが、少し考えると分かりますが、非常に危険なコードを簡単に注入することが可能です。使用者本人のコードなら問題はないと思いますが、他人が作ったブックマークレットは、リスクが伴います。諸刃の剣ですかね。

Out of sync with server ??

VIsual C# 2008 と MySQL Connector / NET 6.4.3 で、ブロードキャストされるUDPパケットを受信してDBのテーブルへ格納する、という一連のコードをC#で組んでましたら、ローカルに立てたMySQLには普通に接続できてSQLクエリーも処理されるんだけど、LAN内の他のマシン上のMySQLサーバーへSQL文を投げると・・・

Out of sync with server

という今まで見たことのないメッセージとともに例外がスローされてしまって困ってました。

コードはごく普通のものなんだけど・・・。

/*
  myInsertQuery は、テーブルへINSERTするSQL文(string型)
*/
using System.Data.Common;
...
try
{
  lock(DBConnection)
    {
      DBConnection.Open();
      
      DbCommand command = DBConnection.CreateCommand();
      command.CommandText = myInsertQuery;
      command.ExecuteNonQuery();
      
      DBConnection.Close();
    }
}
catch(Exception e)
{
  MessageBox.Show(e.Message);
  return false;
}

クライアントと同じマシン上のMySQLサーバーへのクエリーは成功するんですが、LAN内の他のマシン上のMySQLサーバーへのクエリーがどうやっても “Out of sync with server”というエラー。

ローカルマシンは、WindowsXP + MySQL 5.1 (正常に処理される)
リモートマシンは、CentOS 5.5 + MySQL 5.0.77 (エラーが出る)

訳が分からない。で、MySQL Connector/NET が6.4.4にマイナーアップしていたので、入れ替えたら、普通に成功した。

なんだよ! ただのConnectorのバグかよ・・・orz

ググったら、同じエラー・メッセージでbugレポート出てた・・・。

Perlの綺麗な始め方は?

特に意味ないです。

Perl コードを書くとき、↓ のようなテンプレートから mainサブルーチンを書くようにしてました。

#!/usr/bin/perl
use strict;
use warnings;

#スタートアップ・コード
sub main
{
  my @argv = @_;
  
  print &quot;Content-type: text/plain;charset=utf-8\n\n&quot;;
  print &quot;ほげほげ\n&quot;;
}

#その他のコード
sub func
{
  1;
}
#スクリプト実行開始
main(@ARGV);
#スクリプト実行終了

ファイルの最後でmain をコールすることで、Cとかのmain関数を真似て、自己満足してたんですが・・・

なんか最後に main をコールするところが、ちょっと格好悪く感じて、他に良い書き方ないかな~、と模索してました。バカでしょ(^^;;;

で、最近 jQuery を使ってjavascriptコードを組むことが多いので、その作法にならって、下記のようにしてみた。

use strict;
use warnings;

#スタートアップコード
&amp;{
  sub
  {
    my @argv = @_;
    print &quot;Content-type: text/plain;charset=utf-8\n\n&quot;;
    print &quot;ほげほげ\n&quot;;
  }
}(@ARGV);

#その他のコード
sub func
{
  ...
}

main というCの名残が消えてしまいましたが・・・、ちょっとスッキリ! ^^

それと、Perlモジュールを作るとき、たとえばコンストラクタ(new)で以下のようにハッシュをクラスに関連づけることが多いのですが、

sub new
{
  my $param = shift;
  my $class = ref($param) || $param;
  my $this = { x =&gt; [], y =&gt; {} };

  bless($this ,$class);

  return $this;
}

問題は、そのゲッターとかセッター。
$this->{x} とか $this->{y} とかでアクセスするのはちょっと頂けない。とはいいつつ、例えば、

sub x { return shift-&gt;{x};}
sub y { return shift-&gt;{y};}

とかやるのは正直面倒。最近知ったのですが(^^;;; caller という関数が使えば・・・

sub getter {
  my @name = split(/::/,(caller 1)[3]);
  return shift-&gt;{pop @name};
}
sub x { return shift-&gt;getter;}
sub y { return shift-&gt;getter;}

#追記  ↑より↓の方がコピペしなくていいかもネ。あってんのかな?
no strict 'refs';
foreach my $name_(qw/x y/)
{
  *{__PACKAGE__.'::'.$name_} = sub { return shift-&gt;{$name_};}
}

という方法で・・・・、あ、Class::Accessor 使えば良かったんだ。。。orz