digital hole

Software developing and otherwise.

Flower

Archive for the ‘プログラミング’ Category

シンプルな画像アップロードCGI

cgi-lib.pl を使う場合。

問答無用で全POSTデータを読み込むので、場合によっては効率が悪い。その分コードは美しい(まさに必要な文脈だけを伝えている)。

img_up.pl

#!perl

# cgi-lib.plを使う場合

require "cgi-lib.pl";
$cgi_lib::maxdata = 500000; #必要であれば上限値変更

print "Content-type: text/html; charset=Shift_JIS;\n\n";
print "<html><body>\n";

if($ENV{REQUEST_METHOD} ne 'POST'){
    # フォーム
    print '<form action="img_up.pl" enctype="multipart/form-data" method="POST">';
    print '<input type="file" name="img" size="50">';
    print '<input type="submit" value="送信">';
    print '</form>';
}
else{
    # パラメータ読み取り
    ReadParse(*IN);
   
    # 書き出し
    open(OUT,">test.jpg");
    binmode OUT;
    print OUT $IN{'img'};
    close(OUT);
   
    # 結果
    print 'アップしました。<br><img src="test.jpg">';
}

print "\n</body></html>\n";

CGI.pmを使う場合

必要なときだけ読み込み処理を行えるので、柔軟性がある。

img_up2.pl

#!perl

# CGI.pm を使う場合

use CGI;

print "Content-type: text/html; charset=Shift_JIS;\n\n";
print "<html><body>\n";

if($ENV{REQUEST_METHOD} ne 'POST'){
    # フォーム
    print '<form action="img_up2.pl" enctype="multipart/form-data" method="POST">';
    print '<input type="file" name="img" size="50">';
    print '<input type="submit" value="送信">';
    print '</form>';
}
else{
    # パラメータ読み取り
    my $q = new CGI;
   
    # 読み取り -> $data
    my $file = $q->param('img');
    my $data;
    while(read($file, my $tmp, 1024)){
        $data .= $tmp;
    }
   
    # 書き出し
    open(OUT,">test2.jpg");
    binmode OUT;
    print OUT $data;
    close(OUT);
   
    # 結果
    print "file = $file<br>";
    print 'アップしました。<br><img src="test2.jpg">';
}

print "\n</body></html>\n";

最もシンプル(?)な画像出力CGI

ただ既存の画像を無加工で吐き出すだけ。

#!/uer/local/bin/perl

open(IN,"a.jpg");
binmode IN;
binmode STDOUT;

print "Content-type: image/jpg\r\n\r\n";
while(my $n = read(IN, my $buf, 1024)){
  print $buf;
}

close(IN);

Boost Regex++

C++ で正規表現が使いたい。Boost Regex++ がよさげ。

boost入手・導入

http://www.boost.org/
boost_1_35_0.zip を入手。D:\boost_1_35_0 として解凍。
VCのインクルードパスに D:\boost_1_35_0 を追加。

regexビルド

boostの多くのモジュールはビルドしなくても使えるが、regexはビルドが必要。

Visual Studio 2005 Professional にて。
スタートメニューの [Visual Studio 2005] - [Visual Studio Tools] - [Visual Studio 2005 コマンド プロンプト] を実行。

> cd D:\boost_1_35_0\libs\regex\build
> nmake -f vc8.mak              (lib作成)
> nmake -f vc8.mak install      (VCディレクトリにlibをコピー)

参考:
http://d.hatena.ne.jp/twhs/20080112

コーディング実験

#include <boost/regex.hpp>
#include <string>
using namespace std;

