2009年1月4日日曜日

LinuxでDLLを流用する

XMMSのpluginなんかでは昔から使われていた方法ですが、wineを使ってDLLを読み込む方法があります。・・・というか、ありました、なのかな?

古いwineの実装では、PROCESS_InitWineとか使ってexec的な事ができたんだけど、今は実装が変わっちゃってるみたい。

wine/library.hを見ると、wine_dll_loadとかwine_dlopenなんて関数があって期待しちゃうんですが、こいつらはwine_initを叩いた後でないと動作しません。ではwine_initを・・・とか思うと、ntdll.dll内で落ちます。実は(少なくとも今現在のcurrentに関しては)wine_initだけでは不十分で、wine_pthread_set_functionsという関数を使って、スレッド回りの処理をする関数群をまとめた関数テーブルをセットしてやる必要があります。この関数はlibrary.hには見えてこないけど、objdump -T libwine.soで確認でき、実際にextern指定してあげればリンクして呼び出すことができます。ではここでセットするwine_pthread_functions構造体を用意すれば良いのか・・・と思って準備を進めると・・・必要となる関数は、
  • init_process
  • init_thread
  • create_thread
  • init_current_teb
  • get_current_teb
  • exit_thread
  • abort_thread
  • pthread_sigmask
これだけあります。プロセスとスレッドの管理はまぁpthreadをラッパーを書いてあげれば済む話なんだけど、TEB関連はちょっとヘビー。細かいことは1年くらい前にmixiで書いたのでそちらを参照してもらうとして、ようはセグメントレジスタをいじって、特殊なアドレスにスレッド情報を格納しておく必要があるって事。ここまでいくと、wineコマンドをスクラッチから書くのとたいしてかわらない手間だという事がわかる。

そうすると、いっそのことexecでwineを叩けばいいじゃんって話になって、結局は必要なDLLを読むEXEを作ってあげて、forkした先の子供でwineをexec。あとは親子でプロセス間通信をして用を足す、って流れに。とりあえずこの方向でやる場合、pipeとmmapで通信できる事は確認してあります。

サンプルコードを書くとこんな感じ。どっちかの方法というよりかは組み合わせて通信するんだろうな。pipeで同期化も兼ねてコマンドを指示。データはmmapの共有メモリにて、って。pipeだとデータの管理が大変だし、共有メモリのポーリングは辛そう。

他にも自作のWinBeを使うって方法もあります。現在、DarwinとLinuxにも移植してあって、勝手しったる〜ってやつで。こっちだとWin32とLinux両方の実行環境を1つのプロセス内に混在させる事ができるんですが・・・如何せん、ひとりで作ったものなので、wineに比べると圧倒的に互換性が低いのが難点です。それでもcodecやら、リンクしたいなと思う類いの物なら大抵動いてくれるので、それなりに重宝します。


-------- winmain.cpp --------

#include <windows.h>
#include <stdio.h>

int WINAPI
WinMain
(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HANDLE hFile = CreateFile("shared_file",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE != hFile) MessageBoxA(NULL, "CreateFile: success", "TEST", MB_OK);
HANDLE hShare = CreateFileMapping(hFile,
NULL,
PAGE_READWRITE,
0,
1024,
"shared_memory");
if (INVALID_HANDLE_VALUE != hShare) MessageBoxA(NULL, "CreateFileMapping: success", "TEST", MB_OK);
char *sharedBuffer;
sharedBuffer = (char *)MapViewOfFile(hShare,
FILE_MAP_WRITE,
0,
0,
1024);
if (NULL != sharedBuffer) MessageBoxA(NULL, "MapViewOfFile: success", "TEST", MB_OK);

snprintf(sharedBuffer, 1024, "this message is send via mmap");
MessageBoxA(NULL, "Write to Memory Mapped File", "TEST", MB_OK);

DWORD dwWrote;
BOOL rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
"hello from exe",
15,
&dwWrote,
NULL);
if (rc) MessageBoxA(NULL, "WriteFile to STDOUT: success", "TEST", MB_OK);
else MessageBoxA(NULL, "WriteFile to STDOUT: failed", "TEST", MB_OK);

