自作VC用DLLの暗黙的リンク用ライブラリの作成ツール(ダウンロード有)

二週間作ったインポートライブラリー作成ツール。DLL持てるがLIBはない場合に使える。
このツールを使うためにDLLに暗黙的リンクについて常識は必要です。

LIB.exeツールでDEFから作ったLIBは、よく使えないことがある。特にx86
例えばcalc.dllにaddと言う関数がある。

extern "C" int __stdcall add(int, int);
エクスポート名はaddだが、linkerは探すのは__imp__add@8あるいは_add@8で
LIB.exeで作ったインポートライブラリにリンクできない。

このツールを使って:
まずXMLファイルを書く、1.xmlとしてセーブする



calc.dll

add
__imp__add@8
_add@8


そしてMakeImpLib 1.xml 1.libで1.libを作成する。

LIBに何があるって知りたいなら

dumpbin /all 1.lib
でわかる。

なんか日本語能力の限界でよく伝えられない><
DLLインポートに詳しい人は多分私は何かを作ったのはわかるでしょう、たぶん。

DLLファイルのリソースにReadmeとヘーダーがあります。どうせソースコード付いてないから(ぁ)このDLLたち使えたいなら使ってください。ほかのは複雑だから、LibGenHelper.dllだけ見れば結構だと思う。説明は全部英語なんだけど(おい)。

DL http://www1.axfc.net/u/3157589.7z

興味ある方は、あるいはソースコード見たい方はツイッターで気軽に私と連絡して。

ちなみに、にほんご関数名にも対応する。


<?xml version="1.0" encoding="UTF-8"?>

calc.dll

add
__imp__プラス@8
_プラス@8

extern "C" int __stdcall プラス(int, int);

特定な場合で仮想メソッドの呼び出しを高速化する方法

一昨日に考えたことです。
CRC16の計算に、こういうインターフェイスを設計した。

class ICrc16
{
public:
virtual ~ICrc16() = 0 {}
virtual void ProcessBit(bool val) = 0;
virtual void ProcessByte(char val) = 0;
virtual void ProcessBlock(char* ptr, int len) = 0;
virtual short Get() = 0;
};
CCITTの実装はこのようです。
class CCrc16CCITT : public ICrc16
{
unsigned short _rem;
public:
CCrc16CCITT()
{
_rem = 0xffff;
}

~CCrc16CCITT()
{
}

void ProcessBit(bool val)
{
bool topBit = _rem >> 15;
_rem <<= 1;
if ( (topBit ^ val) == true )
_rem ^= 0x1021;
}

void ProcessByte(char val)
{
for (int i = 7; i >= 0; --i)
ProcessBit( (val & (1 << i)) != 0 );
}

void ProcessBlock(char* ptr, int len)
{
for(int i = 0; i < len; ++i)
ProcessByte(ptr[i]);
}

short Get()
{
return _rem;
}
};
問題はProcessBitとProcessByteは仮想メソッドで、このようなメソッドを呼び出すのはとても時間を掛かる:まず仮想メソッドテーブルで関数を探しなければならない。非仮想メソッドを呼び出すときこの段階は必要がない。
VCコンパイラーはパラメータ/Ox 付けるとき、ProcessBitを呼び出すところのソースコードは以下のアセンブルを生成する。
	mov	eax, DWORD PTR [ebx]
mov eax, DWORD PTR [eax+4]
test ebp, esi
setne cl
movzx edx, cl
push edx
mov ecx, ebx
call eax
ror esi, 1
dec edi
jns SHORT $LL3@ProcessByt
300MBファイルのCRC16を計算すれば、ProcessByte関数を314572800回呼び出す。そしてもっと厳しいのはProcessBitを2516582400回呼び出す。全部
	mov	eax, DWORD PTR [ebx]
mov eax, DWORD PTR [eax+4]
......
call eax
こうして仮想メソッドを呼び出す。時間を無駄に使った。

よく考えたらそうしないとダメな原因は、例えば私はCCrazyCrcのクラスを書いて