void test()
{
	//簡単な検索 ("abcdefg" から "abc" の有無を判定)
	printf("-- simple --\n");
	{
		const char* source = "abcdefg"; //検索対象
		boost::reg_expression<char, boost::regex_traits<char> > regex("abc"); //正規表現
		boost::match_results<const char*> results; //結果
		//実行
		bool found = boost::regex_search(source, results, regex);
		printf("found = %d\n", found?1:0);
	}

	//抜き出し ("abcdafc" から "a.c" にマッチするものを検索)
	printf("-- pick up --\n");
	{
		const char* source = "abcdafc"; //検索対象
		boost::reg_expression<char, boost::regex_traits<char> > regex("a.c"); //正規表現
		boost::match_results<const char*> results; //結果
		//実行
		bool found = boost::regex_search(source, results, regex);
		if(found){
			string p = results.str(0);
			printf("found = %s\n", p.c_str());
		}
	}

	//カッコ (カッコ付きの正規表現。複数の結果を取得できる)
	printf("-- brackets --\n");
	{
		const char* source = "abcdafc"; //検索対象
		boost::reg_expression<char, boost::regex_traits<char> > regex("(a.c).*(.f)"); //正規表現
		boost::match_results<const char*> results; //結果
		//実行
		bool found = boost::regex_search(source, results, regex);
		if(found){
			for(int i=0;i<(int)results.size();i++){
				printf("found[%d]: pos = %d, len = %d, str = %s\n",
					i, results.position(i), results.length(i), results.str(i).c_str());
			}
		}
	}
}

参考:
http://www.s34.co.jp/cpptechdoc/article/regexpp/

実用に向けたコーディング

#include <boost/regex.hpp>
#include <string>
#include <vector>
using namespace std;
vector<string> get_match_strings(const char* _source, const char* _regex)
{
	const char* source = _source; //検索対象
	boost::reg_expression<char, boost::regex_traits<char> > regex(_regex); //正規表現
	boost::match_results<const char*> results; //結果
	//実行
	bool found = boost::regex_search(source, results, regex);
	//解釈
	vector<string> ret;
	if(found){
		for(int i=0;i<(int)results.size();i++){
			ret.push_back(results.str(i));
		}
	}
	return ret;
}

上のような関数を用意しておくと便利。ちょっと負荷はかかるけど。
で、下のように使う。

void test2()
{
	//文字列内から「URL全体」と「サーバ名」を抜き出し。 (正規表現は適当)
	vector<string> ret = get_match_strings(
		"abc http://www.google.co.jp/gonyo/ponyo/ def",
		"http://([A-Za-z\\.]+)/[A-Za-z\\./]+"
	);
	for(int i=0;i<(int)ret.size();i++){
		printf("match[%d] = %s\n", i, ret[i].c_str());
	}
}

落とし穴メモ

「#include <boost/regex.hpp>」と書くところを
「#include <boost/regex.h>」と書き間違えてしまった場合↓

error C2039: 'reg_expression' : 'boost' のメンバではありません。
error C2065: 'reg_expression' : 定義されていない識別子です。

ややこしやー。

Sleipnirの検索ボックスのホイール動作を無効化

Sleipnir (2.8.2限定) で、検索ボックスでのホイール動作を無効化するアプリを作ってみました(ホントはプラグインとかで実現できればよかったんですけど、Sleipnirのプラグイン仕様を調べるよりも、Win32で直接アプリ組むほうが楽だったので、ただのEXE式となっています)。
http://mofmof.nsf.tc/soft/archives/SleipnirWheel.zip

メカニズム

まず、フック開始部分。ここは定番。

HANDLE hHook=SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hDll, 0);

フックした場所でやること。WM_MOUSEWHEEL メッセージを書き換える。MSG構造体を直接書き換えちゃうのは、ややトリッキーかもしれない。私はフックの定石を知りません。