char readBuffer[256];
DWORD dwRead = 0;
rc = ReadFile(GetStdHandle(STD_INPUT_HANDLE),
readBuffer,
255,
&dwRead,
NULL);
readBuffer[dwRead] = 0;
char message[512];
snprintf(message, 512, "ReadFile from STDIN: %s", readBuffer);
MessageBoxA(NULL, message, "TEST", MB_OK);

return 0;
}


-------- main.cpp --------

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>

int
main
(int argc, char **argv)
{
int elf2exe_pipefd[2];
int exe2elf_pipefd[2];

if (-1 == pipe(elf2exe_pipefd)) perror("pipe: elf2exe");
if (-1 == pipe(exe2elf_pipefd)) perror("pipe: exe2elf");

pid_t cpid = fork();
if (-1 == cpid) perror("fork");

if (cpid == 0) {
// child (exe)
close(STDIN_FILENO);
close(STDOUT_FILENO);
if (-1 == dup2(elf2exe_pipefd[0], STDIN_FILENO)) perror("dup2: elf2exe");
if (-1 == dup2(exe2elf_pipefd[1], STDOUT_FILENO)) perror("dup2: exe2elf");
close(elf2exe_pipefd[0]);
close(elf2exe_pipefd[1]);
close(exe2elf_pipefd[0]);
close(exe2elf_pipefd[1]);

char *exec_argv[] = {
"wine",
"test.exe",
NULL,
};
execvp(exec_argv[0], exec_argv);
// no return
} else {
// parent (elf)
close(elf2exe_pipefd[0]);
close(exe2elf_pipefd[1]);
int fd_rd = exe2elf_pipefd[0];
int fd_wr = elf2exe_pipefd[1];

char buf[256];
ssize_t size = read(fd_rd, buf, 255);
if (-1 == size) perror("read");
buf[size] = 0;
printf("read from exe: %s\n", buf);
write(fd_wr, "hello from elf", 14);

int fd_map = open("shared_file", O_RDWR);
if (-1 == fd_map) perror("open");
char *shared = (char *)mmap(NULL, 1024,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd_map, 0);
if (NULL == shared) perror("mmap");
puts(shared);
fflush(stdout);
wait(NULL);
puts("parent process exit");
}
return 0;
}

3 件のコメント:

Unknown さんのコメント...

お久しぶりです。《ぬえ》/sawadaです。

お~、なんて懐かしい話題!
WinBeが今でも生かせているというのは素晴らしいですね。

とよしま さんのコメント...

おー、懐かしい方のコメント。うれしいです。昔書いたコードをいじると、わりと覚えていて驚くんですが、それ以上にタイムスタンプみて「え?こんな昔だっけ?」というのが驚きです(笑。

「あなた知ってた? 時間はね、加速してるの少しずつ」ってのは林原めぐみの歌だったかな・・・。深いです。

せっかく話題にしてくれたのでWinBe使った場合のコード例でも。

Win32ワールド用のプロセス情報やTEBを用意してやらないといけないのは一緒ですが、クラスになってるのでわりと楽ちんです。

Coff coff("foo.dll");
coff.Load();
coff.Import();

STARTUPINFOA info;
memset(&info, 0, sizeof(STARTUPINFOA));
info.cb = sizeof(STARTUPINFOA);
info.dwFlags = STARTF_USESTDHANDLES;
File Stdin(STDIN_FILENO);
File Stdout(STDOUT_FILENO);
File Stderr(STDERR_FILENO);
info.hStdInput = (HANDLE)&Stdin;
info.hStdOutput = (HANDLE)&Stdout;
info.hStdError = (HANDLE)&Stderr;
info.lpReserved = "reserved";
info.lpDesktop = "desktop";
info.lpTitle = "title";

Process process;
process.SetStartupInfoA(&info);
process.SetInstance(coff.GetInstance());
process.SetStartupDirectory("./");

ThreadEnvironmentBlock teb;

coff.ProcessAttach();
coff.ThreadAttach();
BarFunc bar = (BarFunc)coff.GetAddressByName("barFunc");
bar();

Unknown さんのコメント...

覚えていて下さって嬉しいです。コードは随分すっきりしてますね。

時間はどんどん加速していきますよー。歳とともに時間の使い方をどう変えていくかが大事なんだっていうことを痛感しているこの頃です(^^;