プログラム全般」カテゴリーアーカイブ

fastmake

以前は VisualStudio の nmake を使っていました。
最近は fastmake を使っています。

fastmake

●特徴

・高速動作させるための様々な記述が可能。
・豊富な組み込みコマンド。
・Windows native で exe ファイル一つのみ。
・基本的に gmake 互換。
・-j の並列化可能。ただし .PARALLEL 宣言したルールのみ
・コマンド行は TAB じゃなくても良い。

●高速な理由

一番効果的だと感じたのがサブシェル呼出を徹底的に廃したこと。
例えば普通の make だとコマンド実行でシェルを経由するので余計に時間がかかります。
echo や del などのよく使う命令もシェルコマンドなのでシェルが走っています。

# nmake
echo_abc:
    @echo A
    @echo B
    @echo C

この場合 shell が 3回起動するので非常に低速です。
(UNIX shell だと (~;~) 等で呼び出しをまとめたりできる。)

fastmake は echo, rm, mkdir, cd 等よく使う機能が組み込まれており、
サブシェル呼出を必要としません。
コンパイラなどのコマンド実行もシェルを経由せずに直接 exec 可能です。

# fastmake
echo_abc:
    $(echo A)
    $(echo B)
    $(echo C)

%.obj .NOSHELL .PARALLEL : %.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

$(echo~) は組み込み関数です。リダイレクトの代わりに $(echo ~,outpufile)
で直接出力ファイル名を指定することも可能です。
.NOSHELL 指定はシェルを経由せずに直接コマンドを exec しています。

Makefile 中さらに別の Makefile を呼び出すことがよくあります。
やはり通常は make の呼出のためにシェルを経由します。

# nmake
android:
    $(MAKE) -f Android.mak all

fastmake は自分自身を呼び出す組み込み関数があります。

# fastmake
android .PHONY :
    $(make -f Android.mak all)

この場合同一プロセス内で実行可能で、シェル呼び出しも外部コマンドの呼出も
スキップします。プロセス起動が無いため高速です。

フォルダまるごと削除も簡単に。

# nmake で rm -fr $(OBJDIR) 相当
OBJDIR = obj
clean:
    if exist $(OBJDIR) rmdir /Q /S $(OBJDIR)

# fastmake
OBJDIR = obj
clean:
    $(rm $(OBJDIR))

●便利な機能など

ドキュメントにも書いてありますが .COMBINE 指定があります。

%.obj: %.cpp
    cl $@ $<

等のパターンマッチは、生成するファイルごとに個別にコマンドを実行します。

cl a.cpp
cl b.cpp
cl c.cpp
~

.COMBINE 指定すると 1回のコマンド呼び出しにまとめて実行されます。

%.obj .COMBINE : %.cpp
    cl $@ $<

cl a.cpp b.cpp c.cpp ~

フォルダごとに make -f Win32.mak clean を実行する場合次のように書けます。

# fastmake
WIN32_DIRS = math file d3d11 gl3 gles2 network script
clean_win32 .PHONY :
    $(foreach dir,$(WIN32_DIRS),$(make -C $(dir) -f Win32.mak clean))

ドキュメントには下記の方法も紹介されていますが単純に foreach した方が速いようです。

# fastmake
WIN32_DIRS = math file d3d11 gl3 gles2 network script
clean_win32 .PHONY :  $(WIN32_DIRS:+.win32clean) ;
%.win32clean .PHONY :
    $(make -C $* -f Win32.mak clean)

内部コマンドは副作用として実行される関数扱いなので、マクロ置換できる場所なら
どこにでも書けます。下記の make もマクロ展開時に実行されます。

DIRS = a b c
A:=$(foreach dir,$(DIRS),$(make -f sub.mak))

結果が不要な内部コマンドは空文字になるようです。

$(echo $(echo B)A$(echo C))
 ↓
B
C
A

●気がついたことなど

環境変数はデフォルトでは取り込まれません。
個別に .IMPORT 指定が必要です。