class CCrazyCrc : public CCrc16CCITT {
void ProcessBit(bool val) {...}
};
ならばCCrc16CCITTProcessByteの関数に呼び出すProcessBitはCCrc16CCITT::ProcessBitじゃなくCCrazyCrc::ProcessBitになった。でもCRCの計算についてこの可能性がないはず。コンパイラーは「CCrc16CCITT::ProcessByteに呼び出すProcessBitは決してCCrc16CCITT::ProcessBitだ」って事実を伝えばいい。

そしてCCrc16CCITTはこうなった。

class CCrc16CCITT : public ICrc16
{
unsigned short _rem;
public:
CCrc16CCITT()
{
_rem = 0xffff;
}

~CCrc16CCITT()
{
}

void ProcessBit(bool val)
{
bool topBit = _rem >> 15;
_rem <<= 1;
if ( (topBit ^ val) == true )
_rem ^= 0x1021;
}

void ProcessByte(char val)
{
for (int i = 7; i >= 0; --i)
CCrc16CCITT::ProcessBit( (val & (1 << i)) != 0 );
}

void ProcessBlock(char* ptr, int len)
{
for(int i = 0; i < len; ++i)
CCrc16CCITT::ProcessByte(ptr[i]);
}

short Get()
{
return _rem;
}
};
VCコンパイラは/Oxパラメータ付けば生成したコードに、CCrc16CCITT::ProcessBlockメソッドにもCCrc16CCITT::ProcessByteメソッドにもアセンブル言語の「call」はなくなった。

136MBのファイルをCRCを計算して、
前者のコードは大体15.8秒かかる。後者大体8.8秒かかる。7秒節約した。

GIMP 2.8ツールオプションに文字消えるバグの回避パッチの作成方法

GIMP 2.8 (Win32)は中国語および日本語OSで実行する時、
ツールオプションに文字はよく消える。
回避対策として環境変数のLANGをenに設定すればいい。

前に私はその「環境変数LANGをenにする」って手順をEXEに入らせる。
今はその方法より簡単な方法をここで紹介する。

使ったツールはVC++、そしてCFF Explorer。

「環境変数LANGをenにする」と言うC言語コードはこれです:


putenv("LANG=en");
でもgimp-2.8.exe使ってるC言語ランタイムは自分のVCコンパイラーの使ってるC言語ランタイムと違ってる可能性がある。安全な方法はLoadLibraryとGetProcAddressでputenv関数のアドレスを取得する

HMODULE hCRT = LoadLibraryW(L"msvcrt.dll");
int (__cdecl *fPutenv)(const char*) = (int (__cdecl*)(const char*)) GetProcAddress(hCRT, "_putenv");
fPutenv("LANG=en");
このコードをDLLファイルに入る、そしてgimp-2.8.exeの起動するときその自作DLLをロードすればパッチはできた。

#include <windows.h>
DWORD CALLBACK DllMain(HINSTANCE hDll, DWORD nReason, LPVOID pUseless)
{
if (nReason == DLL_PROCESS_ATTACH) {
HMODULE hCRT = LoadLibraryW(L"msvcrt.dll");
int (__cdecl *fPutenv)(const char*) = (int (__cdecl*)(const char*)) GetProcAddress(hCRT, "_putenv");
fPutenv("LANG=en");
}
return TRUE;
}
extern "C" __declspec(dllexport) void dummy() {}

起動するときDLLをロードしたいなら、何かの関数をインポートしないとだめです。だからそのdummyは・・・
このソースをコンパイルする:
cl /LD gimpPatch.cpp

そしてCFF Explorerの出番です。CFF Explorerでgimp-2.8.exeを開いて、
左側でImport Adder機能を選んで、右側でAdd -> gimpPatch.dll -> dummy -> Import By Name -> Rebuild Import Table。後はEXEファイルをセーブして、完成です。

gimpPatch.dllをgimp-2.8.exeと同じフォルダで放置して、gimp2.8.exe起動して試す。そのバグを回避したはず。

