久々に出しました。Loqui 0.5.4です。ほぼほぼSolaris11で動くようにしただけなので、すでに動いてる環境の方々は別に上げなくても大丈夫です。具体例がSolaris11ですが、特殊対応をしたわけではなく、「これだと裏でunsigned使われてたらうまくいかないよなぁ」という感じのものを直しただけなので、もしかしたら前は動かなかったけどこれで動くようになったプラットフォームがあるかもしれません。
aboutの画面を書きかえたときに、「Copyright (C) 2002-2013」というのを見て、ブランクがあるとはいえ結構な時間たってるんだなーと思ってちょっと驚いた。もう10年以上経つのね...
2013-01-27
2013-01-08
ドラッグした範囲をキャプチャしてMediaWikiに上げるツール「ScreenToWiki」を作った
MediaWikiは画像のアップロード面倒だよねーという話があったのですが、「MediaWikiほどにもなればAPIついてるんじゃね?」と「WPFで全画面に透明ウィンドウ張れば、gyazo的範囲選択も別に難しくないよね」「画像保存もKiritoriMageの感じでやればいいよね」となったので、「Wikiにいきなりアップロードするツール作るの簡単じゃね?」ということで作ってみました。その名もScreenToWiki。
例によって.NET Framework 4.0が必要です。
パスワードの保存は、人が見えない程度にしか変えない(Base64するだけ)ので、まったくおすすめできないです。セキュリティはWikiごとの事情があると思うので、事情に応じておねがいします。
でも他のものにも対応できるようにMEFでクラスを取ってくるようにしているので、IImageUploaderを実装したクラスを作れば他のWikiにもアップロードできますよ! Wikiでなくてもおもしろいかも。
もともとGitHubであげようと思っていたのですが、Goodbye, Uploadsということで、アーカイブのアップロードがなくなってしまったので、オープンソースなWindows系ソフトだったらcodeplexかなぁということでCodeplexに載せてみました。ダウンロードが目立つのでユーザには使いやすいかも。
ダウンロード
ダウンロードはこちらから。例によって.NET Framework 4.0が必要です。
使い方
使い方は、実行すれば選択範囲の選択がでてくるので、ドラッグで選んだあと、WikiのURLとかユーザ名とかを入力すればアップロードできます。gyazoだと思えば概ねOKだと思います。ただ、エラーハンドリングは弱いので、なにかあるとあまりわかりません…。パスワードの保存は、人が見えない程度にしか変えない(Base64するだけ)ので、まったくおすすめできないです。セキュリティはWikiごとの事情があると思うので、事情に応じておねがいします。
罠
MediaWiki 1.16以降でないとuploadのAPIがないそうなので動きません。Ubuntu 12.04が1.15でしばらく悩みました。その他
MediaWikiしか使えないのにToWikiはおかしいじゃないかって?今のところその通りです。でも他のものにも対応できるようにMEFでクラスを取ってくるようにしているので、IImageUploaderを実装したクラスを作れば他のWikiにもアップロードできますよ! Wikiでなくてもおもしろいかも。
もともとGitHubであげようと思っていたのですが、Goodbye, Uploadsということで、アーカイブのアップロードがなくなってしまったので、オープンソースなWindows系ソフトだったらcodeplexかなぁということでCodeplexに載せてみました。ダウンロードが目立つのでユーザには使いやすいかも。
2012-12-23
Linux boxにUSBストレージを接続したとき勝手に同期するようにする
同期とはなんだか難しそうだと思ったあなた!正しいと思います。
そんな「同期」ですが、今日の話は難しくありません。
簡単に書くと、udevデーモンにデバイス固有の設定を入れて、つないだときにrsyncを走らせましょうという話です。
udevは若干変わっているので、最近のUbuntuでも大丈夫だと思うが、RHEL(特に5)だとちょっとわからない。
ただし、GNOMEが動いていたりすると、そっちで勝手にマウントされそうなので、無効にしておいたほうがいいかも。
今回のkoboはsdaとして認識された。
(このSheevaPlugはルートがSDカードだからmmcblk0なのだけど、sdaがシステムになっているデバイスが多いと思うので、この下のコマンド達をパスまでコピペしないよう注意。)
/dev/sdaがデバイスだとわかったので、devadmコマンドのinfo -q path -n /dev/XXXを使って、sysfsでのパスを特定する。
そうしたら、udevadmコマンドのinfo -p [パス] -aを使って、デバイスに関する情報を表示する。
(もちろん、この2つのコマンドは$()とかを使って1行で実行してもよい)
ただし、万が一でも誤爆しないよう念の為「ATTRS{manufacturer}」と「ATTRS{product}=="eReader"」も使うことにした。なので、今回使う情報は以下。
同じ種類のデバイスをひとつしか持っていないときは、USBデバイスの種類を特定するかぎである「ATTRS{idVendor}」「ATTRS{idProduct}」
を使ってもよいと思う。
/etc/udev/rules.dに以下のように設定ファイルを配置すればOK。
/etc/udev/rules.d/92-kobo.rules
あとは、デバイスをつなぐと、/dev/koboが作られる。
udevadmのman pageを見ると「The udev daemon detects changes automatically」って書いてあるけど、もし認識しなかったら以下のコマンドを叩いてみるといいかも。
まずマウントするためのディレクトリを作る。
koboの内部ストレージは、パーティションなしでデバイスまるまるマウントすればいいので、以下のような行を
/etc/fstabに追加する。
マウントできることを確かめる。
ディスク容量が表示されたら、umountしてデバイスを外す(本当は切り離しもやったほうがいいんだろうけど…)
/usr/local/bin/synckobo.shに配置する。
デバイスをつないで、マウントされてない状態で、スクリプトを叩いて同期できたらOK.
見慣れない"--modify-window=1"については、「rsync FAQ」を参照。vfatならつけておくのが幸せだと思う。
接続して同期されれば成功!
そんな「同期」ですが、今日の話は難しくありません。
簡単に書くと、udevデーモンにデバイス固有の設定を入れて、つないだときにrsyncを走らせましょうという話です。
必要なもの
- 適当なLinux box(今回はDebian squeeze)
- 適当なUSB storage
udevは若干変わっているので、最近のUbuntuでも大丈夫だと思うが、RHEL(特に5)だとちょっとわからない。
ただし、GNOMEが動いていたりすると、そっちで勝手にマウントされそうなので、無効にしておいたほうがいいかも。
デバイスを特定する
つないだら、まずは普通にdmesgコマンドでログを見るか、fdisk -lでデバイスを調べる。今回のkoboはsdaとして認識された。
(このSheevaPlugはルートがSDカードだからmmcblk0なのだけど、sdaがシステムになっているデバイスが多いと思うので、この下のコマンド達をパスまでコピペしないよう注意。)
/dev/sdaがデバイスだとわかったので、devadmコマンドのinfo -q path -n /dev/XXXを使って、sysfsでのパスを特定する。
$ sudo udevadm info -q path -n /dev/sda /devices/platform/orion-ehci.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:0/block/sda
そうしたら、udevadmコマンドのinfo -p [パス] -aを使って、デバイスに関する情報を表示する。
(もちろん、この2つのコマンドは$()とかを使って1行で実行してもよい)
$ sudo udevadm info -p /devices/platform/orion-ehci.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:0/block/sda -a
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/platform/orion-ehci.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:0/block/sda':
KERNEL=="sda"
SUBSYSTEM=="block"
(略)
looking at parent device '/devices/platform/orion-ehci.0/usb1/1-1':
KERNELS=="1-1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}=="Self-powered"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="c0"
ATTRS{bMaxPower}==" 2mA"
ATTRS{urbnum}=="229"
ATTRS{idVendor}=="2237"
ATTRS{idProduct}=="4173"
ATTRS{bcdDevice}=="0110"
ATTRS{bDeviceClass}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bNumConfigurations}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{speed}=="480"
ATTRS{busnum}=="1"
ATTRS{devnum}=="13"
ATTRS{version}==" 2.00"
ATTRS{maxchild}=="0"
ATTRS{quirks}=="0x0"
ATTRS{authorized}=="1"
ATTRS{manufacturer}=="Kobo"
ATTRS{product}=="eReader"
ATTRS{serial}=="XXXXXXXXXXXXX"
(略)
looking at parent device '/devices/platform/orion-ehci.0':
KERNELS=="orion-ehci.0"
SUBSYSTEMS=="platform"
DRIVERS=="orion-ehci"
ATTRS{modalias}=="platform:orion-ehci"
looking at parent device '/devices/platform':
KERNELS=="platform"
SUBSYSTEMS==""
DRIVERS==""
重要なのは「ATTRS{serial}」というところ。これをキーにデバイスを決定する。ただし、万が一でも誤爆しないよう念の為「ATTRS{manufacturer}」と「ATTRS{product}=="eReader"」も使うことにした。なので、今回使う情報は以下。
- ATTRS{manufacturer}=="Kobo"
- ATTRS{product}=="eReader"
- ATTRS{serial}=="XXXXXXXXXXXXX"
同じ種類のデバイスをひとつしか持っていないときは、USBデバイスの種類を特定するかぎである「ATTRS{idVendor}」「ATTRS{idProduct}」
を使ってもよいと思う。
udevの設定をする(1) - デバイスに名前をつける
まずは、調べた情報を使って、/dev以下に勝手なシンボリックリンクを置く。今回はkoboなので/dev/kobo。/etc/udev/rules.dに以下のように設定ファイルを配置すればOK。
/etc/udev/rules.d/92-kobo.rules
KERNEL=="sd*", ACTION=="add", \
ATTRS{manufacturer}=="Kobo", ATTRS{product}=="eReader", \
ATTRS{serial}=="XXXXXXXXXXXXX" \
NAME="%k", SYMLINK="kobo"
あとは、デバイスをつなぐと、/dev/koboが作られる。
$ ls -l /dev/kobo /dev/sda lrwxrwxrwx 1 root root 3 Dec 23 18:19 /dev/kobo -> sda brw-rw---- 1 root floppy 8, 0 Dec 23 18:19 /dev/sda
udevadmのman pageを見ると「The udev daemon detects changes automatically」って書いてあるけど、もし認識しなかったら以下のコマンドを叩いてみるといいかも。
$ sudo udevadm control --reload-rules
fstabの設定
/dev/koboがつながったときに、マウントできるようにする。まずマウントするためのディレクトリを作る。
$ sudo mkdir /media/kobo
koboの内部ストレージは、パーティションなしでデバイスまるまるマウントすればいいので、以下のような行を
/etc/fstabに追加する。
/dev/kobo /media/kobo vfat rw,codepage=932,iocharset=utf8 0 0
マウントできることを確かめる。
$ sudo mount /media/kobo $ df -h
ディスク容量が表示されたら、umountしてデバイスを外す(本当は切り離しもやったほうがいいんだろうけど…)
$ sudo umount /media/kobo
自動同期するスクリプトを用意する
マウントしてrsyncし、アンマウントしてメールを送るだけの簡単なスクリプトを用意して、/usr/local/bin/synckobo.shに配置する。
デバイスをつないで、マウントされてない状態で、スクリプトを叩いて同期できたらOK.
見慣れない"--modify-window=1"については、「rsync FAQ」を参照。vfatならつけておくのが幸せだと思う。
udevの設定をする(2) - 接続時にスクリプトを実行する
さきほど作った設定ファイルに「RUN」を追加する。KERNEL=="sd*", ACTION=="add", \
ATTRS{manufacturer}=="Kobo", ATTRS{product}=="eReader", \
ATTRS{serial}=="XXXXXXXXXXXXX" \
NAME="%k", SYMLINK="kobo", \
RUN="/usr/local/bin/synckobo.sh"
接続して同期されれば成功!
2012-12-15
PowerShellでApacheのログを集計する
この記事はPowerShell Advent Calendar 2012向けです。PowerShellはまだまだマスターには遠いですが、ありがたみがわかってきたので、書くことにしました。昨日の牟田口さんの記事を見るとAdd-TypeもPSObjectも使わずにPowerShellでクラスを作ってみろと言われているような気がしますが、誘惑を振り切りもともと想定していたネタです。
今日は、PowerShellを使ってApacheのログを集計してみます。Windowsを使っている人だったら、「bashとsedともげもげで十分じゃん」「Perl使うよ」と言わず、いいところがあるので見てください。特にログ集計でshもLLもわずらわしくなって、SQLiteやPostgreSQLにワンタイムのDBを作ってSQLでログ集計したことがある人におすすめです。
ひとつ正規表現を変えるだけなので、ちょっと違ってても大きな問題はないです。
以下のスクリプトを(Win7/Vistaの場合)「C:\Users\(ユーザ名)\Documents\WindowsPowerShell\Modules\ApacheLogParser\ApacheLogParser.psm1」に配置します。
ここはあまりPowerShellっぽくないので、ポイントだけ書きます。
returnを書いているので、値を返すにはそうしないといけなそうに見えますが、このケースは別に書かなくても平気です。どういうことかというと、値を返すコマンドレットを使ったら、|Out-Nullとか、>$null とかして捨てないと、どんどん「流れ」ていってしまうのです。shでコマンドを実行したらstdout/stderrに出力が出るようなものです。不要なものは捨てるようにします。
Pathを受けとるのではなく、ログ本文を受け取るようにして、この関数自体をパイプで使うのが「ぽい」ような気もしますが、物理ファイルを指定するケースが多そうなのでとりあえずこの形にしました。需要に応じて変更するのがいいと思います。Perlの<>や、RubyのARGFみたいにstdin or 引数のファイルからのInputというのが簡単にできるといいのですが。
試しに、「Read-ApacheLog」にログを食べさせてみましょう。そのまま出すと出すぎるので、「Select-Object(エイリアス:select)」の-Lastオプションで数を減らしたものが以下です。
各行の「 : 」の左側に書かれたプロパティ名を使って、さまざまな操作をしていくことになります。
"/pukiwiki"にたくさんアクセスされているようです。
例がとてもよくないのですが、0時台にたくさんアクセスがあり、18時台に少しアクセスがあるようです。他の時間帯には使われていません。
(日付+時間を作るには、若干トリッキーですがDateTime型の「Date」プロパティで日付部をもらい、「AddHours」メソッドを呼び出して時間を付加しています。)
このようにひとつのログ形式に対して、パーサを1回作ってあげれば、あとは直感的に集計処理が可能になります。量的にどこまで耐えられるかは検証していないのですが、GUIな表計算ソフトでうんぬんするよりはさっくりできると思います。もちろんApacheのログでなくて、テキストのログは同じように処理することが可能ですので、しょっちゅうログを処理するような方は、試してみてはいかがでしょうか。
今日は、PowerShellを使ってApacheのログを集計してみます。Windowsを使っている人だったら、「bashとsedともげもげで十分じゃん」「Perl使うよ」と言わず、いいところがあるので見てください。特にログ集計でshもLLもわずらわしくなって、SQLiteやPostgreSQLにワンタイムのDBを作ってSQLでログ集計したことがある人におすすめです。
メリット
そもそもなにがうれしいの?という部分ですが、PowerShellには「パイプラインをオブジェクトが流れる」という特徴があり、これを利用するとshやPerlやRubyでの処理と比較して以下のようなメリットがあります。- (shで処理する場合と比較して)パーサがパイプにテキストではなく、オブジェクト群を流せるので、再利用が容易。きちんとパイプに流れてきたオブジェクトを処理するコマンド(コマンドレット)が用意されている。空白区切りテキストを見て、「うーん、User-Agent何番目かな…1, 2, 3...」と数えなくてよい。
- (Perl等で処理する場合と比較して)sedやawk, sort等々をつなげて作っていくような考え方で、パイプに流して絞り込んでいく形で処理を記述できるので、試行錯誤で試していくのがラク。
対象ログ
今回対象とするのは、"combined"として定義されているタイプのログです。こんな感じです。ひとつ正規表現を変えるだけなので、ちょっと違ってても大きな問題はないです。
パーサ(モジュール)の準備
まずは、ログのパーサを準備します。とはいっても、テキストにマッチしてオブジェクトをばんばん返す(流す)関数を書くだけです。以下のスクリプトを(Win7/Vistaの場合)「C:\Users\(ユーザ名)\Documents\WindowsPowerShell\Modules\ApacheLogParser\ApacheLogParser.psm1」に配置します。
ここはあまりPowerShellっぽくないので、ポイントだけ書きます。
- 関数名は動詞-名詞にする。使える動詞は「Approved Verbs for Windows PowerShell Commands」を参照。
- 正規表現のマッチ結果である$matches ハッシュテーブルを活用する。
- PowerShellの文字列の特殊文字のエスケープは`。 例: `"。
- 正規表現では、名前つきキャプチャを活用する。 例: (?<Host>.*?)。こうすると、$matchesに指定した名前で入ってくる。
- パイプラインには、ハッシュをPSObjectにして流す。その際は、「New-Object」の-Propertyを使うと便利。
returnを書いているので、値を返すにはそうしないといけなそうに見えますが、このケースは別に書かなくても平気です。どういうことかというと、値を返すコマンドレットを使ったら、|Out-Nullとか、>$null とかして捨てないと、どんどん「流れ」ていってしまうのです。shでコマンドを実行したらstdout/stderrに出力が出るようなものです。不要なものは捨てるようにします。
Pathを受けとるのではなく、ログ本文を受け取るようにして、この関数自体をパイプで使うのが「ぽい」ような気もしますが、物理ファイルを指定するケースが多そうなのでとりあえずこの形にしました。需要に応じて変更するのがいいと思います。Perlの<>や、RubyのARGFみたいにstdin or 引数のファイルからのInputというのが簡単にできるといいのですが。
作業開始前の準備
使う段になったら、powershellを起動後、「Import-Module」で先程のモジュールをロードします。PS C:\temp> Import-Module ApacheLogParser
試しに、「Read-ApacheLog」にログを食べさせてみましょう。そのまま出すと出すぎるので、「Select-Object(エイリアス:select)」の-Lastオプションで数を減らしたものが以下です。
各行の「 : 」の左側に書かれたプロパティ名を使って、さまざまな操作をしていくことになります。
実践
先程のパーサを使って、具体的に集計をしてみます。トータルのアクセス数
もっともシンプルなものとして、全てのアクセス数を数えてみましょう。ここでは、いろいろ数えてくれる「Measure-Object(エイリアス: measure)」を使います。PS C:\temp> Read-ApacheLog .\access.log | measure Count : 281 Average : Sum : Maximum : Minimum : Property :281アクセスあることがわかりました(テスト用に作ったので少なくてごめんなさい)
特定のパスのみのアクセス数
先の「Measure-Object」に加えて、与えた条件を満たすもののみを流す「Where-Object(エイリアス:where、?)」を使います。PS C:\temp> Read-ApacheLog .\access.log | where { $_.Path -eq "/favicon.ico" } | measure
Count : 2
Average :
Sum :
Maximum :
Minimum :
Property :
"/favicon.ico"へのアクセスは2回あるようです。ステータス別アクセス数
ステータスコードで分類してみましょう。今度は指定したプロパティが同じ値を持つオブジェクトをグループ化する「Group-Object(エイリアス:group)」と、表示順を整えるために「Sort-Object(エイリアス:sort)」を使います。PS C:\temp> Read-ApacheLog .\access.log | group Status | sort Name
Count Name Group
----- ---- -----
259 200 {@{Time=2012/12/14 0:09:47; Host=127.0.0.1; Request=OPTIONS * HTTP/1.0; TimeString=1...
1 301 {@{Time=2012/12/14 0:17:35; Host=10.0.2.2; Request=GET /mediawiki/index.php HTTP/1.1...
1 302 {@{Time=2012/12/14 0:17:44; Host=10.0.2.2; Request=POST /mediawiki/index.php?title=M...
11 304 {@{Time=2012/12/14 0:17:41; Host=10.0.2.2; Request=GET /mediawiki/skins/common/share...
9 404 {@{Time=2012/12/14 0:14:26; Host=10.0.2.2; Request=GET /favicon.ico HTTP/1.1; TimeSt...
200が多いですが、3xxや4xxもちらほらあることがわかります。トップディレクトリごとのアクセス数
今度はトップディレクトリごとのアクセス数を集計してみましょう。「Group-Object」に置換を行うブロックを渡します(パスのファイル名部分と、サブディレクトリ以下をカットします)。PS C:\temp> Read-ApacheLog .\access.log | group { $_.Path -replace '[^/]*$','' -replace '^(/.+?)/.*','$1' } | sort -Descending Count
Count Name Group
----- ---- -----
207 /pukiwiki {@{Time=2012/12/14 0:23:12; Host=127.0.0.1; Request=GET /pukiwiki/ HTTP/1.1; TimeStr...
58 /mediawiki {@{Time=2012/12/14 0:14:30; Host=10.0.2.2; Request=GET /mediawiki/ HTTP/1.1; TimeStr...
11 {@{Time=2012/12/14 0:09:47; Host=127.0.0.1; Request=OPTIONS * HTTP/1.0; TimeString=1...
4 / {@{Time=2012/12/14 0:14:26; Host=10.0.2.2; Request=GET / HTTP/1.1; TimeString=14/Dec...
1 /trac {@{Time=2012/12/14 0:17:51; Host=10.0.2.2; Request=GET /trac/ HTTP/1.1; TimeString=1...
"/pukiwiki"にたくさんアクセスされているようです。
時間帯別アクセス数
パーサスクリプトの中で、時刻はSystem.DateTime型に変換しておいたので「Hour」プロパティが使えます。今までと同じように「Group-Object」を使えばOKです。PS C:\temp> Read-ApacheLog .\access.log | group { $_.Time.Hour }
Count Name Group
----- ---- -----
227 0 {@{Time=2012/12/14 0:09:47; Host=127.0.0.1; Request=OPTIONS * HTTP/1.0; TimeString=1...
54 18 {@{Time=2012/12/14 18:30:05; Host=127.0.0.1; Request=GET /pukiwiki/index.php?InterWi...
例がとてもよくないのですが、0時台にたくさんアクセスがあり、18時台に少しアクセスがあるようです。他の時間帯には使われていません。
日付/時間帯別アクセス数をCSV出力する
CSV出力することも可能です。「Export-Csv」コマンドレットを使います。(日付+時間を作るには、若干トリッキーですがDateTime型の「Date」プロパティで日付部をもらい、「AddHours」メソッドを呼び出して時間を付加しています。)
PS C:\temp> Read-ApacheLog .\access.log | group { $_.Time.Date.AddHours($_.Time.Hour) } | select Name,Count | Export-Csv output.csv -Encoding default
平均レスポンスバイト数を調べる
combinedフォーマットには「%b:レスポンスのバイト数」があるので、レスポンスのサイズを集計することも可能です。「Where-Object」「Measure-Object」を使って、/pukiwiki以下の返答の平均バイト数を出してみましょう。PS C:\temp> Read-ApacheLog .\access.log | where { $_.Path -match "/pukiwiki" } | measure -Property BytesSent -Average
Count : 207
Average : 3019.21256038647
Sum :
Maximum :
Minimum :
Property : BytesSent
このようにひとつのログ形式に対して、パーサを1回作ってあげれば、あとは直感的に集計処理が可能になります。量的にどこまで耐えられるかは検証していないのですが、GUIな表計算ソフトでうんぬんするよりはさっくりできると思います。もちろんApacheのログでなくて、テキストのログは同じように処理することが可能ですので、しょっちゅうログを処理するような方は、試してみてはいかがでしょうか。
2012-12-03
ILGeneratorの「向こう側」でTypeやMethodInfoを得るために"ldtoken"命令と~FromHandleメソッドを使う
最初のころは黒魔術だったのにだんだん慣れてきちゃった。とはいえ、書いておかないと忘れかねないので書いておく。今回のテーマはタイトルの通りなので、タイトルを読んでわかる人には不要の話。
しかし、メタデータ トークンとは何か?
ということで、詳細は上記ページに書いてあるが、型やメソッドなんかのプリミティブな表現くらいに思っておけば大丈夫だと思う。
幸いなことに、ILGenerator.Emit(OpCodes.Ldtoken, xxx)なら、TypeやMethodInfoを渡せば裏でうまいことやってくれるので、あまり意識しなくていい。
このメタデータトークンを使って"ldtoken"を使うと、「RuntimeHandle」が得られる。またややこしいものがでてきたが、
ということで、メタデータトークンの構造体表現くらいだと思っておけばいいと思う。
これでTypeが得られたので、あとは好きに使えばOK.
たとえば、Nameを表示したければこんな感じ。
このコードで生成したコードがこんな感じ。
概ねTypeの場合と同じだが、Methodの場合は二つ注意点がある。
名前を表示したければ、同じようにこんなかんじ。
これで生成されたコードがこんな感じ。
また、RuntimeMethodHandleについては、IL生成をしていない場面でもメソッドを識別するのに便利な場面があるかもしれない。
概要
ILGeneratorを使ってコードを書いているような状況では、ILGeneratorの「向こう側」(生成したILコードが動作している文脈のこと。ILを使ってコードを生成していると、文脈を区別したくなるので、勝手にこう呼んでいる)でTypeやMethodInfoなどのリフレクション用のオブジェクトが欲しくなるときがある。もちろん、Nameを使ってGetMethod等々を「向こう側」で呼ぶということも可能だが、「こっち側」(ILコードを生成している側の文脈)でType等々が識別できている状況では、もう少しいい方法がある。それは以下の通り。- "ldtoken"命令と「メタデータ トークン」を使って、評価スタックにRuntimeHandleをpushする。
- 各種~FromHandleメソッドを使って、TypeやMethodInfoなどを得る。
実践 - Typeの場合
Typeの場合こんな感じ。var typeObj = il.DeclareLocal(typeof(Type)); // ローカル変数の準備
il.Emit(OpCodes.Ldtoken, typeof(SampleClass)); // -- (1)
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // -- (2)
il.Emit(OpCodes.Stloc, typeObj); // ローカル変数へのストア
(1) メタデータトークンからRuntimeTypeHandleを得る
まず、「メタデータ トークン」を使ってldtoken命令を実行し、RuntimeHandleを得る。書式 / アセンブリ形式 / 説明(from OpCodes.Ldtoken フィールド)
D0 < T > / ldtoken token / メタデータ トークンをそのランタイム表現に変換します。
しかし、メタデータ トークンとは何か?
メタデータは、ランタイム型 (クラス、値型、およびインターフェイス)、グローバル関数、グローバル変数などの抽象化の宣言情報です。(from メタデータ トークン)
ということで、詳細は上記ページに書いてあるが、型やメソッドなんかのプリミティブな表現くらいに思っておけば大丈夫だと思う。
幸いなことに、ILGenerator.Emit(OpCodes.Ldtoken, xxx)なら、TypeやMethodInfoを渡せば裏でうまいことやってくれるので、あまり意識しなくていい。
このメタデータトークンを使って"ldtoken"を使うと、「RuntimeHandle」が得られる。またややこしいものがでてきたが、
内部メタデータ トークンを使用する型を表します。(RuntimeTypeHandle 構造体)
ということで、メタデータトークンの構造体表現くらいだと思っておけばいいと思う。
(2) RuntimeTypeHandleからTypeオブジェクトを得る
RuntimeTypeHandleからTypeオブジェクトを得るには、Type.GetTypeFromHandle(RuntimeTypeHandle)を使う。これでTypeが得られたので、あとは好きに使えばOK.
たとえば、Nameを表示したければこんな感じ。
// 試しに名前を出してみる
il.Emit(OpCodes.Ldloc, typeObj);
il.EmitCall(OpCodes.Callvirt, typeof(Type).GetProperty("Name").GetGetMethod(), null);
il.EmitCall(OpCodes.Call,
typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
null);
このコードで生成したコードがこんな感じ。
ldtoken [HelloMetadata]HelloMetadata.SampleClass call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) stloc.0 ldloc.0 callvirt instance string [mscorlib]System.Reflection.MemberInfo::get_Name() call void [mscorlib]System.Console::WriteLine(string)
実践 - MethodInfoの場合
MethodInfoの場合はこんな感じ。var methodInfoObj = il.DeclareLocal(typeof(MethodInfo));
il.Emit(OpCodes.Ldtoken, typeof(SampleClass).GetMethod("SampleMethod"));
il.EmitCall(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new Type[] { typeof(RuntimeMethodHandle) } ), null);
il.Emit(OpCodes.Stloc, methodInfoObj);
概ねTypeの場合と同じだが、Methodの場合は二つ注意点がある。
- GetMethodFromHandle()は、MethodInfo/ConstructorInfoの親であるMethodBaseに存在する。
- GetMethodFromHandle()は、Typeが指定できるオーバーロードがあるので、メソッド名だけではGetMethod()できない。
名前を表示したければ、同じようにこんなかんじ。
// 試しに名前を出してみる
il.Emit(OpCodes.Ldloc, methodInfoObj);
il.EmitCall(OpCodes.Callvirt, typeof(MethodInfo).GetProperty("Name").GetGetMethod(), null);
il.EmitCall(OpCodes.Call,
typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
null);
これで生成されたコードがこんな感じ。
ldtoken method void [HelloMetadata]HelloMetadata.SampleClass::SampleMethod() call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle) stloc.1 ldloc.1 callvirt instance string [mscorlib]System.Reflection.MemberInfo::get_Name() call void [mscorlib]System.Console::WriteLine(string)
その他
FieldInfoでは試してないけど、OpCodes.Ldlocの説明を見る限り、概ね同じ方法でいけると思う。また、RuntimeMethodHandleについては、IL生成をしていない場面でもメソッドを識別するのに便利な場面があるかもしれない。
2012-12-02
自室の照明をスマートフォンからOn/Offする
寝たいんだけど、電気消しに行くのめんどくさくて寝れない。そんなことありませんか?今回はそんな人向けのソリューション。でも、うまく動かなくて勝手についたり、チカチカしちゃったりしても自己責任でお願いします。
ちなみに、おおかたの期待を裏切り、今回はハードウェア工作はナシ。既製品だけ。
複雑だろうか?ちなみに、結構昔からある方法。
まずvid/pidの設定。
/etc/modprobe.d/remosta.conf作成
起動時にロードされるようにする。
/etc/modules追記
このデバイスが使える人のグループを作る。
このデバイスを/dev/remostaXにし、グループを変更する。
/etc/udev/rules.d/92-remosta.rules作成(92なのは91でこのデバイスのグループ変更がされていたから)
再起動して、以下の状態になっていればOK。
Ruby/serialportが必要なので、インストールしておく。
あとはこのスクリプトをダウンロードして、以下の通りで動く。
受信
送信
これで「A」と書いてあるほうの黄色いシールがついてるLEDから信号が発信される。
https://github.com/sunnyone/lightremo
スクリプトを/var/www/lightremoに配置したら、以下の感じで設定する。
www-dataを"remosta"グループに所属させるのを忘れない。
Apacheの設定をリロードして、http://[SERVER]/lightremo/lightremo.cgiにアクセスすれば以下の画面がでてくるはず。
onやoffを押して実際に照明がOn/Offされたら成功!
ちなみに、おおかたの期待を裏切り、今回はハードウェア工作はナシ。既製品だけ。
仕組み
「電気を消したいだけなのに複雑だ」と言われたので絵にしてみた。複雑だろうか?ちなみに、結構昔からある方法。
使った物
- 天井照明器具専用 リモコンスイッチOCR-04 (\1,500くらい)
- パソコン用学習リモコン PC-OP-RS1 (\4,500くらい)
- Debian GNU/Linuxの入った箱:今回は玄箱PRO (\不明:今ならGuruPlug (\20,000くらい)や、Raspberry Pi (\4,000くらい)がいいかも)
作り方
Step1. OCR-04を設置する
OCR-04はこんな形をしたもの。箱に書いてある通り、照明器具と天井の引っ掛けシーリングの間に受信部をセットするだけ。元々のスイッチはOnにしっぱなしにしないと使いものにならないので、ついてきたリモコンは、スイッチがあったところの隣に置いておく。Step2. Linux boxとPC-OP-RS1をつなぐ
今回はNASとして稼動していた玄箱PROを使ってしまったが、USBがついたLinuxの箱ならなんでもいいはず。LinuxからPC-OP-RS1は、USBシリアルデバイスとして認識できるが、VendorID/ProductIDが登録されていないので、指示してあげる必要がある。コマンドは以下の通り。# modprobe ftdi_sio vendor=0x0411 product=0x00b3これで/dev/ttyUSBXができたらひとまずOK。再起動しても認識される&あとで使うための権限が調整できるよう、以下の設定を行う。
まずvid/pidの設定。
/etc/modprobe.d/remosta.conf作成
alias remosta ftdi_sio options ftdi_sio vendor=0x0411 product=0x00b3
起動時にロードされるようにする。
/etc/modules追記
remosta
このデバイスが使える人のグループを作る。
# groupadd remosta
このデバイスを/dev/remostaXにし、グループを変更する。
/etc/udev/rules.d/92-remosta.rules作成(92なのは91でこのデバイスのグループ変更がされていたから)
KERNEL=="ttyUSB*", ACTION=="add", \
ATTRS{idVendor}=="0411", ATTRS{idProduct}=="00b3", \
MODE="0660", GROUP="remosta", NAME="%k", SYMLINK="remosta%n"
ここの設定は間違えるとOSが起動しなくなるから注意!(一回やってHDD抜き出した…)再起動して、以下の状態になっていればOK。
$ ls -al /dev/remosta* /dev/ttyUSB0 lrwxrwxrwx 1 root root 7 Nov 23 20:58 /dev/remosta0 -> ttyUSB0 crw-rw---- 1 root remosta 188, 0 Dec 1 22:59 /dev/ttyUSB0
Step3. コマンドラインで送受信してみる
Rubyでスクリプトを書いたので、それを使う。Ruby/serialportが必要なので、インストールしておく。
# apt-get install libserialport-ruby
あとはこのスクリプトをダウンロードして、以下の通りで動く。
受信
$ ruby remosta.rb recv Receiving. →ここでPC-OP-RS1に対して、リモコンで送ってみる fffff070000e03fc07f80f.... →受信した信号 Received.
送信
$ ruby remosta.rb send fffff070000e03fc07f80f....(信号) Sending. Sent.
これで「A」と書いてあるほうの黄色いシールがついてるLEDから信号が発信される。
Step3. Web UIをつける
凝るほどでもないので、レガシーにRuby/CGIでWebを作った。https://github.com/sunnyone/lightremo
スクリプトを/var/www/lightremoに配置したら、以下の感じで設定する。
<Directory /var/www/lightremo>
AddHandler cgi-script .cgi
AllowOverride None
Options +ExecCGI
</Directory>
今回は自宅の無線LANでしか接続できないところに置いたから、CGIとしてはとくに認証もしていないけど、必要ならかける。www-dataを"remosta"グループに所属させるのを忘れない。
Apacheの設定をリロードして、http://[SERVER]/lightremo/lightremo.cgiにアクセスすれば以下の画面がでてくるはず。
onやoffを押して実際に照明がOn/Offされたら成功!
2012-11-26
Android向けのシンプルな壁紙を作るためのGIMP用スクリプト
いくや先生がこんなことをおっしゃっていたので、作ってみました。祝・初Script-Fu.
ダウンロードはこちら: https://gist.github.com/4143779 (android-wallpaper.scm)
といっても、端末の画面サイズの画像をセンタリングしてAndroid壁紙サイズにリサイズするだけだけども。
しょっちゅう壁紙作って替えたい人向けですね。
この場合の例は、Xperia acroなので480x854。
(詳しく:Androidの壁紙に最適な画像サイズがわからない...助けてライフハッカー!)
横に並べることができない以上、サイドパネルの部分を何らかの部分で埋めないといけないのでinteractiveなGIMPで実装することにしたという流れがあったのであった。
おかげで、10年以上昔に書いてみようと思って書いたことがなかったScript-Fuを書けるようになった。Schemeを実用で使ったのは初めてかも。
おれおれ Script-Fu 入門
ポイントだけ書くと、以下の感じ。
Androidの壁紙が簡単に作れるGIMPの拡張機能(?)とかあればいいのにー。
— いくや (AWASHIRO Ikuya)さん (@ikunya) 11月 25, 2012
ダウンロードはこちら: https://gist.github.com/4143779 (android-wallpaper.scm)
といっても、端末の画面サイズの画像をセンタリングしてAndroid壁紙サイズにリサイズするだけだけども。
しょっちゅう壁紙作って替えたい人向けですね。
使い方
Scriptのダウンロードと配置
ダウンロードしたandroid-wallpaper.scmを以下の場所に配置後、GIMPを起動する。- C:\Users\[username]\.gimp-2.8\scripts (Windows (Vista以降))
- ~/.gimp-2.8/scripts (Linux他)
端末の画面サイズの壁紙候補の画像をロードする
壁紙にしたい画像を読み込み、端末の画面サイズに加工する。変なAndroid壁紙サイズではなく、スペック表に書かれている画面サイズ。この場合の例は、Xperia acroなので480x854。
Script起動
メニュー項目が追加されているので、「画像>Resize for Android Wallpaper」から起動する。調整と保存
「Resize for Android Wallpaper」を実行すると、壁紙候補の画像がセンタリングされた状態で画像が大きくなる。「Wallpaper Layer」というレイヤーが追加されるので、ここに適当なパターンなりグラデーションなりを貼ればシンプルな壁紙の完成。裏話
作る前、Androidの壁紙はホーム画面の枚数分横並びだと誤解してたから、ImageMagickでちゃちゃっとくっつければ終わりだろうと思ったんだけど、画面サイズ2倍の横幅をトータルで合うようにちょいちょいずらすだけっていうやっかいな仕様であった。(詳しく:Androidの壁紙に最適な画像サイズがわからない...助けてライフハッカー!)
横に並べることができない以上、サイドパネルの部分を何らかの部分で埋めないといけないのでinteractiveなGIMPで実装することにしたという流れがあったのであった。
おかげで、10年以上昔に書いてみようと思って書いたことがなかったScript-Fuを書けるようになった。Schemeを実用で使ったのは初めてかも。
Script-Fuの書き方
Script-Fuの書き方は、以下を読めばわかった。おれおれ Script-Fu 入門
ポイントだけ書くと、以下の感じ。
- スクリプトはfoobar.scmとして$HOME/.gimp-2.8/scriptsに配置する。
- スクリプトを配置したら、「フィルター>Script-Fu>スクリプトを再読み込み」で読み直す。
- どんな関数があるかは、「フィルター>Script-Fu>Script-Fu コンソール」でScript-Fuコンソールを起動して「参照」ボタンを押せばインクリメンタルサーチで調べられる。
- スクリプト用の関数を適当にdefineして、script-fu-register関数を呼んで登録する。メニューにも載せたい場合はscript-fu-menu-registerを呼ぶ。
- スクリプト用の関数の引数はscript-fu-registerの引数で決まる。
- どんな感じに書けばいいかはGIMP内蔵のスクリプトを読めばわかる。/usr(C:\Program Files\GIMP 2)/share/gimp/2.0/scripts にたくさんある。
登録:
投稿 (Atom)






