ホーム > 使った

C++Builder : implibでVisual C++のDLLを使う

概要

C++には優れたライブラリが多いものの昨今ではWindows用に提供されるMakefileは、ほぼVisual C++一択である。とはいえGUIをRADで作るならC++Builder(以下BCB)の方が早くて楽なのも事実である。そこでDLLはVC++のものを使い、implibでBCB用のlibを生成しリンクするという、両者のいいとこ取りをやってみた。

ちょうどEmbarcaderoからもYoutubeビデオが出ていたので合わせて紹介しておく。実はPDFライブラリについて調べていたところだったので、実例としてVC++で作られたPDFlibのDLLをサンプルとして使ってみる。

環境

Windows10 Pro x64
C++Builder 10.3.2 Enterprise
PDFlib 9.2.0 MSWin32 C/C++ (デモ)

解説ビデオ

新しいC++Builder YouTubeビデオシリーズ「Rapid C++ Development: C++BuilderとVisual C++を組み合わせる」 - Embarcadero Blogs - Developer Tools - IDERA Community

全10回でVC++のDLLを使う際の手順や注意点などが紹介されている。やりとりするデータ型も数値、文字列、構造体と一通り扱っている。また、メモリをアロケートした場合、ファイルハンドルの扱い、DLLの動的リンクにも触れており、翻訳も含め内容はよくまとまっている。時間がなければ最後のビデオのまとめだけ見ればよい。その前はVC++とBCBで同等のGUIアプリ(ストップウォッチ)を作りBCBの優位性を宣伝しているだけなのでDLLとは関係ない。

さすがにVC++からいまどきMFCで新規Windowsプログラムを作るのか疑問だが、MSがWPFを含めて迷走しているのも確かなので.net Core次第なのも無理からぬところではある。個人的にはMSが迷走してくれているおかげでレガシーアプリならBCB(Delphi)もまだアリだろう。レガシーアプリがWindows10でも動く点が良くも悪くもMicrosoft = Windowsではなかろうか。

それはともかく非常に乱暴にポイントをまとめると以下の通り。

  • VCのDLLからBCB用のlibはimplibで生成する
  • .objの変換にはcoff2omfを使う
  • DLLの関数はCリンケージで呼び出す
  • 呼び出し規約はDLLでは基本的に__stdcallでにする
  • シンボルが一致しているかどうかはtdumpで調べる
  • 数値・文字列はサイズを含めた型を一致させる
  • 構造体はアライメント・パディングを一致させる
  • メモリ確保/開放はAP/DLL側のいずれかで両方とも行うようにする
  • 但しWin32 API(HeapAllloc/HeapFree)を使う場合この限りではない
  • これはファイルオープン/クローズでも同じ
  • 動的リンクの場合は関数ポインタのシグネチャを一致させる

リソース管理を一方でまとめるのは同じ標準関数でもVCとBCBではランタイムライブラリが異なるため、VCでmalloc()したポインタをBCBでfree()しても無効だからである。そのため現実的にはfree()呼び出しを行なうラッパーを提供する。

PDFlibのDLLを使ってみる

PDFライブラリとしてlibHaruはフリーだしBCBでもmakeできてお手軽であるが、久しくメンテナンスされていない点が少し心配だったのでPDFlibのDLLも使ってみた。実際にBCBからサンプルプログラムでPDFを生成させてみた手順は以下の通り。

PDFlibのデモをダウンロード

PDFlib Product FamilyからWindows Server x86 and Windows 7/8/10 x86にあるPDFlib-9.2.0-MSWin32-C-C++.zipをダウンロード。下の方には日本語向けのTutorialとAPI Referenceがあるので合わせてダウンロードする。

implibでBCB用のlibを作る

PDFlib-9.2.0-MSWin32-C-C++.zipを解凍するとpdflibディレクトリにDLLがあるのでコマンドラインからimplibを使う。

なお、doc\system-requirements.txtに記載のある通りPDFlibのC/C++バインディングは標準でVC++ 2010以降となっており、VS Community 2017でbind\cpp\examples.slnを開いてビルドすればサンプルプログラムを生成・実行できる。(私の環境ではいくつかエラーになったがhello.exeはできていたので、とりあえず先に進む。)

> implib -a pdflib_bcb.lib pdflib.dll
> tdump pdflib_bcb.lib > pdflib_bcb.txt

tdumpした結果を見ると分かるがシンボル名にアンダーバーが必要なのでimplibは-aオプションを付ける。

BCBでVC++のDLLを使う

BCBで新規VCLアプリケーション(Win32)のプロジェクトを作り、プロジェクトにbind\cにあるpdflib.hとhello.c、先ほど生成したpdflib_bcb.libを追加する。

hello.cのmain()をhello()などと名前を変えて以下のように修正する。ついでなので文字列を日本語で出力してみる。

int
hello(void)  // 関数名変更
{
	PDF *p;
	/* This is where the data files are. Adjust as necessary. */
	// パスを適時修正、またはdataの方を移動させる(中身まで見ていないが...)
	const char* searchpath = "../data";

	/* create a new PDFlib object */
	if ((p = PDF_new()) == (PDF *) 0)
	{
		printf("Couldn't create PDFlib object (out of memory)!\n");
		return(2);
	}

	PDF_TRY(p) {
		char optlist[256];
		const char* fontopt =
		// 日本語フォントに変更しエンコードをcp932にする(sjisだとエラー)
		"fontname=MS明朝:0 encoding=cp932 fontsize=24";
		/* This means we must check return values of load_font() etc. */
		PDF_set_option(p, "errorpolicy=return");

		// 日本語フォントを使う場合に必要っぽい
		PDF_set_option(p, "FontOutline={MS明朝=msmincho.ttc}");

		sprintf(optlist, "SearchPath={{%s}}", searchpath);
		/* Set the search path for font files */
		PDF_set_option(p, optlist);

		if (PDF_begin_document(p, "hello.pdf", 0, "") == -1) {
			printf("Error: %s\n", PDF_get_errmsg(p));
			return(2);
		}

		/* This line is required to avoid problems on Japanese systems */
		PDF_set_option(p, "hypertextencoding=host");

		PDF_set_info(p, "Creator", "hello.c");
		PDF_set_info(p, "Author", "Thomas Merz");
		PDF_set_info(p, "Title", "Hello World (C)!");

		PDF_begin_page_ext(p, 0, 0, "width=a4.width height=a4.height");

		PDF_fit_textline(p, "Hello world!", 0, 50, 700, fontopt);
		// 出力文字列を日本語に変更
		PDF_fit_textline(p, "(says C by C++builder 日本語)",  0, 50, 676, fontopt);

		PDF_end_page_ext(p, "");

		PDF_end_document(p, "");
	}
// 以下略

フォームにボタンを配置してダブルクリックしUnit1.cppにhello()のexternとhello()の呼び出しを追加する。

// includeの下あたり
extern "C"
{
	extern int hello(void);
}

// hello()呼び出し
void __fastcall TForm1::Button1Click(TObject *Sender)
{
	hello();
}

フォントパスの指定が不明なので、C:\Windows\Fontsにあるmsmincho.ttcをexeのできるWin32\Debugディレクトリにコピーする。また、pdflib\pdflib.dllも同様にコピーする。

実行

アプリケーションを実行しボタンを押すとexeのあるディレクトリにhello.pdfができる。指定した日本語文字列が出力されていることを確認する。

hello.pdfのjpg

なお、下記のリソースやAPIリファレンスを見る限りでは、CバインディングでもUTF8を使えるようだ。

リソース