2011年9月15日木曜日

64bit環境で動かない.NETアプリ

一部の特殊なアプリを除けば、64bit環境でも従来のアプリはWow64上で普通に動作するものが多いが、たまに.NETアプリは動かないものがある。フックやシェル拡張など他のプロセスに寄生するアプリやドライバならともかく.NETでそんなの書かないのに何故? と不思議に思うだろう。

どういう訳かというと.NETアプリの実行ファイルはコンパイルした段階では純粋なマシン語ではない中間言語で、実行時にCPUのビット数に合ったマシン語に変換される仕組みになっており、同じ実行ファイルでも64bit OS上では64bit, 32bit OS上では32bitで動作する。

ここに一つの罠があって、.NETアプリにはたまにネイティブのDLLを利用しているものがあり、このDLLは始めからマシン語でありコンパイル時に指定したビット数のCPUでしか動作しない。そのため、64bit環境で動作させると.NETアプリのプロセスは64bitで起動して、32bitのDLLを読み込もうとしてエラーとなってしまう。

32bitのネイティブアプリのように始めからWow64上で32bitアプリとして起動させることができれば32bitのDLLでも読み込める。実は.NETアプリのコンパイル時にTarget CPUにx86を選んでいれば、64bit環境でも32bit CPUのマシン語に変換されるため、Wow64が発動してちゃんと動いてくれる。上に書いた罠が発動するのはコンパイル時にTarget CPUにAny CPUを選んでいる場合なのだ。

最近はこのノウハウも広まってきたので、アプリ作者の人もちゃんとコンパイルオプションを選んでくれるし、今動かなくても将来のバージョンアップで動くようになるだろうけど、もう作者の人が更新を止めちゃっている場合はどうしたら良いのか。

実はコンパイルオプションを変えても実行ファイルの大部分である中間言語の部分は全く同じで、ヘッダにあるフラグがちょっと違うだけなので、バイナリエディタで書き換えてやれば良い、というのは大変なのでちゃんとツールがある。

CorFlags.exe "対象の.exe" /32BIT+

問題はこのコマンドは単体では配っていなくて、.NET FrameworkのSDKを入手してインストールしないと使えるようにならないこと。自分はVisual Studio C#のExpress Editionを使っているので始めから入っていたけど、そうじゃない人はちょっと面倒かもしれない。

2011年9月10日土曜日

64bitプロセスから32bitプロセスにDLL Injection (C言語)

ググったら32bit→64bitはNGだけど、64bit→32bitは行けるという情報があったので試したら行けた。DLL Injectionって何?という人は「別のプロセスにコードを割り込ませる3つの方法」にとても詳しく書かれているのでそちらを参考に。(こういうマニアックな記事は好きだ)

面倒なので自作アプリのソースの一部をそのまま貼る。本筋じゃない処理も混ざってるけど自力で読み飛ばし推奨。GOTO_Eは最後のラベルにgotoするマクロ。
bool pAttachRemoteThread(DWORD pid, const char* dllName, bool autoFree, bool waitRemoteSemaphoreRelease){

bool result = false;
HANDLE hThread = NULL;
PWSTR vDllPath = NULL;
HANDLE hProcess = NULL;
HANDLE hSemaphore = NULL;
HANDLE hMutex = NULL;

hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION |
PROCESS_VM_WRITE,
FALSE, pid);

if(hProcess == NULL) GOTO_E;

BOOL isWow64;
IsWow64Process(hProcess, &isWow64);

// DLLのフルパスの編集
char dllPath[MAX_PATH+1];

GetModuleFileName(g_hInst, dllPath, sizeof(dllPath));
PathRemoveFileSpec(dllPath);
PathAppend(dllPath, dllName); //このDLLと同じディレクトリ

if(_stricmp(PathFindExtension(dllPath), ".dll") == 0){ // 末尾の.dllは除去(大小文字無視)
PathRemoveExtension(dllPath);
}

#ifdef _WIN64
if(!isWow64){ // 64bitのDLL名には64を追加
strcat(dllPath, "64");
}
#else
// 32bit版ビルドで64bitのプロセスにアタッチしようとしたらエラー
BOOL iamWow64;
IsWow64Process(GetCurrentProcess(), &iamWow64);
if(iamWow64 && !isWow64) GOTO_E;
#endif

strcat(dllPath, ".dll");

// ロードするDLLのパスをリモートプロセスのメモリに書きこむ。
size_t dllPathLen = strlen(dllPath) + 1;
vDllPath = (PWSTR)VirtualAllocEx(hProcess, NULL, dllPathLen, MEM_COMMIT, PAGE_READWRITE);
if(vDllPath == NULL) GOTO_E;
if(WriteProcessMemory(hProcess, vDllPath, (PVOID)dllPath, dllPathLen, NULL) == 0) GOTO_E;

// Loadlibraryのリモートプロセスでのアドレスを取得
PTHREAD_START_ROUTINE addr = pGetProcAddress(isWow64, "LoadLibraryA");
if (addr == NULL) GOTO_E;

// DLL名 + pidが同じ処理は同時実行を抑止。
char mutexName[FILENAME_MAX + 12 + 1];
sprintf(mutexName, ":%s%d", dllName, pid);
hMutex = CreateMutex(NULL, FALSE, mutexName);
if(WaitForSingleObject(hMutex, TIMEOUT) == WAIT_TIMEOUT) GOTO_E;

// リモートの処理終了を検知するセマフォ(名前:DLL名 + pid)を用意
if(waitRemoteSemaphoreRelease){
char* semaphoreName = pGetSemaphoreName(pid, dllName);
hSemaphore = CreateSemaphore(NULL, 0, 1, semaphoreName);
free(semaphoreName);
if(hSemaphore == NULL) GOTO_E;
}

// LoadLibraryを実行して終了コード判定
hThread= CreateRemoteThread(hProcess, NULL, 0, addr, vDllPath, 0, NULL);
if (hThread == NULL) GOTO_E;

// スレッドの終了待ち
if(WaitForSingleObject(hThread, TIMEOUT) == WAIT_TIMEOUT) GOTO_E;
DWORD exitCode;
GetExitCodeThread(hThread, &exitCode);
if(exitCode == NULL) GOTO_E;

// インジェクションしたDLLがセマフォをリリースするのを待つ。
if(waitRemoteSemaphoreRelease)
if(WaitForSingleObject(hSemaphore, TIMEOUT) == WAIT_TIMEOUT) GOTO_E;

// FreeLibraryを実行
if(autoFree){
addr = pGetProcAddress(isWow64, "FreeLibrary");
if (addr == NULL) GOTO_E;

CloseHandle(hThread);
hThread= CreateRemoteThread(hProcess, NULL, 0, addr, NULL, 0, NULL);
if (hThread == NULL) GOTO_E;

if(WaitForSingleObject(hThread, TIMEOUT) == WAIT_TIMEOUT) GOTO_E;
GetExitCodeThread(hThread, &exitCode);
if(exitCode == NULL) GOTO_E;
}

result = true;

ERROR_HANDLER:
if(vDllPath != NULL) VirtualFreeEx(hProcess, vDllPath, sizeof(dllPath), MEM_RELEASE);
if(hProcess != NULL) CloseHandle(hProcess);
if(hSemaphore != NULL) {
ReleaseSemaphore(hSemaphore, 1, NULL);
CloseHandle(hSemaphore);
}
if(hMutex != NULL) {
ReleaseMutex(hMutex);
CloseHandle(hMutex);
}
if(hThread != NULL) CloseHandle(hThread);

return result;
}

ここまでは最初に紹介したリンクに書いてあることそのままなので、これで終わるならあちらを読んだ方が面白い。