.IMPORT .IGNORE : PATH DXSDK_DIR

変数 include への代入が出来ません。

include = $(DXSDK_DIR)\\include

コンパイラへ渡す環境変数として "include" が用いられることがありますが、
上のように書くと include 命令と勘違いしてしまうようです。
export 宣言することで回避できます。

export include=$(DXSDK_DIR)\\include

Makefile 中に C言語のブロックコメント /*~*/ が使えるように拡張されています。
最近これが問題になることが分かってきました。

COPYFILE_SRC = system/*.dds

こんな感じの記述があると、system"/*".dds の "/*" 以降がコメント扱いになり
無視されてしまいます。エラーが出ないので厄介です。

.FOREACH が意外に汎用性がありません。
パターン定義以外ではあまりうまく機能しないようです。

$(make ~) の make 呼び出しでは -j 指定を変更できません。
下の例の (1) では job 数が変わりません。
job 数を変更する場合は (2) のように自分自身を実行しなおす必要があります。

# (1)
android .PHONY :
    $(make -j 10 -f Android.mak all)

# (2)
FMAKE = fastmake.exe
android .PHONY .NOSHELL :
    $(FMAKE) -j 10 -f Android.mak all

nmake だとファイルの存在確認が簡単にできますが fastmake ではすっきり
書けません。要研究。

# nmake
MAYAPATH = C:\Program Files\Autodesk\Maya2011
!if EXIST( "$(MAYAPATH)" )
~
!endif
# fastmake
PROGFILES = C:\\Program Files
PROGFILES_AUTO = $(PROGFILES)\\Autodesk
.IF $(ls $(PROGFILES),d,Autodesk) != ""
  .IF $(ls $(PROGFILES_AUTO),d,Maya2011) != ""
~
  .END
.END

マルチタッチイベントの構造と違い

● Windows 7

           PRIMARY
WM_TOUCH:  id3 (x,y) DOWN
WM_TOUCH:  id3 (x,y) MOVE
WM_TOUCH:  id3 (x,y) MOVE   id4 (x,y) DOWN
WM_TOUCH:  id3 (x,y) MOVE   id4 (x,y) MOVE
WM_TOUCH:  id3 (x,y) MOVE   id4 (x,y) MOVE   id5 (x,y) DOWN
WM_TOUCH:  id3 (x,y) UP     id4 (x,y) MOVE   id5 (x,y) MOVE
WM_TOUCH:                   id4 (x,y) MOVE   id5 (x,y) MOVE
WM_TOUCH:                   id4 (x,y) MOVE   id5 (x,y) UP
WM_TOUCH:                   id4 (x,y) UP

●Android 2.x 以降

         pointerIndex/pointerId (x,y)
ACTION_DOWN:  0/id0 (x,y)
ACTION_MOVE:  0/id0 (x,y)
ACTION_MOVE:  0/id0 (x,y)
ACTION_DOWN:  0/id0 (x,y)   1/id1 (x,y)                 actionPointerId=1
ACTION_MOVE:  0/id0 (x,y)   1/id1 (x,y)
ACTION_DOWN:  0/id0 (x,y)   1/id1 (x,y)   2/id2 (x,y)   actionPointerId=2
ACTION_MOVE:  0/id0 (x,y)   1/id1 (x,y)   2/id2 (x,y)
ACTION_UP:    0/id0 (x,y)   1/id1 (x,y)   2/id2 (x,y)   actionPointerId=0
ACTION_MOVE:                0/id1 (x,y)   1/id2 (x,y)
ACTION_MOVE:                0/id1 (x,y)   1/id2 (x,y)
ACTION_UP:                  0/id1 (x,y)   1/id2 (x,y)   actionPointerId=1
ACTION_UP:                  0/id1 (x,y)                 actionPointerId=0

●iOS (iPhoneOS)

touchesBegan:  (x,y)
touchesMoved:  (x,y)
touchesMoved:  (x,y)
touchesBegan:               (x,y)
touchesMoved:  (x,y)
touchesMoved:  (x,y)        (x,y)
touchesBegan:                             (x,y)
touchesMoved:  (x,y)        (x,y)         (x,y)
touchesMoved:  (x,y)        (x,y)         (x,y)
touchesEnded:  (x,y)
touchesMoved:               (x,y)         (x,y)
touchesMoved:                             (x,y)
touchesEnded:               (x,y)         (x,y)

たまに更新しています。
Multitouch 関連情報

vim のコマンド起動ツール

Windows 版 vim を使っています。
といってもちょっと便利な vi 程度にしか使っておらず、カスタマイズは最小限で
済ませています。コマンドシェルから gvim を起動してファイル毎に別のエディタを
開くスタイルです。
shell + gvim の組み合わせは X68000 の Ko-Window 上で vi (stevie) を使う
場合とよく似ています。shell と別ウィンドウでいくつでも開けるからです。
ほとんど同じ感覚で使用しています。

vim は vi 系のテキストエディタです。
コマンドシェルからヒストリを使ってエディタを呼び出していると、すでに開いている
ファイルなのに、つい多重で開いてしまうことがあります。
同じファイルを複数のエディタで同時に開くのはまずいです。
最後に保存した方の更新しか残らないからです。
stevie の時はよくミスしがちでした。

幸い vim の場合はあまり困ったことにはなりません。
ファイルを開くと .swp ファイルが作られるので、そのファイルがすでに編集中なのか
わかります。すでに .swp ファイルがあるなら、このまま開くか ReadOnly
で続けるか、起動を中止するか確認してくれます。

便利な反面、衝突するたびに動作を選択して中止し、起動中のウィンドウから
目的のものを探し出すのはだんだん面倒になってきます。
そこで、下記のようなプログラムを作って使っていました。

・指定したファイルがすでに gvim で開いているかどうか調べる
  ・もしすでに存在していたら、そのウィンドウをアクティブにする
  ・無ければ新たに gvim でそのファイルを開く

もしかしたら vim 自体に何らかの設定があったり、最初から似たような機能が付いて
いたり、または同等のものが存在しているのかもしれません。
必要あるかどうかわかりませんが置いておきます。

runvim100.zip

使い方

runvim [option] [<ファイル名>]

 -e<起動するgvimのパス>

たとえば alias や shell の関数定義で下記のように定義しておきます。
(gvim のパスは任意)

alias vi='runvim.exe -eC:/app/Vim71/gvim.exe'

上記定義した後「vi ファイル名」で起動すると、すでに開いていれば対応する
gvim を最前面に持ってきて入力フォーカスします。
無ければ gvim を通常起動します。

以下 vim (7.1) の個人的な設定です。
Vim 自体は本家 vim.org からダウンロードしたパッケージをそのままインストール
しています。

" $HOME/_vimrc
se ai columns=80 lines=46 hls nobackup ruler
se guifont=MS_ゴシック:h8:cSHIFTJIS
se linespace=0
se guioptions=grLt
se mouse=a
se iminsert=0 imsearch=0
se enc=cp932
se guioptions-=a
se statusline=%<%f\ %m%r%h%w%{'['.(&fenc!=''?&fenc:&enc).']['.&ff.']'}%=%b\ %B\ %l,%c%V%8P
lan en
lan cty en
lan mes en
lan tim ja
syntax enable
hi Constant	guifg=#a03800
hi PreProc	guifg=#008010
hi Folded	guifg=#808000 guibg=#fffffe
hi FoldColumn	guifg=#808000 guibg=#fffffe
so $HOME/format.vim
let format_join_spaces=2
se foldmethod=syntax

実はフォントは難ありで、この設定だと小文字のエル=l と大文字のアイ=I の区別が
付かないけど慣れました。このままプログラム書いてます。

format.vim は日本語で join したときに空白が入らないようにするため使用しています。

doxygen syntax を追加しています。
Vim を install したフォルダ内の filetype.vim を書き換えます。
"setf cpp" や "setf c" と定義されているところを全部 "setf cpp.doxygen"
のように変更します。".doxygen" を追加するわけです。
これで syntax として cpp が選択されると同時に doxygen のキーワードハイライト
が有効になります。

折りたたみ機能も利用しています。
そのまま folding を有効にすると C ソースコードのブロックも折りたたまれて
しまうので、その設定は削除。その代わり doxygen のコメントを部を折りたためる
ようにしています。

(1) syntax/c.vim の中の syntax ~ fold ~ と書かれた行の "fold" を全部削除
  たとえば下記のように (syn ~ で始まる場合もあり)

syntax region	cBlock		start="{" end="}" transparent fold
 ↓
syntax region	cBlock		start="{" end="}" transparent

(2) syntax/doxygen.vim に下記の 2行を追加

syn region myDoxFold start="/\*!" end="\*/" transparent fold keepend
syn sync fromstart