パッチ作成は普通でUDM Free利用すればいい。

私の使った方法はDLLの代わりに、直接そのコードをGIMP-2.8.exeに入る。
パッチ作成はNSISとVPatchを利用するのです。

GIMP 2.8.4 Win32に対応するパッチ

原理など詳しくはこちらへ→ http://d.hatena.ne.jp/sorayukinoyume/20130206
とりあえず原理など同じです。文字の消えることに対応するパッチ。
昨日記事のトラックバックからGIMPの初心者質問掲示板を発見した。
そこに2.8.4の更新情報を手に入れた。
そして今日は2.8.4のパッチ作った。
64ビットの作業環境持たなくてパッチ作れない。
これはパッチです:
http://www1.axfc.net/uploader/so/2785006.zip
対応するGIMPのインストーラーはgimp-2.8.4-setup.exeです。

よろしく

GIMP 2.8.2はWin32でツールオプションに文字が消えるバグ修正パッチ

GIMP 2.8.2は中国語版、日本語版Windowsで実行するとき、
ツールオプションに文字は時々消える。
これで使えないと思う。
BugZillaに報告すると、ほかのレポに統合された。
https://bugzilla.gnome.org/show_bug.cgi?id=668239
あのレポをよく見ると、対策はLANG=enの環境変数追加する。
GIMPのためシステムに環境変数を追加するとはちょっと気持ち悪い。
batファイルを利用するとかの対策もあるが、
互換性に考えればxcfファイルをダブルクリックなどの場合は効けない。
そしで自分はバイナリパッチ作った。
原理は簡単だ:EXEを実行する時msvcrt.dllに_putenv関数を呼ぶ。パラメタはLANG=enにする。
パッチはここでダウンロード:
http://www1.axfc.net/uploader/so/2782890.7z
NSIS + VPatchで作ったパッチ。
gimp-2.8.2-setup-1.exeでインストールやつに使える。

Audacityの改造とLRC/字幕エディターとしての利用

Audacityはオープンソースのツールから、ソースを編集して再構築すればいいですが。再構築は面倒そうなので直接バイナリを編集する方法を使った。
うほっ!WAVEエディターです。
LRCの歌詞作りたいとき、Audacityで時間を確認したら精確なタイミングを取得できる。でもやつ自身はカーソルのところのタイミングを取得するプログラミングインターフェースがなかった。
私、こいつを改造してLRCエディターの一部として使う。

最初に思いついたのは、もしAudacityはカーソルのタイミングをとあるグローバル変数に保存すれば、そのアドレスを探せば十分です。Cheat Engine使って、型は「Double」そして検索モードはUnknown Initial Valueにして、探しは難しくないはず。

いくつがある。0x2ce9a50のようなアドレスはグローバル変数に見えそうじゃない。

ええと、それじゃあどこの指令でこの変数の値を変わるって探す。0x2ce9a50を下のアドレス配列を追加する。そしてCheat engineの「Find out what writes to this address」機能を使って、Audacityでカーソルの場所を変わって簡単に探した。

指令を探したら後はaudacityを改造するのだ。

私の方法は、Audacityの起動するとき共有メモリブロックを開く。成功したら毎度fst qword ptr[ecx]した後qword ptr [ecx]の値を共有メモリにコピーする。その共有メモリブロックは自分は作った何かのLRCエディターで作る。そのコードはアセンブル言語で書く。このようです


BITS 32

_shmemName:
db "81bdd671-bf82-43a9-8078-dd11039f8eeb", 0, 0, 0, 0

_shmemHandle:
dd 0x12345678

_shmemAddr:
dd 0

dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
dd 0x90909090
_entry:
xor eax, eax
mov dword [_shmemHandle], eax
push _shmemName
push 0
push 2
; call OpenFileMapping
dw 0x15ff
dd 0
cmp eax, 0
jle _fail

mov dword [_shmemHandle], eax