ここからが問題の64bitプロセスから32bitプロセスのLoadLibraryのアドレスをどうやって知るかなのだが…
はじめはCreateToolhelp32Snapshotでkernel32のベースアドレス取ってきて、PEファイルの構造を読んで関数のRVAを足せば…と試みたのだけれど、ベースアドレスの取得が上手く行かなかった。64bitプロセスから見るとkernel32.dllはロードしておらず、WOW64関連のDLLだけをロードしているように見える。

結局、32bitプロセスを起動して取得するというしょぼい力技で対応することに。もっとスマートな方法あったら教えて欲しい。
PTHREAD_START_ROUTINE pGetProcAddress(BOOL isWow64, const char* funcName, const char* libName = "kernel32"){

if(_stricmp(libName, "user32") != 0 &&
_stricmp(libName, "kernel32") != 0 ) return NULL;

// kernel32, user32.dllの関数はどの全プロセスで同じアドレスにロードされるので
// 自プロセスで取得したアドレスを別プロセスでもそのまま使用できる。
// ただし、プロセスのbit数が異なる場合は別になるため、
// 64bitから32bitのプロセスにアタッチする場合は別の方法でアドレスを取得する。
if(!isWow64)
return (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(libName), funcName);

// 二回目以降の呼び出しはキャッシュを返す。
static map<string, PTHREAD_START_ROUTINE> cache;
string key = string(libName);
key.append(funcName);
if(cache.count(key) ==1) return cache[key];

// 戻り値に32bitでの関数アドレスを返すコマンドを呼び出す。
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb=sizeof(si);

// コマンドプロンプトは表示させない。
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;

// このDLLと同じディレクトリ\GetProcAddress32.exe ライブラリ名 関数名
char cmdLine[MAX_PATH + 64 +1];
GetModuleFileName(g_hInst, cmdLine, sizeof(cmdLine));
PathRemoveFileSpec(cmdLine);
PathAppend(cmdLine, "GetProcAddress32.exe");
strcat(cmdLine, " ");
strcat(cmdLine, libName);
strcat(cmdLine, " ");
strcat(cmdLine, funcName);

if(CreateProcess(NULL, cmdLine, NULL,NULL, FALSE,NORMAL_PRIORITY_CLASS, NULL,NULL,&si,&pi) == NULL){
error("CreateProcess()", cmdLine);
return NULL;
}

CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess,INFINITE);
DWORD exitCode;
GetExitCodeProcess(pi.hProcess, &exitCode);
CloseHandle(pi.hProcess);

// 実行結果をキャッシュ
PTHREAD_START_ROUTINE addr = (PTHREAD_START_ROUTINE) exitCode;
cache[key] = addr;
return addr;
}

上で呼び出しているコマンド。
#include <windows.h>

int main(int argc, char** argv)
{
if(argc != 3)
return NULL;
else
return (int) GetProcAddress(GetModuleHandle(argv[1]), argv[2]);
}

デバッグ用マクロ (C言語)

デバッグ時のみ有効なエラー出力用マクロ。何となくWindows以外でも動きそうな感じに書いてみたけど、実際に使っているのはWindowsだけ。他のOSは試してないので動かないかもれない。(→追記:一応FreeBSD gcc4.2でそれっぽく動いたのでとりあえず良しとする。)

使い方は共通のヘッダファイルに下を書いておいて、それをincludeして
debug("ProcessId=%d",GetCurrentProcessId());
とか書けば
[D] test.cpp[55] WinMain[0]: ProcessId=6508
みたいにメッセージと一緒にソースコードのファイル名や行数、関数名、エラーコードをコンソールに出力してくれる。
GOTO_Eと書くと、メッセージを吐いてからERROR_HANDLERという名前のラベルにgotoする。
#ifndef _LOG_H
#define _LOG_H

#include <stdio.h>

#ifdef _WIN32
#include <windows.h>
#define ERRCD (int)GetLastError()
#define PATH_DELIM '\\'
#else
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#define ERRCD errno
#define PATH_DELIM '/'
#endif

#ifdef _WIN32
// Ctrl-Cとかでコンソールウインドウを閉じなくする。
inline BOOL WINAPI HandlerRoutine(DWORD type)
{
switch(type)
{
case CTRL_C_EVENT: //Ctrl+C
case CTRL_BREAK_EVENT: //Ctrl+Break
case CTRL_CLOSE_EVENT: //CLOSE
return TRUE;
case CTRL_LOGOFF_EVENT: //LOGOFF
case CTRL_SHUTDOWN_EVENT: //SHUTDOWN
default:
return FALSE;
}
}
#endif

inline void debuglog(const int level, const char *file, const char* func, const int line, const int errcd, const char *fmt, ...)
{
#ifdef _WIN32
static bool hasConsole = false;

if(!hasConsole) {
HWND hWnd = GetConsoleWindow();
if(hWnd == NULL) {
FILE *fp;
AllocConsole();
SetConsoleTitle((LPCSTR)"Debug");
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
freopen_s(&fp, "CONOUT$", "w", stderr);
hasConsole = true;

// 「閉じる」をボタン、右クリックメニューから削除。
// コンソールウインドウを閉じられるとプロセスが終了されるのを防げないため。
hWnd = GetConsoleWindow();
HMENU hMenu = GetSystemMenu (hWnd, FALSE);
HINSTANCE hinstance = (HINSTANCE) GetWindowLongPtr (hWnd, GWLP_HINSTANCE);
DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
DrawMenuBar(hWnd);
}
}
#endif
static char* table[] = {"[D]","[I]","[W]","[E]","[A]"};
#pragma warning(push)
#pragma warning(disable:4996)
static int limitlevel = getenv("loglevel")==NULL?0:atoi(getenv("loglevel"));
#pragma warning(pop)

if(level < limitlevel) return;

// フルパスだったらファイル名に
const char* fname = strrchr(file, PATH_DELIM);
if(fname == NULL)
fname = file;
else
fname++;

va_list argp;
fprintf(stderr, "%s %s[%d] %s[%d]: ", table[level], fname, line, func, errcd);
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
fprintf(stderr, "\n");
}

#ifdef _DEBUG
#define debug(...) debuglog(0, __FILE__, __FUNCTION__, __LINE__, ERRCD, __VA_ARGS__);
#define info(...) debuglog(1, __FILE__, __FUNCTION__, __LINE__, ERRCD, __VA_ARGS__);
#define warn(...) debuglog(2, __FILE__, __FUNCTION__, __LINE__, ERRCD, __VA_ARGS__);
#define error(...) debuglog(3, __FILE__, __FUNCTION__, __LINE__, ERRCD, __VA_ARGS__);
#define critical(...) debuglog(4, __FILE__, __FUNCTION__, __LINE__, ERRCD, __VA_ARGS__);

#define GOTO_E {error("[ERROR] goto FUNC_END"); goto FUNC_END;}
#else

#define debug(...)
#define info(...)
#define warn(...)
#define error(...)
#define critical(...)
#define GOTO_E goto FUNC_END
#endif
#endif

2011年9月9日金曜日

lockステートメントは同じスレッドをロックしない (C#)

lockステートメントで少しハマったのでメモ。ハマったコードを単純化したサンプルが以下。こんなプログラム作らねーよというツッコミはなしで。

やってることは単純で起動時にファイルを作って、ウインドウがクリックされたらリネームして元に戻す。リネームして戻すところをlockステートメントで排他をかけている。もし処理が同時に走るとリネーム済みなのに再リネームを試みてFileNotFoundになる。そしてリネームした状態で処理を止められるようポップアップを表示している。

この状態でウインドウを連続クリックすると何が起こるだろうか? 私のようなヘボプログラマは排他待ちになりエラーなしで処理されると直感的に思ってしまった。 でも実はエラーになる。
using System;
using System.IO;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
[DllImport("user32.dll", EntryPoint = "MessageBox")]
extern static Int32 MessageBox(Int32 hWnd, string text, string caption, UInt32 type);

public Form1()
{
InitializeComponent();
if(!File.Exists(fromPath) File.Create(fromPath);
if(File.Exists(toPath) File.Delete(toPath);
}

    string fromPath = "aaa.txt";
    string toPath = "bbb.txt";

    void CriticalSection(string title){
lock (this)
{
File.Move(fromPath, toPath);
MessageBox(0, "処理中", title, 0);
File.Move(toPath, fromPath);
}
}

private void Form1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
CriticalSection("aaaa");
else
CriticalSection("bbbb");
}
}
}