折りたたみ操作は vim 上で zr または zm です。

.hlsl や .fx 等、自分が使うファイルの syntax も追加しています。
専用の syntax ファイルが無くても、C言語系なら c や cpp を割り当てるだけで
見やすくなります。
たとえば filetype.vim を書き換えてこんな風に。(fsc は自前の C言語スクリプト)

" filetype.vim
au BufNewFile,BufRead *.fsc			setf cpp.doxygen

Vim の syntax や script ファイルは下記ページで検索できます。

vim.org Scripts Browse all

関連エントリ
vi の話

VisualStudio 2010 beta lambda 式

VisualC++ 2010 beta ではラムダ式が使えます。
無名の関数を inline で宣言することが可能で、関数オブジェクトのように
取り扱いできます。

Download Visual Studio 2010 Beta
Examples of Lambda Expressions

変数で受け取る場合は auto や template を使った汎用の入れ物が必要です。

auto f0= []( int x, int y ){ return x * y; };
std::tr1::function f1= f0;

lambda 式は無名の型宣言に相当するので、同等の型を明示的に宣言することが
できません。
名前をつけるだけなら下記の定義は出来ますが、これも f0 が有効な範囲のみです。

typedef decltype(f0) lambda_type2;
lambda_type2 f2= f0;

全く中身が同じ lambda 式を定義しても別の型と見なされるようです。

