今回もまた分野が変わってC++の内容と、若干のOS関連です。プログラムを書いていて、実行ファイルが一つであればいいのですが、機能を必要に応じて追加したりする場合は共有ライブラリをリンクすることが一般的です。そのリンク方法には、明示的リンクと暗黙的リンクがあり、ここではC++プログラム中で呼び出す方法についてOSごとに分けてまとめたいと思います。
暗黙的リンクの場合、Visual Studioを使うのであれば、DLLを作ってあとはプロジェクトの設定のリンカの設定の部分でのそのファイルを指定するだけなので、また時間を見つけて追記もしたいと思いますが、とりあえずは暗黙的リンクについては後回しにさせてください。暗黙的リンクですと、実行時にDLLが見つからないとエラーが出てプログラム自体が起動できなかったりしますが、明示的にリンクする場合、DLLの有無に応じて処理を変えたりと柔軟な対応が可能です。
共有ライブラリのサンプルプログラムとして、DLLのファイル名はstd::stringで与え、DLLから「Hello Hoge!」とコンソールに表示するプログラムを呼び出したいと思います。これであなたもhoge中毒?(ええ、私はhoge中毒ですw)
明示的リンク
Windowsで使うAPIは、LoadLibrary、GetProcAddressとFreeLibraryの3つの関数です。まぁ、私がとやかく書くよりも本家の参考サイトを見た方が確実かもしれませんが、個人的にできるだけOS固有のAPIは最小限に抑えプラットフォーム依存を減らしたC++で書きたかったので、多少試行錯誤しております。
作り方としてVisual Studio 2015を使うのであれば、新しいプロジェクトの作成に進み、Win32のプロジェクトでDLLを選べば勝手に雛形を作ってくれます。が、捻くれ者の私はまず、作ってくれた雛形を無視して、BOOL APIENTRY DllMainの下に呼び出すDLL側のプログラムとして以下のようなものを作成しました(もちろんincludeは先頭の方に)。これだけなので、問題なくビルドは通ってプロジェクト名.dllというファイルができあがります。ここではWin32DLL.dllとします。
#include <cstdio> (中略) extern "C" __declspec(dllexport) void hoge(void) { printf("Hello Hoge!\n"); }
extern “C”でC++の名前修飾を抑制し、さらにこちらはWindowsに固有な処理ですが__declspec(dllexport)によって名前をエクスポートします。
次に作成されたDLLファイルに含まれるこの関数を呼び出すプログラム本体を書くわけです。次に新しいプロジェクトとして、今度はWin32コンソール アプリケーションを選びます。先程作ったWin32DLL.dllをこのコンソールアプリケーションの実行ファイルが作成されるディレクトリに持ってきて、ファイル名だけの指定で読み込めるようにします(念の為、アプリケーションとDLLのbit数x86 or x64は揃えること)。
LoadLibraryの引数にstd::stringのファイル名を食べさせれば終わりか、と思ったのですが、文字コード初心者の私は大変苦労しました。色々WindowsのAPIを使う方法も試しましたが、最終的にはC++11の範囲内で書きました(ただC++17でdeprecatedとあるのでそのうち書き直すかも)。
void *ptrHandle; // DLLハンドル定義(HINSTANCEじゃなくても動きました) std::string filename = "Win32DLL"; // ファイル名 std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> cv; // コンバーター作成 std::wstring filename2 = cv.from_bytes(filename + ".dll"); // 拡張子付けてコンバート ptrHandle = LoadLibrary(filename2.c_str()); // DLL読み込み if (ptrHandle == nullptr) { printf("Failed to enter Hoge World!\n"); return -1; }
ここまででcodecvt、stringをincludeする必要があります。ここまででDLLの読み込みまでできました。次に、読み込んだDLLから関数のポインタを取り出して実行してみましょう。
// 関数ポインタ定義(少々本家のサンプルと違いますがこちらも動きます) using HOGE_FUNCTION = void (*)(void); // 関数アドレス取得 ptrDllFunc = (HOGE_FUNCTION)GetProcAddress(static_cast<HINSTANCE>(ptrHandle), "hoge"); if (!ptrDllFunc) { printf("Hoge is lost...orz\n"); FreeLibrary(static_cast<HINSTANCE>(ptrHandle)); return 1; } ptrDllFunc(); // 実行 FreeLibrary(static_cast<HINSTANCE>(ptrHandle)); // 後片付け
以上をまとめて必要なヘッダとWindows固有の勝手に付けられるものと合わせますと、
#include "stdafx.h" #include <codecvt> #include <string> #include <windows.h> using HOGE_FUNCTION = void (*)(void); int main() { void *ptrHandle; HOGE_FUNCTION ptrDllFunc; std::string filename = "Win32DLL"; std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> cv; std::wstring filename2 = cv.from_bytes(filename + ".dll"); ptrHandle = LoadLibrary(filename2.c_str()); if (ptrHandle == nullptr) { printf("Failed to enter Hoge World!\n"); printf("Check compatible DLL (x86 or x64) is on the same directory\n"); return 1; } ptrDllFunc = (HOGE_FUNCTION)GetProcAddress(static_cast<HINSTANCE>(ptrHandle), "hoge"); if (!ptrDllFunc) { printf("Hoge is lost...hogehoge\n"); FreeLibrary(static_cast<HINSTANCE>(ptrHandle)); return 1; } ptrDllFunc(); FreeLibrary(static_cast<HINSTANCE>(ptrHandle)); return 0; }
無事にHello Hogeと出たことと思います。あまり新規性もないのですけど、単にhogeを広めたかっただけかもしれません。あとmacOSやLinux版
も書こうとしたのですが、ここで力尽きました…orz
参考:MSDN 明示的なリンク、検索パス
続きのmacOS版
コメント