何故か? ヒントはこちら。

lock によって、あるスレッドがクリティカル セクションになっているときは、別のスレッドはコードのクリティカル セクションにはなりません。ロックされたコードを別のスレッドが使おうとすると、オブジェクトが解放されるまで待機 (ブロック) します。

MSDN lock ステートメント (C# リファレンス)


「別のスレッドは」という表現がこの現象を引き起こした正体。別に同じスレッドが同時には動くことはないんだから問題ないと思うのでは? 上のコードを以下のようにちょっと変えて試してみると良い。
              File.Move(fromPath, toPath);
MessageBox(0, ""+Thread.CurrentThread.ManagedThreadId, title, 0);
File.Move(toPath, fromPath);

同じ数字が表示されるでしょ? 要はクリティカルセクション中でスレッドを切り替えるなということ。スレッドなんて切り替えていないって? 何故、メッセージボックスで処理を止めてるのにメインウインドウの描画が更新されているのか考えてみよう。

アンマネージドコードのデバッグで手を抜いて一時的にメッセージボックスを使ったばかりに変なことにはまって無駄に時間をかけてしまった。

2011年9月6日火曜日

Bloggerの投稿画面は狭すぎる

HDのディスプレイで使っていると狭くてやっていられない。そんな訳で広くする方法を探したわけですが。
見つかったのが以下のユーザスクリプト。


でも、これ古い投稿エディタじゃないと効かない。新しい投稿エディタのメリットより、編集画面が広い方が嬉しいのでしょうがなく設定から古い投稿エディタを有効にして使ってる。画像をアップロードする時に特大に設定ができないのと、プレビューが不完全なのは困るけれど… 他力本願だけど誰か何とかしてくれないだろうか。



設定の保存にxmlファイルを使う(C#)

C#で設定ファイルを簡単に実装しようと思ったらXmlSerializerを利用して設定用クラスの情報をxmlファイルにダンプ、ロードする方法が楽だ。
// 設定用クラス
public Config{
// publicにする必要あり
public string config1 = "初期値";
}

// 起動後
// 設定ファイル読み込み
// Configの部分は設定クラス名で変わる
XmlSerializer serializer = new XmlSerializer(typeof(Config));
string confFile = Path.Combine(Application.StartupPath, Path.GetFileNameWithoutExtension(Application.ExecutablePath) + ".xml");
if (File.Exists(confFile))
{
using (FileStream fs = new FileStream(confFile, FileMode.Open))
{
Config = (Config)serializer.Deserialize(fs);
}
}
// ファイルがない場合は初期値をロード
else
{
Config = new Config();
}

// いろいろな処理をやる。

// 終了前
// 設定ファイルの保存
using (FileStream fs = new FileStream(confFile, FileMode.Create))
{
serializer.Serialize(fs, Config);
}
これだけで動作はするのだが、環境によっては処理がハングすることがあるらしい。標準のXmlSerializerでは実行時に動的コンパイルでシリアライズ処理用のバイナリを作るが、この処理がまずいみたいなので、事前にコンパイル済みのバイナリを用意してそれを利用するようにすれば良いらしい。手順をは以下の通り。
  1. 設定用クラスを含めてコンパイルしたアセンブリからシリアライズ用のDLLを作成する。具体的にはビルドイベントでビルド後のイベントに「"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\sgen.exe" /f "$(TargetPath)" /o:"$(ProjectDir)"」を追加する。
  2. 作成されたDLL(アセンブリ名.XmlSerializers.dll)を参照設定に追加する。
  3. DLLを利用してシリアライズするようにプログラムを修正する。
using Microsoft.Xml.Serialization.GeneratedAssembly;  //先頭に追加

// ConfigSerializerの部分は、設定用のクラス名によって変わる。
// XmlSerializer serializer = new XmlSerializer(typeof(Config));
ConfigSerializer serializer = new ConfigSerializer();
1つだけ自分がハマったことがあったので注意として付記しておく。DLLを参照に追加するとデバック時にDLLが見つからずにエラーにならないよう、ビルドのたびにアセンブリの出力ディレクトリに参照DLLをコピーしてくれるローカルコピーという機能がある。

この際、親切なことに「参照しているDLL名.xml」(&.pdb)という名前のファイルがあればそれもコピーしてくれる。それだけなら歓迎なのだが、何故か「アセンブリ名.xml」のファイルがあれば、一緒にコピーしてしまうという謎の仕様になっているらしい。

何がまずいかというと、このサンプルのように設定ファイル名を「アセンブリ名.xml」にしていて、sgenに/oオプションを付けずアセンブリと同じディレクトリにDLLを出力、それをそのまま参照に追加するという条件が重なると困った現象が起きる。

例えばReleaseディレクトリに出力したDLLを参照に追加したとする。Releaseビルドで一回でもデバッグを実行して設定ファイルを出力した状態で、Debugビルドに切り替えてデバッグを行うと、設定ファイルがReleaseディレクトリからDebugディレクトリにコピーされてしまい、Debugビルドでアプリが書き換えた設定が再ビルドのたびにリセットされてしまうという… これで何時間無駄にしたことか…

対策は以下のどれか
  • DubugビルドとReleseビルドで参照設定の指定をちゃんと切り替える。GUIからは無理なのでプロジェクトファイルをテキストエディタで直接編集する必要あり。
  • ローカルコピーを無効にする。参照設定のプロパティから切り替えられる。
  • 「アセンブリ名.xml」ファイルが出力されない場所にDLLを出力して、参照設定に追加する。
今回の例では3番目を採用した。

コマンドプロンプトで利用可能なフォント

使えるフォントに制限があるみたいなので、使えたやつをメモとして残しておく。(随時更新予定)

◎ 日本語フォント
◎ 英字フォント
  • Lucida Console
  • Courier New
  • Consolas (Vista以降)
→ と思ったけど抜粋して書いておく。

「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont」に 932. (フォントを2つ以上追加する場合はドットの数を増やしていく)という名前の文字列型のキーを作ってフォント名を設定するとコマンドプロンプトのフォントの設定画面から選べるようになる。

コマンドプロンプトとフォントの怪

コマンドプロンプトの文字化けにしばらく悩まされたのでその奮戦記。

◎ 起こったこと
  • ある日、Visual Studioをインストールするとスタートメニューに入る「Visual Studio 20xx コマンドプロンプト」(VS用の環境変数が設定済みのコマンドプロンプト)を初めて起動したら、日本語が文字化けして表示された。
  • その少し前にmsysgitをインストールしていて、インストール時にコマンドプロンプトのフォント設定に関する確認を求められていたから、それ絡みかもしれないと思いつき、コマンドプロンプトの設定画面からフォントの設定値を見てみるとちゃんと「MSゴシック」になっている。
  • そもそも、msysgitインストール後に普通のコマンドプロンプトを使ったことは既にあって、文字化けは発生していなかった。
はじめはVisual StudioもExpress Editionだし、昔は日本語のサポートが遅れていたし、とVisual Studioのせいかと思って、そちらを想定したキーワードでググってしまい、直ぐに解決しなかった。濡れ衣を着せてごめんなさい。


◎ mysisgitを疑い直してググッてみた

すぐにレジストリ「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont」のキー「0」を「Lucida Console」→「*MS ゴシック」に変更すれば良いという情報を発見したのだが… ここが長い道のりの始まりだった。
その通りにしようと思いレジストリエディタで「HKEY_LOCAL_MACHINE\~\TrueTypeFont」を開いたら、違和感があって変更できなかったのだ。


違和感の内容は文字化けしているコマンドプロンプトの文字コードはShift-JIS、つまりCP932なのだが、キー「932」の設定値はMSゴシックと、正しそうな設定だということだ。そんな訳でもう少し調べてみることにした。


◎ 調査と実機確認の結果

検索で引っかかった「コンソール(cmd.exe)の文字コードを UTF-8 に」や「MSのナレッジベース」を読んで分かったのは、「HKEY_LOCAL_MACHINE\~\TrueTypeFont」の設定により、コマンドプロンプトのフォント設定画面の一覧に表示されるフォントが決まるということだ。確かにフォント一覧には限られたフォントしか表示されない。

ここから書くことはWebで見つかった断片的な情報と、自分のPC(Windows7 64bit)で色々試した結果から推測したもののため、間違った部分があるかもしれない。

表示するフォントは以下のアルゴリズムで決まる。
  1. コマンドプロンプトの現在のコードページを取得
  2. 各レジストリキーの名前について、以下のチェックを行う。
    1. 名前を先頭からスキャンして数字になる部分を切り出す。
    2. 切り出した文字を数値に変換する。
    3. 現在のコードページが特定のものでなければ、数値に変換した値が0になるキーのフォントが表示される。
    4. ただし、フォントの属性チェックが走り、条件(等幅であるなど)を満たさないフォントは表示されない。うちの環境で表示できたのは「Lucida Console」, 「Consolas」, 「Courier New」だけだった。
    5. 現在のコードページが特定のものである場合は、数値に変換した値がコードページと同じキーのフォントが表示される。特定のコードページというのはおそらくレジストリに初めからエントリされている932, 936, 949, 950でおそらく漢字圏のコードページだけの特別処理なのだろう。
    6. これも、フォントの属性チェックが走るが、0の場合と少し条件が異なる。表示ができたのは「MS ゴシック」, 「Osaka-等幅」, 「VL ゴシック」などの日本語フォントのみだった。(試してないがWebの情報だとMeiryoKe_Consoleもいけるらしい)
  3. 例外としてラスタフォント(Terminal)は必ず表示する。
複雑で例がないと分かり難い。レジストリが
00          フォントA
000 フォントB
932    フォントC (Shift-JIS)
932.    フォントD
932a フォントE
936    フォントF
65001 フォントG (UTF-8)
だったとすると、コードページが932であればフォントC, D, Eが表示される。コードページ936ならフォントF。ここまでは直感的なのだが、コードページ65001はフォントA, Bが表示されるわけでWeb上にも困惑している人がちらほら。


◎ ややこしいことに…

上で決まるのはあくまでコマンドプロンプトの設定画面から設定できるフォントであって、直接設定の保存先を書き換えれば、設定画面に表示できないフォントも設定できる。では、設定の保存先がどこなのかというとこれがまたややこしい。

まず、大元の設定は「HKEY_CURRENT_USER\Console」にある。素の状態でコマンドプロンプトを使うと、ここの設定が読み込まれる。ただ、ここの設定は読み込み専用で、コマンドプロンプトの設定画面から設定を変更しても書き込まれない。
変更のあった設定値だけ、別の場所に書き込まれてそちらが優先して読まれる。

その書き込み先というのはコマンドプロンプトをショートカットから起動した場合はショートカットファイル、exeを直接起動した場合は「HKEY_CURRENT_USER\Console」の下にディレクトリが掘られ、その中に保管される。exeが変わると作られるディレクトリ名が変わる。例えば64bit版Windowsだと、32bitと64bitの2つのcmd.exeがあるが、ディレクトリ名はそれぞれ「%SystemRoot%_System32_cmd.exe」, 「%SystemRoot%_SysWOW64_cmd.exe」となる。

UTF-8で設定画面からは選べない日本語フォントを設定したい場合、ここの設定値を変えれば良い。ショートカットの書き換えは難しいのでレジストリを書き換える。実はexeを直接起動した場合に作られるディレクトリはexeのパスで決まるわけではなく、起動時のウィンドウタイトルで決まり、これはstartコマンド指定できる。手順は以下。
  1. cmd.exeのショートカットを作成する。
  2. リンク先を「cmd.exe /c start "utf8" cmd」に変更する。
  3. そのリンクからコマンドプロンプを起動して、フォントの設定変更を行う。この時ボールドフォントにチェックを入れておくと良い。(後述)
  4. utf8以下に新しい設定値が作られるので、それを書き換える。注意点としてフォントの設定はFaceName, FontSize, FontWeightの3つが揃っていないと不正と判断され、親ディレクトリの設定が読み込まれてしまう。項番3でフォントをボールドフォントにしないとFontWeightというキーが作られないため、このディレクトリのフォント設定は読まれない。別に自分でキーを追加しても可。
  5. ついでにCodePageというキーを65001にしておけば起動毎のchcpが不要になる。

注意点としては、やはり任意のフォントが設定できるわけではなくコードページ932で一覧に表示できないフォントは指定しても表示がおかしくなるということと、以降コマンドプロンプトの設定画面を使おうとしても日本語フォントは選べないため、regeditを使用するなくなってしまうということだ。


◎ あの日、私に何が起こっていたか?

私はmsysgitをインストールする前の時点でコマンドプロンプトを使ってフォントの設定を書き換えたことがあり、ショートカットにその時設定されたフォントが残っていた。でも、Visual Studioの管理コンソールは使ったことがなかったのでショートカットにフォントの設定は残っておらず、レジストリキーのデフォルトの設定「HKEY_CURRENT_USER\Console\FaceName」が読まれた。ここの設定がmsysgitで書き換えられたので文字化けが発生していたというわけである。

コマンドプロンプトのフォントの設定画面ではフォントの表示はMSゴシックになっていたが、それはフォント選択リストの仕様によりコードページ932では「Lucida Console」は表示できなかったためであり、実際の設定値は「Lucida Console」になっていたのだ。だから改めてフォントを設定しなおせば文字化けは治っていたはずである。


◎ 最後に残った謎

色々試して納得しかかったけれど、よくよく考えると1つだけ説明のつかないことがある。それは何故ググったらmsysgitで文字化けしたら「HKEY_LOCAL_MACHINE\~\TrueTypeFont」を直せという情報が引っかかるのかだ。実際自分の環境ではここを直さなくても文字化けは治ったし、調査結果からは治るとも思えない。OSバージョンのせいかとも思ったけれど情報元の人もWindow7(64bit)だし、複数いるようだし、勘違いということもなさそうだ。

自分的には正しい対策は「HKEY_CURRENT_USER\Console\FaceName」を書き換えることのはずなので納得が行かない。でももう調べる気力は起きない。もう既に困らなくなる程度に情報が得られてからも突っ込んで調べ過ぎた感がある。
教えてエロイ人。


Bloggerでラベルのリネーム

一度つけたラベルをリネームしたくなり、メニューを見てみたけどそれらしいものがない。どうしようと思ってググッてみたら、原始的な方法で実現できることが分かった。

http://kamimura4401.blogspot.com/2007/01/blogger-tips.html

投稿の一覧でリネームしたいラベルを選んで一覧表示して、全選択して新しい名前のラベルを追加して古いラベルを削除でOK。ラベルの付いた記事が多くなり複数ページに表示されるようになるとちょっと面倒になるかもしれないが、できなくはなさそうだ。分かってみれば何故すぐに思いつかなかったんだろうと思う。

2011年9月3日土曜日

シリアライズ可能なDictionary(C#)

Dictionaryはシリアライズできないのでシリアライズ可能なSerializableDictionaryを作る方法。

http://d.hatena.ne.jp/lord_hollow/20090206
http://d.hatena.ne.jp/lord_hollow/20090602/p1

上に書いてある方法だと、Dictionaryが空の時にエラーになるので少し手(色付け)を入れてる。
public class SerializableDictionary<Tkey, Tvalue> : Dictionary<Tkey, Tvalue>, IXmlSerializable
{
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(KeyValue));

bool isEmpty = reader.IsEmptyElement; reader.Read(); if (isEmpty) { return; }
while (reader.NodeType != System.Xml.XmlNodeType.EndElement) { KeyValue kv = serializer.Deserialize(reader) as KeyValue; if (kv != null) Add(kv.Key, kv.Value); } reader.Read(); } public void WriteXml(System.Xml.XmlWriter writer) { XmlSerializer serializer = new XmlSerializer(typeof(KeyValue)); foreach (var key in Keys) { serializer.Serialize(writer, new KeyValue(key, this[key])); } } public class KeyValue { public KeyValue() { } public KeyValue(Tkey key, Tvalue value) { Key = key; Value = value; } public Tkey Key { get; set; } public Tvalue Value { get; set; } } }

2011年9月1日木曜日

ListViewのちらつきを抑える(C#)

ListViewはダブルバッファリングが無効なため、項目を追加・削除すると表示にちらつきが生じる。また、背景色を変更するとListView全体に再描画され、これもまたちらつく。一定時間おきに背景色を更新するアプリを作ったら気になってしょうがなかった。対策にはListViewを継承したクラスを作って代わりに使用すれば良い。

引用元:http://geekswithblogs.net/CPound/archive/2006/02/27/70834.aspx
class ListViewNF : System.Windows.Forms.ListView
{
public ListViewNF()
{
//Activate double buffering
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);

//Enable the OnNotifyMessage event so we get a chance to filter out
// Windows messages before they get to the form's WndProc
this.SetStyle(ControlStyles.EnableNotifyMessage, true);
}

protected override void OnNotifyMessage(Message m)
{
//Filter out the WM_ERASEBKGND message
if(m.Msg != 0x14)
{
base.OnNotifyMessage(m);
}
}
}

2011年8月30日火曜日

Visual Studioで外部ツールでソースを更新した時の確認ダイアログがうざい

[ツール]-[オプション]-[環境]-[ドキュメント]-[環境外でのファイルの変更を検出]-[保存する場合、変更を自動的に読み込む]にチェックを入れる。

2011年8月29日月曜日

全プログラムでフォントをアンチエイリアス

64bit環境にも対応しているgdippを使ってみた。インストールするだけでもお手軽に利用できるが、設定ファイルを少しだけいじる。
  1. UIメニューで使うフォントやメイリオはアンチエイリアスを無効に
  2. embolden (フォントの太さ)を増やす。
<?xml version="1.0" encoding="UTF-8" ?>
<gdipp>
<version>0.9.1</version>

<gdimm>
<process>
<freetype>
<cache_max_faces>8</cache_max_faces>
<cache_max_sizes>16</cache_max_sizes>
<cache_max_bytes>2097152</cache_max_bytes>
<lcd_filter>1</lcd_filter>
</freetype>
</process>

<font name="メイリオ.*">
<renderer>0</renderer>
</font>

<font name="Tahoma">
<renderer>0</renderer>
</font>

<font name="MS UI Gothic">
<renderer>0</renderer>
</font>

<font max_height="72">
<auto_hinting>1</auto_hinting>
<embedded_bitmap>0</embedded_bitmap>
<embolden>10</embolden>
<gamma>
<red>0.9</red>
<green>0.9</green>
<blue>0.9</blue>
</gamma>
<hinting>1</hinting>
<kerning>0</kerning>
<render_mode>
<mono>0</mono>
<gray>1</gray>
<subpixel>1</subpixel>
<pixel_geometry>0</pixel_geometry>
<aliased_text>0</aliased_text>
</render_mode>
<renderer>10</renderer>
<shadow>
<offset_x>1</offset_x>
<offset_y>1</offset_y>
<alpha>0</alpha>
</shadow>
</font>

<font>
<renderer>0</renderer>
</font>
</gdimm>

<demo>
<count>5000</count>
<threads>2</threads>
<random_text>0</random_text>
<font>Arial</font>
<font>Consolas</font>
<font>Segoe UI</font>
<font>Tahoma</font>
<font>Verdana</font>
</demo>

<exclude>
<process>conhost\.exe</process>
<process>dwm\.exe</process>
<process>logonui\.exe</process>
<process>service\.exe</process>
<process>spoolsv\.exe</process>
<process>svchost\.exe</process>
<process>taskhost\.exe</process>
<process>userinit\.exe</process>
<process>werfault\.exe</process>
<process>wininit\.exe</process>
<process>winlogon\.exe</process>
</exclude>
</gdipp>

Google Chromeでフォントをメイリオで強制的に表示

UserStyleSheetを利用してMSゴシック系のフォントをメイリオで表示させる。
Windows Vista以降

%LOCALAPPDATA%\Google\Chrome\User Data\Default\User StyleSheets\Custom.css

WindowsXP

%APPATA%\Google\Chrome\User Data\Default\User StyleSheets\Custom.css
@font-face {
font-family: "MS Pゴシック";
src: local("MeiryoKe_PGothic"), local("メイリオ"), local("MS Pゴシック");
}

@font-face {
font-family: "MS PGothic";
src: local("MeiryoKe_PGothic"), local("メイリオ"), local("MS Pゴシック");
}

@font-face {
font-family: "MS UI Gothic";
src: local("MeiryoKe_UIGothic"), local("Meiryo UI"), local("メイリオ"), local("MS UI Gothic");
}

@font-face {
font-family: "MS ゴシック";
src: local("MeiryoKe_Console"), local("MS ゴシック");
}

@font-face {
font-family: "MS Gothic";
src: local("MeiryoKe_Console"), local("MS ゴシック");
}

@font-face {
font-family: "MS UI Gothic";
src: local("MeiryoKe_UIGothic"), local("Meiryo UI"), local("メイリオ"), local("MS UI Gothic");
}

使っているフリーフォント

同じフォントだと飽きてくるのか、たまにフォントを変えるとなんか気分よく作業ができる。
そんな訳で使っているフォントの紹介。

◎ プロポーショナルフォント

MS Pゴシックは見飽きてきたので、メイリオを使うことが多い。ただメイリオはこれまで使われて来たMS Pゴシックと幅に結構差があって、従来のフォント幅を前提に作られたページのデザインやAAが崩れる問題もあるので、字体はメイリオのままフォント幅をMS Pゴシック互換にしたメイリオKEを使っている。

meiryoKe_gen : http://okrchicagob.blog4.fc2.com/blog-entry-169.html#169

◎ 等幅フォント

主にプログラミング用。気分を変えるたエディタとかIDEによりフォントを変えている。

・VLゴシックフォント : http://vlgothic.dicey.org/

M+フォントベース。コマンドプロンプトでも使える。

・OSAKAフォント : http://osaka.is.land.to/

Mac由来。これもコマンドプロンプトで使用可能。

◎ 縦書き対応フォント

普段はメイリオを設定しているけど、気分を変えたい時にいじってみたり。

・あずきフォント・うずらフォント : http://azukifont.com/index.html

手書き系フォントは縦書だと記号が変になるものが多いけど、このフォントは縦書きでもいける。



2011年8月26日金曜日

HTTPリクエスト/レスポンスヘッダの確認方法

WebアプリのデバッグなどでHTTPリクエスト/レスポンスヘッダを確認したい時がある。昔からあるのがローカルプロキシを経由する方法で自分は横取り丸を一番使っていて、SSLの場合はAchillessを使ったりしていた。

他にはブラウザに開発用のアドオンのたぐいを追加する方法もあるが、Google Chromeを使うのであればVanillaで確認できることがわかったのでメモしておく。
URLに「chrome://net-internals/#events」を入力すれば良い。

Filterに「URL_REQUEST」を入力すれば抽出できる。

.NET Framework 4.0でsgen.exeがエラー

Visual Studio C# 2010で対象フレームワークを.NET Framework 4.0にしてプログラムを作成し、sgen.exeを実行したら以下のエラーが発生した。

エラー: 正しくない形式: xxxxxxxx.exe のアセンブリを読み込みしようとしました。
- ファイルまたはアセンブリ 'file:///xxxxxxxx.exe'、またはその依存関係の 1 つが読み込めませんでした。このアセンブリは現在読み込まれているランタイムより新しいランタイムでビルドされているため、読み込むことができません。

3.5以下であれば特にエラーもなく実行できる。
調べたところ、バージョン4.0以降は別の場所においてあるsgen.exeを使わなければいけないらしい。

3.5まで:C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\sgen.exe
4.0以降:C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\sgen.exe

2011年8月25日木曜日

WebClientの初回処理に時間がかかる

C#でWebClientを使うと起動後一回目の処理だけ、異常(10秒以上)に時間がかかる。
内部でプロキシ絡みの処理をやっていてタイムアウトが起っているらしい。

対策にはapp.configのconfigurationタグ以下に↓を追加する。
  <system.net>
<defaultProxy
enabled="false"
useDefaultCredentials="false" >
<proxy/>
<bypasslist/>
<module/>
</defaultProxy>
</system.net>

またはプログラムを↓のようにしても良い。
  WebClient webClient = new WebClient();
webClient.Proxy = null;

副作用としてプロキシが必要な環境では処理ができなくなる。

2011年8月22日月曜日

Gitで過去をやり直す方法

・間違ってコミットしたファイルを過去の履歴も含めてリポジトリから一括削除したい。
git filter-branch -f --index-filter 'git update-index --remove "削除ファイルパス"' HEAD

・名前やメールアドレスを設定せずに作業を始めてしまったので一括して治したい。
git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_NAME" = "unknown" ];
then
GIT_AUTHOR_NAME="新しい名前";
GIT_AUTHOR_EMAIL="新しいメールアドレス";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD

関連付けの変更で指定したいプログラムが出てこない

プログラムを置くディレクトリを移動したら関連付けの設定が古くなり、ファイルをダブルクリックしても関連付けされたプログラムで開けなくなったため、関連付けの設定を新しいプログラムの置き場所に更新しようとして困った事が起こった。

[ファイルを開くプログラムの選択]画面から[参照]ボタンを押して、新しい場所のプログラムを何度選択しても、一覧にそのプログラムが表示されず、設定が治せないのだ。

Webで調べたところ以下のような事象らしい。

プログラムで一度関連付けを行うと、そのプログラムのパス情報がレジストリに保存される。
→ 推奨されるプログラムの一覧の表示ではその情報を元にプログラムの存在を確認しており、移動によって存在しなくなったプログラムは一覧に表示されなくなる。
→ ユーザが新たに移動後のプログラムを選択しても、同じ名前のプログラムの情報がレジストリに既に存在している場合、そちちの情報が優先され、ユーザの選択結果は無視され捨てられる。
→ 結果、一覧には表示されない。

regeditから「HKEY_CLASSES_ROOT\Application\プログラムの実行ファイル名」を消すことで出てくるようになった。

Microsoftさん、いくら何でもこの仕様は腐りすぎじゃあないですか?

DLLの共有セクションを使う場合の注意点

グローバルフックを使用するときなど、複数のプロセスでデータを共有するために、DLLの共有セクションを使う場合がある。
この時、共有する変数は宣言と同時に明示的に初期化しなければ、共有されないので注意すること。

#pragma comment(linker, "/SECTION:.SHAREDMEM,RWS")
#pragma data_seg(".SHAREDMEM")
int shared_ng;                                //NG
int shared_ok = 0;                            //OK
struct shared_struct shared_struct_ng;        //NG
struct shared_struct shared_struct_ok ={0};   //OK
#pragma data_seg()

初期化していないグローバル変数は自動的に0に初期化されることになっているわけだけど、このためにBSSという専用のセクションが用意されていて、スタートアップルーチンでクリア処理が走る。
そのため、初期化をしていない変数はせっかく用意した共有用のデータセグメントではなく、BSSセクションに置かれてしまい共有できないということらしい。

Windows7でグローバルフックが効かなくなる

グローバルフックを使ったアプリを使っていたら、起動直後はちゃんと動くんだけれどそのうち動かなくなるという現象が発生していて、調べてみたらWindows7から少し仕様が変わったらしい。

http://blogs.msdn.com/b/alejacma/archive/2010/10/14/global-hooks-getting-lost-on-windows-7.aspx

フック処理がタイムアウト(300ms)を10回超えちゃうと、グローバルフックが解除されてそのプロセスからは二度と有効化できないと…

はっきり言ってこれは仕様不良。フック処理で重い処理を走らせないように作っても、一定以上の負荷がかかることもあるPCでタイムアウトを起こさないなんてことは無理。スワップアウトしてIO待ちで終わり。実際、if文で整数の値を判定してreturnするだけの処理ですらタイムアウトした。

レジストリの値を変えてタイムアウトを大きくすることで多少低減できるらしいので、アプリ起動時に値を追加しちゃうという手はある。勝手にシステムのレジストリの値を変えるのは良くないという人もいそうだけど、グローバルフックを利用したアプリがまともに使えないバグ持ちのOSを治すパッチを当てているみたいなものなので勘弁して欲しいものだ。
HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeoutを大きくすれば良いんだけど… どのくらいにすれば回避できるんだろうか…

アプリをマルチプロセス構成にして、一定時間ごとに再起動をかけさせるのが正しい対処法なのかもしれないけど、こんな腐れた仕様にあわせてそんな面倒なことしたくない。

(後記:と言いつつマルチプロセス構成にしてしまった。他にもっとエレガントな解決法はないんだろうか。)

2011年8月19日金曜日

マルチモニタ環境でmpc-hcの表示ディスプレイを移動すると再生が一瞬止まる

モニタ間でmpc-hcのウインドウを移動すると、ちょっとの間再生が止まってウインドウがブラックアウトする現象の回避策。

[ツール]-[オプション]-[再生]-[出力]
DIrectShowビデオに「EVR カスタムプレゼンタ」を選んでEVRカスタムプレゼンタの設定で「表示変更時に再初期化」のチェックを外す。

あとはフルスクリーン状態で動画再生中に別のモニタのウインドウをクリックしてアクティブ化すると何故か勝手ににフルスクリーンが解除されてしまうことがある問題を何とかしたいんだが…

ついでに変更している設定の覚え書き。
[表示]-[オプション]
-> [プレイヤー]
--> 再生するメディアファイルごとに新しいプレイヤーを開く。
--> 「設定を.iniファイルに保存」のチェックを有効化。
-> [再生]
--> Default track preferenceを「jap」に
-> [再生]-[フルスクリーン]
--> フルスクリーンモニターに大きい方のディスプレイを選択
->[プレイヤー]-[キーバインド]
--> 「プレイリストバーの表示」に「Middle Down」を割り当てる。

[表示]-[常に手前に表示]:再生中
[再生]-[再生終了後]:フォルダ内を順次再生

git入門

・gitの基礎知識
gitの特徴はローカルリポジトリを持てることだ。cvsやsubverionだと、リポジトリはサーバで一元管理され、コミット時は毎回サーバとのやり取りが発生する。サーバにアクセス出来ない環境ではコミットは行えないし、レスポンスもどうしても悪くなる。
gitでは従来のリポジトリをリモートリポジトリと呼び、これに加えてローカルリポジトリというものが導入されている。リモートリポジトリをローカルリポジトリにコピー→コミットはローカルリポジトリに対して行う。→一段落したらローカルリポジトリの変更をリモートリポジトリに反映という使い方をする。
リモートリポジトリには慣習として、名前に .git を付加する。また、リポートリポジトリのブランチにはorigin、ローカルリポジトリのブランチにはmasterという名前が付けられる。

直している最中のディレクトリの中身を作業ツリーと呼ぶ。コミットは作業ツリーの中身を直接登録するのではなく、インデックスと呼ぶ記録域に一時的に登録してから行う。最新のコミットはHEADと呼ぶ。

・特定のファイルをコミット対象から外したい。
git config --global core.excludesfile ~/Dropbox/git/.gitignore

.gitignore
*.obj
*.exe
*.o
*.lib
*.suo
*.pdb
*.ncb
*.exp
*.suo
*~
*.bak
AssemblyInfo.cs
Debug/
Release/

・ユーザ名、メールアドレス設定
$ git config --global user.name "nazochu"
$ git config --global user.email "nazochu@example.com"

・リポジトリを作る。 (With Dropbox)
自分のやりたい使い方というのはリモートブランチをDropboxの同期ディレクトリ内に置くというもの。

# リモートリポジトリ準備
cd ~/Dropbox
mkdir git
cd git
mkdir test.git
cd test.git
git --bare init
#ローカルリポジトリの準備
cd 既存の開発ディレクトリ
git init
git add .
git commit -a
# git pushと打つだけでリモートリポジトリの情報が更新されるよう、
# リモートリポジトリがどこにあるかなどの情報を登録しておく。
git remote add origin ~/Dropbox/git/test.git
git push -u origin master

後は、ローカルリポジトリのディレクトリ以下のファイルを更新したら
git add . → git commit -a → git push でOK

addでインデックスに登録、commitでローカルリポジトリにコミット、pushでリモートリポジトリに反映。

・タグを付ける
git tag 0.1.0.0
git tag -l
# タグ情報をoriginへ
git push --tags

・コミットしたけど漏れがあった。
git add .
git commit --amend

・git addを全部取り消したい
git reset HEAD

・編集中のファイル全てを最後にコミットした状態に戻したい。
git chechout .

・リポジトリとaddしたファイルの差分を見る
git diff --cached

・addしたファイルの情報を見る。
git status

・addしたファイルと最新コミットのファイルの差分を見る
git diff

・編集中のファイルと最新コミットのファイルの差分を見る
git diff --cached

・コミットの履歴を見る
git log

・git pushがrejectされる。
正しい対応策
git pull
git push

ただ、自分一人しか使っていないリポジトリでpushした後にcommit --amendしただけで面倒と言う場合は
git push -f

Git on Windows

参考ページ:http://sourceforge.jp/magazine/09/02/12/0530242/3

・インストール
mysysgitをインストールする。

・日本語対応
lessnkfをbinに放り込む。

C:\Program Files (x86)\Git\etc\inputrc
set output-meta off
set convert-meta off

set output-meta on
set convert-meta on
set kanji-code utf-8

C:\Program Files (x86)\Git\etc\profile
export GIT_PAGER="nkf -s | less"

~/.gitconfig
[core]
editor = 'c:/nazochu/xyzzy-utf8/xyzzy.exe'

エディタはコミット時のコメントの入力に使用し、UTF-8Nで保存しないといけない。保存時にいちいち文字コードを指定していられないので、デフォルトの文字コードをUTF-8にした状態で起動したい。
xyzzy.exe -e "(set-buffer-fileio-encoding *encoding-utf8n*)" で行けるのだが、.gitconfigに括弧を含む値を記載すると思い通りに動いてくれなかったため、xyzzyを別の場所にコピーしてメニューの設定からデフォルトの文字コードを変えてそちらを使うという力技で対処した。

Git Bashでgitと打った後、Tabを押すとハングして反応が帰ってこなくなる現象が発生した。
C:\Program Files (x86)\Git\etc\git-completion.bash が悪さをしていそうだったので、
一時的にファイル名を変えたら、現象は再現しなくなった。
そしてファイル名を元に戻したら、何故か二度と再現しなくなった。

Ruby on Windows

テキストのプログラム言語を自動的に判別するlinguist(https://github.com/github/linguist)というライブラリがあることを知って使いたくなったけれど、Rubyで書かれているらしい。試すためにWindows上で環境を作ろうとしたらちょっとハマったのでメモ。
Rubyの実行環境は簡単に作れたんだが、linguistがbundler(ruby版aptやportsみたいなものらしい)を使っていたり、拡張ライブラリを使っているせいでコンパイルが必要だったりして面倒だった。
bundlerは裏で色々やってくれるのはありがたいんだけど、内部で何をしているのかが直観的に分かり難かった。
任せきりにしてもエラーが発生しないならブラックボックスとして使えるので良いんだろうけどまだその域には達していなさそう。Linuxで使うならもっとスムーズに行くのかもしれないけど。

rubyをダウンロードして解凍。(以下、C:\nazochu\rubyに置いたとして記載)
http://www.garbagecollect.jp/ruby/mswin32/ja/download/release.html

PATHを変えるのは嫌だったので以下のbatファイルを作成してrubyを使う時はこれからコマンドプロンプトを開くようにする。
set path=%path%;C:\nazochu\ruby\bin
%comspec% /k

lignuistのHPにあるとおりにやってみる。
git clone https://github.com/github/linguist.git
cd linguist/
gem install bundler
bundle install
bundle exec linguist bin/linguist

エラーが色々出てくるので順番に解決していく。

・DLLが見つかりませんのポップアップ

以下から落としてきてbinに放り込む。

zlib, readline : http://jarp.does.notwork.org/win32/
openssl : http://www.limber.jp/2005/04/05/316
iconv : http://sourceforge.net/projects/gettext/files/libiconv-win32/

・nmakeが見つかりません。

必要な環境変数が設定されていないせい。Visual Studio 2008コンソールからbundle installを実行する。

・MSC version unmatch: _MSC_VER: 1200 is expected.

C:\nazochu\ruby\include\ruby-1.9.1\i386-mswin32\ruby\ruby.hを修正する。
#if _MSC_VER != 1200

#if _MSC_VER < 1200

・'VALUE' : この型は演算子として使用できません、みたいなエラー

VC++はC99に対応していないのでブロックの最初以外で変数が宣言されるとエラーになる。
escape_utils.cの拡張子を.cから.cppにリネームしてC++としてコンパイルすることで回避。

・rb_define_methodとかで型エラー

1つ前の対策の副作用。C++になったことで関数ポインタの型が合わなくなったのでキャストを追加。
rb_define_method(mEscapeUtils, "escape_html", rb_escape_html, -1);

#define RB_FUNC(f) reinterpret_cast(f)

rb_define_method(mEscapeUtils, "escape_html", RB_FUNC(rb_escape_html), -1);

・hexCharsのところでエラー
const unsigned char hexChars[16] = "0123456789ABCDEF";

const unsigned char hexChars[17] = "0123456789ABCDEF";

・修正したソースファイルを使わせるにはどうするの?

bundlerで自動ダウンロードされるソースを修正したのだが、bunder installを使うと修正前のファイルで上書きされてしまうため困った。しょうがないのでエラーとなるgemだけ、bunderを使わずにインストールすることにする。
cd C:\nazochu\ruby\lib\ruby\gems\1.9.1\gems\escape_utils-0.2.3\ext\escape_utils
nmake
cd C:\nazochu\ruby\lib\ruby\gems\1.9.1\gems\escape_utils-0.2.3
gem build escape_utils.gemspec
gem install --local escape_utils-0.2.3.gem
ここまでやってbundle installに成功。

・MSVCR90.dllが見つからない。
bundle exec linguist bin/linguistと実行したら怒られた。
ランタイムを入れても良いけど必要ないようにコンパイルオプションを変更してmakeをやり直し。

C:\nazochu\ruby\lib\ruby\1.9.1\i386-mswin32\rbconfig.rb
CONFIG["CFLAGS"] = "-MD -Zi -W2 -O2b2xg- -G6 -Zm600"
CONFIG["CXXFLAGS"] = "-MD -Zi -W2 -O2b2xg- -G6 -Zm600"

CONFIG["CFLAGS"] = "-MT -Zi -W2 -O2b2xg- -G6 -Zm600"
CONFIG["CXXFLAGS"] = "-MT -Zi -W2 -O2b2xg- -G6 -Zm600"

・異常に遅い。

そこそこ新しいPCなのに4秒以上かかる。本処理の前の起動処理で時間がかかっているみたいだ。
bunder経由での起動をやめたらかなり早くなった。

lib以下のファイルをruby -e "puts $LOAD_PATH"で出力されるディレクトリのどこかに置くようにして、
rubyコマンドで直接起動。

2011年8月15日月曜日

BloggerでCSSを設定する方法

Bloggerとかだと投稿欄はWebの表示のコピペを属性付きで受け入れられるので、コードとかを枠付きので記入したいときWebから適当にコピペすればできちゃうんだけど、それだと後で背景色とか枠線のフォーマットとかを変えたくなった時に困るのでCSSで指定する方法を調べてみた。

(1) [デザイン]-[テンプレートデザイナー]-[アドバンス]-[CSSを追加]

#code{
  border: solid 1px #000000; padding: 10px;background-color:#f0ffff;
}

(2) 投稿時、[HTMLの編集]を選んで以下のような感じで記入
<div id="code">
test
</div>

【結果】
test

2011年8月12日金曜日

VirtualBoxでエロゲの薦め

自分は最近エロゲをやるときはPCに直接インストールするのではなく、VirtualBoxという仮想環境上にインストールしてやるようにしている。なぜそんなことをしているのか? スタートメニューやプログラムの削除画面を他人に見られても大丈夫なようにという理由がないとは言わないが、もっと嬉しいご利益があるからだ。

ゲームをやる時はやっぱりフルスクリーンモードでやりたい訳だけど、そうすると困るのが同時にゲーム以外のウインドウをちょっと操作したいという時に切り替えが面倒ということだ。デュアルディスプレイ環境ならいくらか軽減できるけど、ゲームによっては起動すると別のディスプレイに移動できなくなってしまう。ちょっと飽きたんでゲームを一時中断してネットでも見ようかという時も、ゲームを落とさないと片方のディスプレイだけで使いづらい。
かといってウインドウモードでやる場合にも、最近のHD対応の1920x1080のディスプレイで、昔の640x480のゲームをやると、ウインドウが小さすぎてやってられない。

このどちらもVirtualBoxを使うと解決できてしまうのだ。VirtualBoxにはスケールモードという機能があって、仮想環境のデスクトップを好きな大きさに拡大・縮小することができる。つまり、仮想環境内でフルスクリーンでゲームを起動し、スケールモードを使ってウインドウの大きさを変えれば好きな大きさのウインドウでゲームをすることが可能になる。

ただ、この状態でウインドウをディスプレイの大きさ目一杯まで拡大し、フルスクリーンのようにゲームをしようしても、ウインドウに枠線があったり、タイトルバーがあるせいで、微妙にフルスクリーンと同じようにはならない。でも↓のツールを使うことでタイトルバーや枠線を消してフルスクリーンと同じ状態でゲームをすることができる。
https://sites.google.com/site/nazochu/Home/tools/autoscale

この似非フルスクリーンだがマルチディスプレイ環境で別のディスプレイへの移動を妨げられることもないし、ゲーム以外のウインドウを重ねて表示することもできるし、通常のフルスクリーン状態に比べてとても使い勝手が良い。仮想環境を使わなくても初めからこういう動作をしてくれれば良いのに。

最後にデメリットがないわけではないので書いておく。まず、仮想環境では起動しないゲームがたまにあることだ。タイプは2つあって、新しすぎてDirect3DのにVirtualBoxが対応しきれていないタイプと、古すぎてDirect2Dのバグが残ったままになっているタイプだ。手元のソフトで数えてみると、10%行かないくらいだった。

VirtualBoxはDirect3Dにも対応したんだけれど、最近だし、まだまだ穴はあるみたい。これはそのうち解決されていくと期待している。一方古いゲームでできない奴は対応の優先度が低そうなので、ちょっと不安。

あと、普通のゲームならともかく3Dを駆使しているやつだとPCのパフォーマンスに問題が出るかもしれない。(自分のやった範囲では問題にはならなかったが)

最後にWindowsのライセンスが余計に必要になる。意外と高い。

2011年8月10日水曜日

VirtualBoxでディスクイメージファイルを再構築する。

(1) ディスクの空き領域をゼロクリアする
Microsoftの配布しているツール(sdelete)が使える。

http://technet.microsoft.com/ja-jp/sysinternals/bb897443(en-us).aspx

sdelete -c 対象ドライブ

(2) 再構築
REM uuidを調べて
 vboxmanage list hdds

REM 再構築実行
 vboxmanage modifyhd [UUID] compact

Windows Updateを使わずにセキュリティパッチを一括入手

久しぶりにプログラムを作った。Windows2000以降なら動くようにはしたいなと思い、動作確認用に仮想環境にWindows2000をセットアップして知ったけれど、Windows2000用のWindows Updateってもう終了していたのね。プログラムの動作確認用でネットに接続するわけではないので、パッチがあたってなくても致命的ではない気がするけどなんか気持ちが悪い。

とはいえ、さすがに手作業で1つずつパッチをインストールする気にもなれず、いい方法がないか探してみたら↓を見つけた。

Windows Updates Downloader
http://www.windowsupdatesdownloader.com/

セキュリティパッチを一括ダウンロードしてくれるソフトで簡単にパッチが一括ダウンロードできた。

後はインストーラを全部実行すれば良い。
これも手作業でやるのはバッチファイルでもつくろうと思ったけど、ネットを探したらスクリプトが簡単に見つかった。

http://w-lab.sblo.jp/article/1308604.html

そんなわけで思ったより簡単にアップデートが完了。

インテリセンスのファイル格納場所の変更

Visual Studio C++ 2010で作ったプロジェクトをWebで配布しようとして、圧縮ファイルにするとやけにサイズが大きいことに気が付く。
これはインテリセンスという入力補完などをやってくれる機能があり、この機能がキャッシュファイルなどの一時ファイル的なもの(.sdf、.ipchなど)を作るのだが、その出力先がプロジェクトディレクトリ内であるせいだ。これをプロジェクトディレクトリ内から別の場所にする設定をメモ。

(1) メニューから[ツール]→[オプション]
(2) [テキスト エディター]→[C/C++]→[詳細]にある、[常にフォールバック位置を使用]をTrueに設定し、[フォールバック位置]に適当なディレクトリを選ぶ。

2011年7月17日日曜日

一時


#include <stdio.h>

int __stdcall test(){
return printf("test\n");
}

int __stdcall test2(){
return printf("test2\n");
}

typedef int (__stdcall *FARPROC)(void);
typedef int (__stdcall *TEST)(void);
typedef int (__stdcall *TEST2)(void);
typedef int (__stdcall *TEST3)(void);

TEST pTest;
TEST2 pTest2;
TEST3 pTest3;

FARPROC getAddr(int i){
FARPROC result = NULL;

if(i == 0)
result = test;
else if(i==1)
result = test2;

return result;
}

void main(){
int result = 1;

for(int i = 0; i >= 0; i++){
pTest = (TEST)getAddr(0);
result &= pTest != NULL;

pTest2 = (TEST2)getAddr(1);
result &= pTest2 != NULL;

pTest3 = (TEST3)getAddr(2);
result &= pTest3 != NULL;
}
}