push 8
push 0
push 0
push 2
push eax
; call MapViewOfFile
dw 0x15ff
dd 0
cmp eax, 0
jz _fail_2

mov dword [_shmemAddr], eax
jmp _fail

_fail_2:
mov eax, dword [_shmemHandle]
push eax
; call CloseHandle
dw 0x15ff
dd 0
_fail:
ret

_replaceCode:
fst qword [ecx]
mov edx, dword [esi + 0x1c0]
push eax
mov eax, dword [_shmemAddr]
test eax, eax
jnz _cont
pop eax
ret
_cont:
push edx
mov edx, dword [ecx]
mov dword [eax], edx
mov edx, dword [ecx + 4]
mov dword [eax + 4], edx
pop edx
pop eax
ret

entryのコードは、Audacityの起動するとき実行する。replaceCodeのコードは、0x4b1f8cのところで実行する。
このコードはnasm -f binでコンパイルしてバイナリコードになる。
CFF Explorerというツールを利用してAudacityのSection HeaderにAdd Section (File Data)。このバイナリコードをaudacityにインポートする。Change section flagsでIsReadableとIsWritableとIsExecutable全部On。そしてCFF ExplorerのImport Adder機能でCreateFileMappingAとMapViewOfFileとCloseHandleのインポートテーブルを作る。セーブ。

まだまだ足りない。ollydbgの出番だ。さっき言ったことを編集した後AudacityをOllydbgで開いて、


007080B3 > $ E8 E0030000 CALL audacity.00708498
007080B8 .^ E9 36FDFFFF JMP audacity.00707DF3
こういうコードを見える。JMP 00707DF3を編集して、さっきインポートするファイルにたくさんの90(NOP)があるだろう?そこにジャンプする。
あと、004B1F8Cのところのコードは編集して(2つのコードをオーバーライトする)replaceCodeの部分にcallする。これで現有コードの編集は終わりました。セーブして(copy to executable -> all modifications)。

再度OllyDbgでAudacity.exeを開く。今度はさっきインポートしたファイルの部分を編集する。まずあの90等。一部をPUSH 00707DF3に編集する。そうするとretの時元のところに戻れる。
後、インポートしたファイルのコードに全部のshmemName、shmemHandle、shmemAddrのような所のオペランドを修復しなければならない。nasmでコンパイルする時基準アドレスは0にしてたからね。
最後、インポートしたファイルのコードに全部のff15(call)指令のオペランドをつめる。さっき追加したインポートテーブルに対応のアドレスで。
そしてセーブする(編集したところ全部選択してそして copy to executable -> selection)


こうして、カーソルのタイミングは共有メモリから取得できる。例えC#で


using System;
using System.Runtime.InteropServices;

namespace Sorayuki
{
public class AudacityPositionReader : IDisposable
{
[DllImport("Kernel32.dll", EntryPoint="CreateFileMappingA", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.StdCall)]
static private extern UInt32 CreateFileMappingA(UInt32 hFile, IntPtr lpAttributes, UInt32 flProtect, UInt32 dwMaximumSizeHigh, UInt32 dwMaximumSizeLow, string lpName);

[DllImport("Kernel32.dll", EntryPoint="OpenFileMappingA", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.StdCall)]
static private extern UInt32 OpenFileMappingA(UInt32 dwAccess, UInt32 bInherit, string lpName);

[DllImport("Kernel32.dll", EntryPoint="MapViewOfFile", CallingConvention=CallingConvention.StdCall)]
static private extern IntPtr MapViewOfFile(UInt32 hFileMapping, UInt32 dwAccess, UInt32 dwOffsetHigh, UInt32 dwOffsetLow, UInt32 dwLen);

[DllImport("Kernel32.dll", EntryPoint = "UnmapViewOfFile", CallingConvention = CallingConvention.StdCall)]
static private extern UInt32 UnmapViewOfFile(IntPtr pBase);

[DllImport("Kernel32.dll", EntryPoint = "CloseHandle", CallingConvention = CallingConvention.StdCall)]
static private extern UInt32 CloseHandle(UInt32 handle);

private UInt32 _hShmem;
private IntPtr _pShmem;

const string _shmemName = "81bdd671-bf82-43a9-8078-dd11039f8eeb";

public AudacityPositionReader()
{
UInt32 hShmem = OpenFileMappingA(3, 0, _shmemName);
if (hShmem == 0)
{
hShmem = CreateFileMappingA(0, IntPtr.Zero, 4, 0, 8, _shmemName);
if (hShmem == 0)
throw new Exception("Can't create shared memory block");
}

_hShmem = hShmem;

IntPtr pShmem = MapViewOfFile(hShmem, 2, 0, 0, 8);
if (pShmem == IntPtr.Zero)
{
CloseHandle(hShmem);
_hShmem = 0;
throw new Exception("Can't map shared memory block");
}
_pShmem = pShmem;
}

~AudacityPositionReader()
{
Dispose();
}

public void Dispose()
{
UnmapViewOfFile(_pShmem);
CloseHandle(_hShmem);
}

public double Position
{
get
{
unsafe
{
double* pVal = (double*)_pShmem;
return *pVal;
}
}
}
}
}

