VC++ランタイムが不要なPython DLLのビルド
Windows環境でPythonを組み込んだアプリケーションを開発してたんですが、Windows用インストーラでsystem32にコピーされるDLL(python**.dll)は、どうやらVC++ランタイムが必要なようです。
そう、Microsoftから「再頒布可能パッケージ」という名前でダウンロードできるアレ(MSVCR**.dll)です。
VSを持っている人は一緒にインストールされていますけど、普通の人はメーカ製PCでもない限りデフォルトで入っていることがなく、自分の作ったプログラムが他所だと動かない〜! という状況をしばしば作るアレです。
ランタイムのインストーラを組み込むのが正攻法なんでしょうけど、大抵の人はよほど重たいプログラムでない限り、ビルド設定を変更してランタイム不要の実行形式を生成しているんじゃないでしょうか。
御託はいいとして、Python自体はそのようにビルドされていない模様。
というわけで、VC++ランタイムが不要なPythonインタプリタのDLLを、自分でビルドすることにします。
まずは、Python公式へ行って、ソースコードの入ったtarボールをダウンロードしてきます。
とりあえずこの記事の時点では、日本語サイトからはPython 3.2.1のソースが手に入るみたいです。
解凍すると何やら沢山出てきますが、今から用事があるのはPCbuild/pcbuild.sln。これがPython全体のビルドを統括するVS2008のソリューションです。
(旧バージョン用のファイルはPCディレクトリにある)
ソリューションを開くと沢山プロジェクトが出てきますが、とりあえずビルドが必要なのは以下の4個。
- kill_python
- make_buildinfo
- make_versioninfo
- pythoncore
このうちpythoncoreがPythonのDLLを生成するプロジェクトです。
こいつの設定を変えてやります。
プロジェクトのプロパティ から、
→構成プロパティ
→C/C++
→コード生成
→ランタイム ライブラリ
を見てやると、Debugビルドでは/MDd、その他では/MDになっていると思うので、これを全部/MTdと/MTに変更します。
ランタイムを使いたくないVSユーザにはお馴染みの設定ですね(多分)。
しかし、これだけではまだ設定が足りません。もう一つやることがあります。
make_buildinfo.cを開けると、ご親切にも次のようなコードがあるので、
... if (strcmp(argv[1], "Release") == 0) { strcat_s(command, CMD_SIZE, "-MD "); } else if (strcmp(argv[1], "Debug") == 0) { strcat_s(command, CMD_SIZE, "-D_DEBUG -MDd "); } else if (strcmp(argv[1], "ReleaseItanium") == 0) { strcat_s(command, CMD_SIZE, "-MD /USECL:MS_ITANIUM "); } else if (strcmp(argv[1], "ReleaseAMD64") == 0) { strcat_s(command, CMD_SIZE, "-MD "); strcat_s(command, CMD_SIZE, "-MD /USECL:MS_OPTERON "); } else { ...
このコードの文字列中の-MDと-MDdを全部-MTと-MTdに修正します。
1個だけ別にコンパイルされるファイルがあるみたいですね。これはそいつのコンパイルオプションのようです。
これで準備完了。
pythoncoreを右クリックしてビルドしてやれば、めでたくソリューションと同じディレクトリにDLLが生成されます。
(他の3個は依存関係が設定されているので、pythoncoreよりも前に自動的にビルドされます)
ところで、このとき生成されるDLLは、
となっています。
python**_d.dllはインストーラでは付属してこないので、自分のアプリケーションがDebugビルドでもPythonはReleaseビルドのものを使う、というよく分からないことをしていたのですが、おまけでDebugビルド用のDLLも手に入れることができました。
結局のところ、あんまりインストーラに頼るなってことですかね?
これで作業が進みそうです。
boost::spirit::leaf_node_dのバグ
boost 1.47.0のspirit(Qiじゃなくて古い方)で、ある言語のパーサを作ってたんですが、次のような構文規則
identifier = leaf_node_d[ lexeme_d[(alpha_p | '_') >> *(alnum_p | '_')] - keywords ];
を書いたところ、どうもうまく動作しない。
具体的にどういうことかというと、たとえば次のような入力があったとして、'abc'というパターンにマッチさせたいんですが、
... = abc; ... = /*comment*/abc; ... = abc;
次のようにマッチしてしまうんですね。
理想: 'abc' 'abc' 'abc' 現実: ' abc' ' /*comment*/abc' ' abc'
いろいろ試してみたんですが、leaf_node_dにセマンティックアクションを付けると期待通りの結果が返ってきたりで、明らかに挙動がおかしい。
で、まあ調べてみたんですが、やっぱりboost::spirit側のバグのようです。
上の記事だと足りないので補足すると、
leaf_node_dが直前のスキップパーサで弾かれた文字を含めちゃうということみたいです。
自分のコードではスキップパーサが次のようにしてあるんで、上記みたいな結果になったのだと思われます。
skip_p = +space_p | comment_p("/*", "*/");
しかし言及が2009年って、、、
修正が入ってないってことは本家に指摘されてないってこと?
とりあえず応急処置として、次のようにスキップを明示的に規則に入れることで対処できました。
identifier = no_node_d[*skip_p] >> leaf_node_d[ lexeme_d[(alpha_p | '_') >> *(alnum_p | '_')] - keywords ];
あと、文字列リテラルに一致させるつもりで書いた、次の規則もうまく動かない。
strl_p = leaf_node_d[ inner_node_d[ lexeme_d[confix_p('\"', *c_escape_ch_p, '\"')] ] ];
理想: "abcde" --> 'abcde' 現実: "abcde" --> '"abcde"'
やっぱりleaf_node_dが悪さをしているみたい。
ううむ。
アルファ値を考慮したレイヤー合成
最近新しいPhotoshopを手に入れて、少し浮かれているお出汁orいりもとです。
さて、Photoshopをはじめレイヤー機能を持つ画像処理ソフトには、ほぼ必ずブレンドモードの設定があります。
これは上下レイヤーの色情報を「合成」して別の色を作り出す処理で、Photoshopで言えば各レイヤーの「乗算」「スクリーン」といったものがこれに当たります。
以下は有名なLennaの画像を少し細工したものですが、上に乗っている緑色がブレンドモードごとにどう作用するかの簡単な例です。
Photoshopの各ブレンドモードについては、以下のサイトが詳しい。
ただ、実際のレイヤー合成の計算は、これらの計算式だけではないのです。
どういうことかと言うと、これらの式には不透明度(アルファ)が考慮されていないんですね。
つまり、完全な不透明(アルファ最大)の画像に関しては上記で解説されている通りでいいのですが、少しでも透明な画像を乗せた場合にはうまく計算できないわけです。
というわけで今回は、レイヤーのアルファまで考慮した合成について書きます。
なお、記事では背景を下のレイヤー、前景を上のレイヤーと定義して使うことにします。
また次の記号を使います。
- : 背景色
- : 前景色
- : 背景色と前景色をブレンドモードに従って合成した色(osakana.factoryさん参照)
- : 背景のアルファ
- : 前景のアルファ
それぞれ定義域は[0,1]に正規化しているものとするので、適宜読み替えてください。
前景のアルファを考慮する
まずは背景が完全に不透明()で、その上に適当なアルファを持った前景を重ねることを考えます。
これは非常に単純で、を使ってとの加重平均を取るだけで済みます。
つまり計算結果の色は、
となります。以下は乗算について、前景のアルファのみを変化させた場合を並べたものです。
この計算式はいくつかの解説書やサイトにも掲載されているので、すでに知っている方も多いと思います。実際、背景が常に不透明であることが分かっていれば、事実上この式で問題ありません。
ところがを最大値と見なしている以上、Photoshopのように背景が不透明とは限らない場合の合成には使うことができません。
前景、背景両方のアルファを考慮する
というわけで、前景に加えて背景のアルファも考慮に入れます。
しかし背景のアルファを考慮に入れるとしても、一体どういう式になるのかがよく分からない。
そこで、実際のPhotoshopの動作を見てみると、以下のようになっています。
つまり、背景のアルファが0に近づくにつれて、前景色そのものが直接現れてくるわけですね。
(※背景と一緒に薄くなったり、チェッカーボードに合成されたりはしません。ここを勘違いされている方が多いような気がしたので、特に強調しておきます)
これをもとに合成結果の特徴を列挙すると、
前景のアルファ | 背景のアルファ | 合成結果 |
100% | 100% | |
100% | 0% | |
0% | 100% | |
0% | 0% | 0(透明) |
となります。
これを満たすような式を2個のアルファから作ってやればいいわけです。
ここで少し話は変わりますが、透明なもの同士を合成する以上、結果もある程度透明であることは想像が付きます。
つまり、合成結果のアルファも前景と背景から計算してやる必要があるわけです。
とから合成結果のアルファを求めるには、以下の式を使います。
1からおよびを引いたものは、レイヤーの透明度と考えられます。式の意味的には、一度透明度に変換したものを乗算して、その結果を1から再び引いてアルファに戻していることになります。
具体的な数値だと、アルファ50%(透明度50%)のピクセル2個の合成はアルファ75%(透明度25%)になる、といった感じです。
(ちなみに、これはスクリーン合成と同じ式だったりします)
さて、この式を変形すると、以下のようになります。
(過程略)
このように無理やり3つの項に分けたのですが、このようにすると、それぞれの項がちょうど色の重みを表すようになります。つまり、
色 | 重み |
という対応関係が成り立つわけです。
式だけではよく分からないので、それぞれの項を、についてプロットしたものを見てみます。
これを見ると、さきほど列挙した合成結果の特徴(図の4隅に該当する)にぴったり一致していることが分かると思います。
またとすると、先ほど書いた加重平均そのものになります。
というわけで、最終的な色とアルファの式は、
として、以下のようになります。
ここで注意したいのは、単純に、、それぞれに重みを掛けて足しただけでは、正確な色にはならない点。、、の作用で色自体が「薄まって」いるので、で割って補正してやる必要があるのです。
またのときは除算ができないので、などと例外処理してやります。
かなり天下り的な説明になりましたが、以上がPhotoshopなどで用いられる合成の式となります。
このままでは計算回数が多いので、実際はもっと式全体を最適化したものが使われているのではないでしょうか。
Linuxでポート開放する
研究でサーバアプリを作成することになりそう。
とりあえず前借りたさくらのVPSで、Cからsocketを直接叩いて通信しようとしたものの、ポートが閉まってるのを失念してて少し悩んだ。
というわけでLinuxでポートを開放する方法を書きます。
最近のLinuxはiptablesというデーモンでポートの開け閉めを管理しているのですが、デフォルトでは全てのポートが閉まっていて全く通信できません。
そこで、iptablesの設定ファイル、/etc/sysconfig/iptablesを直接弄ってやります。
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -i lo -j ACCEPT -A INPUT -p icmp --icmp-type any -j ACCEPT -A INPUT -p tcp -m tcp --dport 20 -j ACCEPT -A INPUT -p tcp -m tcp --dport 21 -j ACCEPT -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited COMMIT
これは中身の一例。各命令が何をしているかは他のサイト様に回すとして、ここで重要なのは、
-A INPUT -p tcp -m tcp --dport **** -j ACCEPT
この行。
これは****番ポートをTCPで受け付け可能にする命令で、****を好きな数字にすれば望むポートを開くことができます。
たとえば12345番ポートを開くなら、80番を開いている命令の下くらいにこう書く。
-A INPUT -p tcp -m tcp --dport 12345 -j ACCEPT
(-Aは上から順に適用されるらしいので、同じ命令があったら近くに書いておくのがいいかもしれない)
開きたいポートの設定を追加したら、iptablesを再起動する。
$ /etc/init.d/iptables restart
これで外からTelnetでも何でもどんと来い!状態に。
ご利用は計画的に。
追記:
ちなみに20番、21番はFTP、22番はSSH、80番はHTTPポートです。
SSH接続で外部から操作している場合、22番の開放を忘れると、最悪一切の通信が不可能になってしまうので注意。
クライアント領域のサイズからウィンドウ全体のサイズを設定する
ちょっとTwitterで流れてたので書いておく。
Win32 APIでウィンドウのサイズを操作する場合、クライアント領域と非クライアント領域というものを常に意識しなければならない。
たとえば次のようなウィンドウがあったとする。
クライアント領域というのは我々がコントロールを配置したり図形描画したりするための領域で、下図の青く塗った部分がそうだ。
非クライアント領域というのはウィンドウの外枠部分で、我々が直接何か操作を行う部分というよりは、そのウィンドウの共通機能を与える部分だと言ってよい。下図の赤く塗った部分がそうだ。
(ただしこの説明には多少語弊があります。詳しくは他のサイト様で)
Win32 APIにはウィンドウのサイズを変更できる関数がいくつかある。
しかしこれらは大抵、非クライアント領域も含めたウィンドウ全体のサイズを調節するもので、「直接クライアント領域のサイズを指定してウィンドウをリサイズする」という操作はない。
クライアント領域のサイズが直接変更できないのは何か都合があるのだと思われるが、ゲームのウィンドウや、普通のアプリケーションでもトップレベルのウィンドウなどでは、非クライアント領域のサイズを無視できる方が大抵ありがたいことが多い。
このため、他のAPIを駆使して自分で実装してやる必要がある。
これにもいくつか方法があり、WebにはGetSystemMetricsでシステム設定を取得したり、AdjustWindowRectを使ったりする方法なども転がっている。
ここではウィンドウの属性に依存しにくく、ある程度汎用的と思われるGetWindowRect, GetClientRectを使う方法を紹介する。
まず必要なのは、
- 現在のウィンドウ全体のサイズ
- 現在のクライアント領域のサイズ
- 希望するクライアント領域のサイズ
である。
希望するサイズは自分で設定するものだが、現在のサイズはAPIによってウィンドウハンドルから取得する必要がある。
ウィンドウ全体のサイズはGetWindowRect、クライアント領域のサイズはGetClientRectによってそれぞれ取得できる。
RECT rw, rc; ::GetWindowRect(hWnd, &rw); // ウィンドウ全体のサイズ ::GetClientRect(hWnd, &rc); // クライアント領域のサイズ
結果はRECT構造体で返されるので、それぞれ.rightから.leftを引いたものが幅、.bottomから.topを引いたものが高さとなる。
(ただし、クライアント領域の.leftと.topは常に0なので無視してもよい)
ここで、ウィンドウ全体のサイズを、非クライアント領域のサイズを、現在のクライアント領域のサイズ、希望するクライアント領域のサイズをとすると、設定するべきウィンドウのサイズは、
である。
// 希望するクライアント領域のサイズを持つウィンドウサイズを計算 int new_width = (rw.right - rw.left) - (rc.right - rc.left) + width; int new_height = (rw.bottom - rw.top) - (rc.bottom - rc.top) + height;
あとはこれをSetWindowPosなどのウィンドウサイズを変更できるAPIに投げてやればよい。
SetWindowPosはウィンドウのサイズや位置、前後関係などをまとめて変更できる万能系のAPIだが、今回はサイズのみ変更したいので、オプションで他の引数を無視させている。
// サイズの設定 // ウィンドウの位置や前後関係は変更しない ::SetWindowPos(hWnd, NULL, 0, 0, new_width, new_height, SWP_NOMOVE | SWP_NOZORDER);
これでクライアント領域のサイズを指定してウィンドウ全体のサイズを調整することができる。
ついでに、上記をまとめて関数にでもしておくと便利かもしれない。
BOOL SetClientSize(HWND hWnd, int width, int height) { RECT rw, rc; ::GetWindowRect(hWnd, &rw); ::GetClientRect(hWnd, &rc); int new_width = (rw.right - rw.left) - (rc.right - rc.left) + width; int new_height = (rw.bottom - rw.top) - (rc.bottom - rc.top) + height; return ::SetWindowPos(hWnd, NULL, 0, 0, new_width, new_height, SWP_NOMOVE | SWP_NOZORDER); }
多項式の計算で乗算を増やさない方法
数値計算なんかをプログラムで書いてると、多項式の値を求めたいことがよくある。
たとえば次の多項式
これを計算するコードは、たとえば次のようになる。
// C/C++ f = x*x + 2*x + 3;
もう少し一般的に、次の多項式
について、と係数の配列からを計算する関数を考える。
double poly1(double x, double a[], int n) { double f = 0.0; for (int i = 0; i <= n; i++) { double g = a[i]; for (int j = 0; j < i; j++) g *= x; f += g; } return f; }
これでもいいのだが、次数の高い多項式を計算しようとすると、この関数の計算量は非常に大きくなる。
理由は簡単で、各を直接乗算で求めているからだ。
たとえばこの関数で100次の多項式を計算しようとすると、加算100回、乗算5050回ということになる。この関数の乗算の計算回数はである。
また、乗算を何度も繰り返すことになるので、浮動小数点数を使った計算では誤差が問題になる。
というわけで、多項式の計算から乗算の回数を減らしたい。
pow関数を使う
普通のプログラミング言語なら数学ライブラリが付属しているはずで、
その中にある(と思われる)を求めるpowを使えば、計算の回数は減らすことができる。
#include <math.h> double poly2(double x, double a[], int n) { double f = 0.0; for (int i = 0; i <= n; i++) f += a[i] * pow(x, (double)i); return f; }
ちなみにpowは、普通次のように実装されている。powが使えなければこの式を代用すればいい。
double pow(double a, double b) { return exp(b * log(a)); }
ただし、powで計算する方法は浮動小数点数が使えない環境や、誤差が出ては困るような状況(整数での計算など)では使うことができない。
高次から順に計算する(ホーナー法)
他に使われる方法としては、計算の順序を変える方法がある。
まず、最初の式を少し変形してみる。
を順番に括り出しただけで特別なことではないが、式から高次のが消えている。
一般の多項式に当てはめるなら、
となる。これを一番内側の括弧から順に計算すればいい。
double poly3(double x, double a[], int n) { double f = a[n]; for (int i = n-1; i >= 0; i--) f = f*x + a[i]; return f; }
このアルゴリズムはホーナー法(Horner法)と呼ばれているもので、高次のをうまくまとめることで乗算を減らす方法である。
関数をながめると、乗算が加算と同じ回数まで減少していることが分かる。たとえば100次の多項式なら5050回の乗算が100回になるわけで、速度の差は歴然である。
またpoly2と比べると、特殊な関数を使用していない分、こちらの方がいくらか高速に動くし、整数で計算した場合も誤差は出ない。
ただし、ホーナー法でも乗算の繰り返しを行っているので、浮動小数点数で計算する場合、最初のpoly1ほどではないにしろ誤差は大きくなる。
浮動小数点数で高速な数学関数が使える環境なら、powを使った実装を選ぶほうがいいかもしれない。
さくらVPSでRedmine構築中2(完了)
なにか根本的なところで間違ってたようで、一度サーバをセットアップし直して下のページそっくりそのままやったら動きました。
ちなみにRedmine1.2.1入れました。
Redmine 1.2をCentOS5.6にインストールする手順
ついでに、過去にリポジトリで管理してたソースを統合するためにsvnもインストール。
Subversionのインストールと設定(Apache編)
どうもDAVモジュールのmod_dav_svn.soはyumのsubversionではインストールされないらしく(そりゃそうか)、別にyumしてLoadModuleを追加することで解決。
cygwinでSubversion+WebDAV
Unknown DAV provider: svn
これでひとまず管理システムとして動きそうです。
やれやれ。