LRESULT CALLBACK GetMsgProc(int nCode,WPARAM wparam,LPARAM lparam)
{
	if(nCode>=0 && wparam==PM_REMOVE){
		MSG* msg=(MSG*)lparam;
		if(msg->message==WM_MOUSEWHEEL){
			//検索ボックスでのホイールイベントを…
			if(IsSleipnirSearchBox(msg->hwnd)){
				HWND hwndFrame = ::GetAncestor(msg->hwnd, GA_ROOT);
				HWND hwndTab = ::FindWindowEx(hwndFrame, NULL, L"Afx:00400000:0", NULL);
				HWND hwndPage = ::FindWindowEx(hwndTab, NULL, L"IEBrowserFrame", NULL);
				//ページウィンドウでのホイールイベントとして差し替える
				msg->hwnd = hwndPage;
			}
		}
	}
	return CallNextHookEx(hHook,nCode,wparam,lparam);
}

ホイールの発生したウィンドウが検索ボックスであるかどうかの判定。

bool IsSleipnirSearchBox(HWND hwnd)
{
	wchar_t szName[256];

	//Sleipnirであることを確認 -> 異なる場合はfalseを返す
	HWND hwndFrame = ::GetAncestor(hwnd,GA_ROOT);
	if(!::GetWindowText(hwndFrame, szName, _countof(szName)))return false;
	if(wcsstr(szName, L"Sleipnir")==NULL)return false;

	//検索ボックスであることを確認 -> 異なる場合はfalseを返す
	while(1){
		if(!::GetClassName(hwnd, szName, _countof(szName)))break;

		if(wcsicmp(szName, L"Edit")==0)goto next;
		else if(wcsicmp(szName, L"FenrirComboBoxCtrl")==0)return true;
		else if(wcsicmp(szName, L"FenrirSearchWindow")==0)return true;
		else return false;

next:
		hwnd = ::GetParent(hwnd);
	}
	return false;
}

メカニズムの前提条件

Sleipnir 2.8.2 のウィンドウ構造が以下のようになっていることを利用しています(これはSpy++で調べました)。

SleipnirMainWindow
├MDIClient
│└Afx:00400000:0
│ └IEBrowserFrame (ページ表示部分)
└Afx:~
 └FenrirSearchWindow
  └FenrirComboBoxCtrl
   └Edit (検索ボックス)

展望

ホイールイベントが発生する度に文字列判定その他の処理が発生するので、負荷を考えると、あんまりよろしくない気分。Sleipnirプロセスのみに限定してフックを行えるなら、ちょっとは気が晴れるんですが。

理想としては、こんなフックアプリケーション作らなくてもいいように、Sleipnir自身に、検索ボックスでのホイール無効にできる機能が付いてほしい。

参考:

ここのフックの解説は、なんというか、妥協無く検証している感があって、なんか好き。
http://park15.wakwak.com/~opapa/cpp/Hook.htm

signed型の最小値は曲者

C++ の話ですが。(たぶん C でも同じ)

-1 を掛けても符号が反転しない

char c = -128;
char c2 = c * -1;

c2 の値が何になるか、というと、-128 です。
-1 を掛けているのに符合が反転しない!?

計算上は c2 に 128 を代入しようとするけど、128 は char では表せないので、結果としてはオーバーフローして -128 となっているわけです。
ちょっと考えれば簡単な話…ですが、この事実を意識しておかないと痛い目に合うことがあるかもしれません。

問題への遭遇例

たとえば、シミュレートプログラムでボールが壁に当たったときの速度反転。

char speed_x = ?;
if(横の壁に当たった){
  speed_x *= -1;
}

こんな処理は割とよく見かけるものですが、もしも speed_x が -128 だったら、
ボールは壁で跳ね返らずにマイナス方向へ通過してしまうことに…。

とりあえず頭の片隅に置いておきたい

こういった変数に char のような型を使うことや、変数の値の範囲をフルに使うこと自体がナンセンスと言われればそれまでですが、まぁこういう落とし穴もありますよ、というお話。
同様に short であれば -32768 、int であれば -2147483648 が危険地帯ですね。
インクリメント、デクリメントのしすぎで符号が入れ替わっちゃうことは割と多くの人が意識されていることかと思いますが、-1 を掛けたのに符号が反転しないこともあり得るってのは意識してない人が多いんじゃないでしょうか。