改造したAudacityおよびソースコード:
http://www1.axfc.net/uploader/so/2751946

仮想マシンでXen+NetBSD Dom0遊ぶ

しばらくDomUの準仮想化OSをインストールしていない。
こうする理由は特になかった。タイトルの言う、遊び。

まずはVMWare Playerの仮想マシンにNetbsd 6.0/i386をインストールする。
この段階は簡単です。普通のOSインストールと特に何か違いはない。

インストール完了後、cdromの/i386/binary/kernel/netbsd-XEN3PAE_DOM0.gzをハードディスクにコピーする。これはXenに必要なDOM0カーネル。Xen 4.1.3で非PAEのカーネルはDOM0として使えないようだ。

そしてpkgsrcを取得する。これはnetbsdのインストールするとき取得できる。あるいは、cdromの /sysinst ツールを起動してConfigメニューにpkgsrcをダウンロード&インストールする。

Xenを入れるためインストールするものは二つ。xentools41とxenkernel41です。二つも/usr/pkgsrc/sysutilsに入る。気軽にmakeしてできる・・・はず。だができない。少なくとも私は失敗した。

xentoolsを構築するためbashは必要です。bashを構築するためgtexinfoは必要です。gtexinfoを構築するためbashは必要です。死ぬ。

仕方ない、bashのバイナリパッケージをインストールする。そしてxentools41を再構築する、今度はできた。

xentools41とxenkernel41を構築完了した後make installを実行する。こうしてOSに入れた。

この段階は終わった後、/usr/pkg/xen41-kernelにXenのカーネルは置いてる。前にハードディスクにコピーしたnetbsd-XEN3PAE_DOM0.gzまだ覚えてる?解凍して / で置いていい。そして /boot.cfg を編集して、Xenで起動のアイテムを追加する

menu=Boot in Xen:load /netbsd-XEN3PAE_DOM0 console=pc; multiboot /usr/pkg/xen41-kernel/xen.gz dom0_mem=384M

dom0_memはNetbsd DOM0に保留するメモリ。赤字の部分は注意して。なければNetBSDの起動から後何も見えなくなった

ここまでは足りない。xendなどのdaemonは入っていないからxmなどのツールは使えない。でもxentoolsをインストールした後 /etc/rc.d にxendなど見つからない。実はやつらは /usr/pkg/share/examples/rc.d にある。xend xencommons xendomainsを/etc/rc.dにコピーする。そして /etc/rc.confに
xend=YES
xencommons=YES
xendomains=YES
を追加する。
システム再起動。boot menuにBoot in Xenを選べば起動する後 xm list でdom0が出る。

下記は参考した文章です:
http://pbraun.nethence.com/doc/sysutils_xen/dom0_netbsd.html
http://www.daemon-systems.org/man/boot.cfg.5.html