DLLの作成と外部DLLの呼び出し

やり残していたclangでのDLL作成と外部DLLの呼び出しをやってみた。

DirectX呼び出し

.libは32bit版(x86ディレクトリに入っている方)をリンク時に指定したらビルドできた。
あと、bccと同様に「__in」とか「__in_bcount_opt」とかをdefineで無効化しないとコンパイルが通らなかった。
それから__uuidofに対応していないようで、以下のマクロを用意しないとコンパイルが通らない。

#define __uuidof(x) IID_##x

※:リンク時には「dxguid.lib」が必要。

他は特に問題なさそう。
ただし、リンク時に以下の警告が出る。

Warning: .drectve `/DEFAULTLIB:"uuid.lib" /DEFAULTLIB:"uuid.lib" ' unrecognized

  • CreateDXGIFactory()
  • D3D11CreateDeviceAndSwapChain()

あたりは呼んだ感じだとちゃんと動いているっぽい。
ただし、Drawコール類は呼び出してないのでそっちは確認していない。


外部DLL呼び出し

他のDLL呼び出しも見てみるってことで、Luaのモジュールを作ってみた。
これで自分が呼び出されるパターンと自分から外部DLLを呼ぶパターンの両方を確認できる。

ソースコードはこんな感じで、helloモジュールのgetName()関数を用意してみた。
処理内容は単に「clang」という文字列を呼び出し元へ返すだけ。

// hello.cpp
#define SO_EXPORT        __attribute__((visibility("default")))

extern "C"
{
#include <lua.h>        // lua_State
#include <lauxlib.h>    // lua_pushstring, luaL_Reg, luaL_register
}

static int get_name(lua_State *L)
{
    const int ret_count = 1;    // 戻り値の数

    lua_pushstring(L, "clang"); // 戻り値を設定

    return ret_count;
}

static luaL_Reg    module_functions[] = {
    { "getName", get_name },    // 関数登録
    {NULL, NULL}
};

extern "C"
{
SO_EXPORT int luaopen_hello(lua_State *L)
{
    luaL_register(L, "hello", module_functions);  // モジュール登録
    return 1;
}
}

makefileはこんな感じ。

CFLAGS  = -Wall -W -fPIC -fvisibility=hidden -std=c++11
LFLAGS  = -shared -static
INCS    = -I .
LIBS    = -lstdc++ $(LUADLL)
CC      = clang
WSDIR   = C:\Program Files (x86)\Wireshark
LUADLL  = "$(WSDIR)\lua5.1.dll"
TARGET  = hello.dll
SHARK   = "$(WSDIR)\tshark"

.PHONY: all debug build test

all: CFLAGS += -O2
all: build

debug: CFLAGS += -g -finstrument-functions
debug: build

build: $(TARGET)

test:
    $(SHARK) -r dummy.cap -X lua_script:sample-run.lua


.SUFFIXES: .cpp .o

.cpp.o:
    $(CC) $(INCS) -c $(CFLAGS) $<

$(TARGET): hello.o
    $(CC) $(LFLAGS) -o $@ *.o $(LIBS)

LIBSに設定した通り、リンク時にlua5.1.dllを指定するとリンクできた。
.libファイルが無くてもdllを直接見に行けば大丈夫らしい。
この辺りはgccに.soファイルを直接見に行かせた場合と同じだね。

で、出来上がったdllファイルをobjdump -xすると

DLL Name: lua5.1.dll
vma: Hint/Ord Member-Name Bound-To
7268 33 luaL_register
7278 86 lua_pushstring

という感じで外部DLLを参照するようになっている。

DLL作成

上記のとおり、gccと同じようにして外部DLL呼び出しをするDLLを作成できた。

  • コンパイル時に「-fPIC」付与
  • 「-fvisibility=hidden」でデフォルト非公開
  • 「__attribute__((visibility("default")))」で対象関数だけを公開
  • 「-shared」でDLL作成

.defファイルを作ってリンク時に指定する(単に.oファイルと一緒にファイルリストとして指定するだけ)こともできるっぽいけど、少なくともLuaから呼んでもらう分には不要だった。
出来上がったファイルをobjdump -xすると

[Ordinal/Name Pointer] Table
[ 0] luaopen_hello

が見えてそれで呼び出してもらえた。

呼び出し側はWireshark(正確にはtshark)のLuaスクリプト機能を使った。
こんなスクリプトファイルを用意して

-- sample-run.lua
do
    require ("hello")

    print ("Hello " .. hello.getName() .. "'s Lua World!")
end

実行すると期待通り「Hello clang's Lua World!」が表示された。

J:\project\lua\clang>mingw32-make test
"C:\Program Files (x86)\Wireshark\tshark" -r dummy.cap -X lua_script:sample-run.lua
Hello clang's Lua World!

「clang」の部分がclangで作ったDLLが返した文字列で、その他の部分はスクリプトで出している。

余談

最初スクリプトファイルの名前を「hello.lua」にしていてはまってしまった。

tshark: Lua: Error during loading:
.\hello.lua:3: loop or previous error loading module 'hello'

とかおっしゃる。
「loop or previous error loading module」でググったら海外の掲示*1

And since socket.lua has require("socket") in it, it will load itself again. And again. And again.

というのを発見した。
よくよく考えたら、requireって.dllだけじゃなくて.luaも検索対象にするからファイル名が同じだと自分自身を読みに行って無限ループになっちゃうんだね。
おぉ、こわいこわい。
初めのうちはDLLの作り方が悪いのかと思って__stdcallをつけたりとか無駄にいろいろ試してしまった。

64bit

すっかり忘れていたんだけど、64bitアプリから32bitのDLLをロードできないんだった。
64bit Wiresharkを入れていたので、いきなり

C:\Program Files\Wireshark\tshark -r dummy.cap -X lua_script:sample-run.lua
tshark: Lua: Error during loading:
error loading module 'hello' from file '.\hello.dll':
%1 は有効な Win32 アプリケーションではありません。

という感じで怒られた。

調べた感じだと、64bitバイナリを作るにはMinGWの64bit版が必要っぽい。
boostのビルドとかも面倒そうだし、しばらくは64bit化は見送る方向で。