コンソールアプリは普通にgnatmake、GUIアプリはpragma Linker_Options("-mwindows")
、では、dllは作れるのか?という挑戦です。
最初は、単純に、pragma Linker_Options("-mdll")
だけでできると思っていました。
しかし、そうやってgnatmakeを繰り返しても、いくらやっても壊れた?dllしかできない様子なので方針変更。
test_dll.oは、dllとして公開する関数を書いたpackageをコンパイルしたものです。 (一応説明しておくと、.oは、gcc版.objファイル)
...>gcc -mdll -o xxx.dll test_dll.o ...>impdef a.def test_dll.dll Borland Impdef Version 3.0.22 Copyright (c) 1991, 2000 Inprise Corporation Warning test_dll.dll: no exports
ちゃんとしたdllっぽいファイルはできたのですが、pragma Export
は明記しているのに、関数をエクスポートしてくれない…。
...>gcc -shared -o xxx.dll test_dll.o
おお、今度は何かエクスポートされてるぞ。
...>impdef a.def test_dll.dll Borland Impdef Version 3.0.22 Copyright (c) 1991, 2000 Inprise Corporation ...>type a.def LIBRARY TEST_DLL.DLL EXPORTS aaa = aaa@0 test_dll_E @2 ; test_dll_E
次に、pragma Export
かpragma Export_Function
かで結構な時間悩んだのですが…。
前者だと、「関数名@数字」が避けられないみたいだけれど、後者だと呼びだし規約が変えられない…。
pragma Convention
で別途規約を指定すると、後者でも「関数名@数字」になってしまう…。
…なんて悩んだのも実は馬鹿らしい話で、-sharedというのはシェアードライブラリを作るオプション。
シェアードライブラリというのはよくわからないのですが、.libの.dll版みたいなもの(Delphiのパッケージみたいなものですか?)で、明示しようがしまいが、ソース上の全関数がエクスポートされてしまうのでした。
呼びだし.exe書いて動作確認までしちゃったじゃないか…。
やはり、-mdllで、何とかして動作させるしかないですね。
…ということで、.defファイルを書く、という古典的解決方法になりました。
LIBRARY TEST_DLL.DLL EXPORTS aaa = aaa
...>gcc -mdll -o test_dll.dll test_dll.o test_dll.def Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups ...>impdef a.def test_dll.dll Borland Impdef Version 3.0.22 Copyright (c) 1991, 2000 Inprise Corporation ...>type a.def LIBRARY TEST_DLL.DLL EXPORTS aaa @1 ; aaa
警告はよくわからないので無視するとして(←おい)、数字でエクスポートされてるみたいではありますが、大丈夫かな?
やってみよ。
program Caller; {$APPTYPE CONSOLE} uses SysUtils; procedure exec; stdcall; external 'test_dll.dll' name 'aaa'; begin exec end.
呼びだし側もAdaを期待してた人はごめんなさい。
Delphiだとインポートライブラリ不要なので楽ですもの。
で、無事、エラーも何も起きない事を確認。(aaaの中は空なので)
やったか!?やったー。
よし、調子に乗って、.dll中で何かやってみましょう。
package body test_dll is procedure aaa is begin Ada.Text_IO.Put("delodelo"); end aaa; end test_dll;
...>gcc -c test_dll.adb ...>gcc -mdll -o test_dll.dll test_dll.o test_dll.def Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups test_dll.o(.text+0x26):test_dll.adb: undefined reference to `ada__text_io__put$4 '
…標準ライブラリが見つからないみたい。
ならば無理やり教えてやるまで。
...>gcc -mdll -o test_dll.dll test_dll.o test_dll.def ...\MinGW\lib\gcc-lib\ming w32\3.2\adalib\libgnat.a Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups
よしGO!
...>caller Runtime error 216 at 0023BFDB
あれー?{$APPTYPE CONSOLE}
は書いたし…(←最初マジ忘れてました)
例外処理を有効にして、詳細なエラー報告をもらう事にしましょう。
というわけでuses SysUtils
を追加。
...>caller EAccessViolation がモジュール test_dll.dll の 0000BFDB で発生しました。 モジュール 'test_dll.dll' のアドレス 0023BFDB でアドレス 00000000 に対する読み込み違 反がおきました。.
…。
中身が空のときは動いたので、dllが壊れているとかはなさそうですし、引数も戻り値も無いので、スタックのやりとりが変という事もないでしょうし… 考えられるとして、ランタイムのスタートアップコードが走って無い、ぐらいですか。
これはネットに頼るしか無いですね…というわけで、検索したら、あっさり発見。
有償版のObjectAdaならdllも変わりなく作れるらしいです。 (実はObjectAdaの試用版は、数年前Ada初挑戦時に使ったことがあるのですが、IDEが使いにくかった&よく落ちた記憶が…金出すなら入力支援&デバッガ付きのIDEは当然ですよね?)
GNUツールのバグがどうたらで、Win9xでは動かないみたいなことが書かれてますが、Win9xなんてのは、そういえば昔、よく落ちるOSがあったなーってな認識なので、何一つ問題はありません。(こんな事書いてるとまた画伯に何か言われそうだ)
サンプルを拾ってきて、make.batを実行しました。
...>make Build the Ada dll `-mpentium' is deprecated. Use `-march=pentium' or `-mcpu=pentium' instead. ...\b_adadll.c が見つかりませんでした。 ...\b_adadll.o が見つかりませんでした。 Build the import library 'lib' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。 ...\adadll.exp が見つかりませんでした。 build the client program Microsoft(R) 32-bit C/C++ Standard Compiler Version 13.00.9466 for 80x86 Copyright (C) Microsoft Corporation 1984-2001. All rights reserved. cl : コマンド ライン warning D4029 : 標準の編集コンパイラでは最適化は使用できま せん。
ファイルが見つからない、というのは、GNATのバージョン違いのせいか、make.bat中で消そうとしている中間ファイルと、実際に生成されている中間ファイルの名前が違うせいです。
libコマンドが無いのは、VC++を持って無いからに他なりません。clが一応起動しているのは、.NET Framework SDKのせいです。
(どっかで見かけたMLコンパイラもそうですが、VC++がインストールされている事が前提になっている場面が多い気がする…そんなにBorland使いって冷遇されてるワケ…?って当のMSすらDirectX8からSDKにBorland用インポートライブラリ付けなくなったぐらいなので、そうなのかも、というより、僕がWin9xなんて忘れたと言ってるのと同じようなものかも)
dllそのものはできたっぽいので、Borland C++でGO! (gcc使えと言われそうな気もするが…)
...>implib -a a.lib adadll.dll Borland Implib Version 3.0.22 Copyright (c) 1991, 2000 Inprise Corporation ...>bcc32 client.cpp a.lib Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland client.cpp: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland ...>client Calling 'Junk' in Ada Dll Excuting procedure 'Junk' from Dll Calling 'Junk_2' in Ada Dll, should return 42 Now excuting function 'Junk_2' from Dll Junk_2 returned 42 Calling 'Junk_3' in Ada Dll with 50, should return 100 function 'Junk_3' in Dll recieved the value: 50 Junk_3 returned 100
おー、動いてます動いてます。
しかし、同じ手順でもってtest_dll.dllを作り直しても、動かなかったので、理由を知るためadadll.adsを覗いてみると…。
何の事は無く、DllMainが書かれていてDLL_PROCESS_ATTACHでAdaInit、DLL_PROCESS_DETACHでAdaFinalが呼ばれていました。
というわけでその辺のコードをごっそりコピー。
転載したらまずいかな。先のリンクから持ってきて頂戴。
要約だけ書いちゃうと、
function DllMain(hInstance: HINSTANCE; Reason: ULONG; Reserved: LPVOID) return BOOL; pragma Export(stdcall, DllMain, "DllMain"); procedure AdaInit; pragma Import(C, AdaInit, "adainit"); procedure AdaFinal; pragma Import(C, AdaFinal, "adafinal");
function DllMain(hInstance: HINSTANCE; Reason: ULONG; Reserved: LPVOID) return BOOL is begin case Reason is when DLL_PROCESS_ATTACH => AdaInit; when DLL_PROCESS_DETACH => AdaFinal; end case; return 1; --TRUE end DllMain;
make.batの先頭の実際にdllを作っている部分もコピーして、adadllをtest_dllに置換して実行。
gnatbind -n -x xx.ali gnatlink -g -mdll -s -o xx.dll xx.ali -Xlinker --base-file=xx.base dlltool --dllname xx.dll --base-file xx.base --output-exp xx.exp --def xx.def gnatlink -g -mdll -s -o xx.dll xx.ali -Xlinker --base-file=xx.base xx.exp dlltool --dllname xx.dll --base-file xx.base --output-exp xx.exp --def xx.def gnatlink -g -mdll -s -o xx.dll xx.ali -Xlinker xx.exp
...>test_make Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups ...>caller delodelo
動いたあ…。結局最後まで警告は消えてくれませんでしたけどね。 stdcallに関係があるのかな、と予想はしてますけど。
おまけ。普通にビルドできないか試してみました。結局ダメでしたけどね。
...>gcc -mdll -o test_dll.dll test_dll.o test_dll.def ...\MinGW\lib\gcc-lib\ming w32\3.2\adalib\libgnat.a Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups test_dll.o(.text+0x15):test_dll.adb: undefined reference to `adainit' test_dll.o(.text+0x1c):test_dll.adb: undefined reference to `adafinal'
adainitですが、BGREPしても見つかりませんでした。
コンパイル時に初めて生成されているようですねえ。
gnatbindが作る中間ファイルの中に登場するようなので、それも渡す事にして、挑戦。
D:\Programming\Tests\Ada\dll>gcc -mdll -o test_dll.dll b~test_dll.o test_dll.o c :\progra~1\mingw\lib\gcc-lib\mingw32\3.2\adalib\libgnat.a
ビルドは成功しましたが…結果を書きますと、caller.exeを起動した時点で、アプリケーションを正しく初期化できませんでしたというダイアログボックスが(コンソールアプリの筈なのに何故かダイアログボックスが)出て、おしまいです。
結局、make.batが最終回答という事ですか。
なんか、Adaそのものよりも、gcc特有の、それも末端の要らない知識ばかり身についてきた気がします…。
・
・
・
む?gnatdll?
・
・
・
何だろう?検索でもしてみるか
・
・
・
うぉおー
これが吠えずにいられるか!
俺の苦労を返せー!!
...>gnatdll -d test_dll.dll test_dll.ali Building relocatable DLL... make test_dll.dll and libtest_dll.a Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups Warning: resolving _aaa by linking to _aaa@0 Use --enable-stdcall-fixup to disable these warnings Use --disable-stdcall-fixup to disable these fixups
上記手順を自動的にやってくれる便利ツールです。こんなものがあったんです。
この例では出力.dll名とソースとなる.aliファイルしか渡してませんが、同名の.defファイルも自動的に使われているようです。
なお、gnatdllなんて名前であるからには、adainit/adafinalを呼ぶDllMainを自動で吐いてくれるかな、と思って試しましたが、ダメでした。
.dllをgnatで使うには、gcc形式のインポートライブラリが必要です。
勿論gcc用のGNUツールを用いてもいいのでしょうが、上記gnatdllでもできました。
.defファイルと.dllからlibxx.aを吐いてくれます。
...>gnatdll -e test_dll.def -d test_dll.dll Building import library... make libtest_dll.a to use dynamic library test_dll.dll
.dll→.defは先のBorland C++ Compilerのimpdefで可能ですから、必要なものは全部揃っている、ということになります。
後はヘッダー(.ads)を書くだけです。
普通に関数宣言して、直後にpragma Import(stdcall, Func_Name, "External_Name")
、それだけ。Delphiでexternal~を書くのとほぼ一緒です。
後は、コンパイル/リンク時に(pragma Linker_Options
でも使って)、リンカに、libxx.aのファイル名を伝えてやればOKです。
…と思ったら、後ろに @4 とか付いているC++な形式じゃないと受け付けてくれない…。
impdefが生成してくれた.defファイルに手動で@なんちゃらを書き足し、それだけではdllの側と食い違うので、さらにgnatdllに-kを渡すと上手くいくようです。
2002-11-10 | 初稿 |
2002-11-15 | gnatdll、使う |
2002-11-16 | @ |