auto	f2= []( int a, int b ){ return	a * b; };
auto	f3= []( int a, int b ){ return	a * b; };
decltype(f2)*	fp2= &f3;  // error

上記の例はエラーです。
そのため定義内容を知らない外部から直接参照する手段がありません。

実際に使ってみると単純な関数ポインタでは無いことがわかります。

・lambda 式に応じて無名のクロージャー用クラスが定義される
・メンバとしてキャプチャした変数のコピーまたは参照が格納される
・クロージャークラスのメンバ関数として呼び出される。

確認してみます。

int v= 0;
auto  f4= []( int a, int b ){ return  a * b; };
auto  f5= [&]( int a, int b ){ return  a * b + v; };
auto  f6= [=]( int a, int b ){ return  a * b + v; };

sizeof(f4) == 1
sizeof(f5) == 8 // x64
sizeof(f6) == 4

sizeof(f4) が 1 なのは実質的にサイズが無いことを意味しています。
sizeof(f5) と sizeof(f6) はどちらも v を参照していますが、たまたま x64 で
コンパイルしていたので異なるサイズになりました。
ポインタ(参照)である証拠で x86 だと 4 になります。

auto	f4= []( int a, int b ){ return a * b; };
f4.operator()( 2, 10 );

operator() の呼び出しが出来ます。

int	v;
auto	f5= [&]( int a, int b ){ return a * b + v; };
int	r0= f5.v; // cannot access private member
int	r1= f5.w; // is not a member

直接アクセスはエラー。変数名はそのままメンバになってます。

lambda 式の定義が返す値はその場でキャプチャされたクロージャーそのものであるということ。
クロージャー未使用でも空の構造体が定義されること。
実行する関数はクロージャー型と静的に関連づけられており、そのために型は常にユニークとなること。
lambda 式の呼び出しは静的なものであってポインタでは無いということ
等がわかります。

実際に出力コードも追いかけて確認。

きちんとインライン展開されているし、これで定義を知らない外部からは直接アクセス
できない理由もわかりました。
外部関数呼び出しのようにインライン出来ない場合は std::tr1::function のような
関数オブジェクトが必要になるわけです。

struct func_base_2 {
    virtual int  operator()( int a, int b )= 0;
};

template
struct func_impl_2 : public func_base_2 {
    T  exf;
    func_impl_2( T lambda_f ) : exf( lambda_f ) {}
    int	operator()( int a, int b )
    {
        return  exf( a, b );
    }
};

struct func_2 {
    func_base_2*   ptr;
    template
    func_2( const T&& lambda_f )
    {
        ptr= new func_impl_2( lambda_f );
    }
    template
    func_2( T& lambda_f )
    {
        ptr= new func_impl_2( lambda_f );
    }
    int	operator()( int a, int b )
    {
        return	(*ptr)( a, b );
    }
    ~
};


    func_2    f7( []( int x, int y ){ return x*x + y*y; } );
    int   mm= f7( 3, 9 );

この場合の f7 は callback として持ち出せます。
使えるのはクロージャーが作られた関数スコープの中のみです。

関連エントリ
VisualStudio 2010 beta1

VisualStudio と UTF-8

そろそろソースコードも UTF-8 で統一したいと常々思っていました。
VisualC++ の場合 BOM (ef bb bf) があれば UTF-8 とみなしてくれます。

コンパイラおよびリンカでの Unicode のサポート

バイナリには記述したコードのまま格納されるわけではなく、
必要に応じて ShiftJIS や UTF-16 に変換されます。

関連エントリ
_T() と TEXT() の違いやソースの文字コード

逆に UTF-8 のままデータを記述することが難しいのと、
BOM が付くと他のコンパイラでエラーになることがあるようです。

"表示" を utf-8 でコンパイルしてみる

  SJIS = 95 5c 8e a6
  UTF8 = e8 a1 a8 e7 a4 ba
  UTF16= 8868 793a

VC2008: utf8+bom  = 95 5c 8e a6  (SJISに変換されている)
VC2008: utf8      = e8 a1 a8 e7 a4 ba
CW    : utf8+bom  = error
CW    : utf8      = e8 a1 a8 e7 a4 ba
gcc4.1: utf8+bom  = error
gcc4.1: utf8      = e8 a1 a8 e7 a4 ba

VC bom 無しはたまたまうまくいってますが、Shift JIS 判定ありなので
問題が出ます。(warning C4819 になる事も)

例えば “引\n” を UTF-8 記述すると、バイト列では

 引       \  n
 e5 bc 95 5c 6e

ShiftJIS でも意味のあるコードなので、’\’ ‘n’ が変換されず
文字として残ります。

常に日本語 (cp932) とみなすこの動作は、Windows のシステムロケールを
見ているようです。VisualStudio は英語版を使っているし、option の
“International Settings” にある Language を
“Same as Microsoft Windows” でなく English にしても変化無し。

Vista で コントロールパネル→地域と言語のオプション→管理
「Unicode対応ではないプログラムの言語」を「英語」に切り替えると
コードが素通りし、”引\n” の \n が改行に変換されるようになりました。

でも他のアプリに影響が出るので、このまま使うのは問題ありです。

gcc では「 -finput-charset=cset 」「 -fexec-charset=cset 」の
オプションがあって、それぞれ個別に指定できるようです。
例えば utf8 と sjis にすると VC とほぼ同じ挙動となりました。
でも BOM はだめ。