DLLの(正しい?)作成方法

[戻る]
なおぞう 2016/02/16(火) 14:38:05
いつもお世話になっております。
VS2013 標準Windowsライブラリを使用、Unicode文字セットを使用、共通言語ランタイム
サポートをしない の環境で、DLLを作成しています。

単純に、DLLでプロジェクトを生成してコンパイルし、testdll.dllが出来たのですが、使
う側ではdllのリンクだけでは動きませんでした。

【testdll.h】
#ifdef __cplusplus
#define EXPORT extern "C" __declspec(dllexport)
#else
#define EXPORT __declspec(dllexport)
#endif

#ifndef __TESTDLL_H
#define __TESTDLL_H

EXPORT int test(int a);//引数で渡された値を100かけて返す

#endif


調べてみると、defを作ればxxxx.DLLだけリンクすれば、DLL内部の関数を使えると書いて
あるのですが、defを使わないでやるようにと言われました。方法を探しているのですが
なかなか見つかりません。このサイトのログも見てみたのですが見つけられませんでした。

DLLの作り方、DLLを読み込む側のルールもいくつかあるのでしょうか。まとめサイトみた
いなのがありましたら教えていただけると助かります。
よろしくお願いします。

瀬戸っぷ [E-Mail] 2016/02/16(火) 15:35:46
>単純に、DLLでプロジェクトを生成してコンパイルし、testdll.dllが出来たのですが、使
>う側ではdllのリンクだけでは動きませんでした。

もう少し、具体的にどうやったのか?
というのが必要かと。

>調べてみると、defを作ればxxxx.DLLだけリンクすれば、DLL内部の関数を使えると書いて
>あるのですが

DEFファイルで外部公開する時の関数名を設定できます。
DEFファイルで指定しない場合にはもろもろのルールにより外部公開する関数名が作成さ
れる為、利用側もそのもろもろのルールに合わせる必要があります。
# 戻り値のサイズとか引数の数や型などで実際に外部公開される関数名が変更される。

dllexportで指定している場合、dllimportにしたヘッダファイルを利用することで、
もろもろのルールを揃えることが可能になります。
# コンパイラ依存については不明ですが。


DLLを「Dependency Walker」で見てみると外部公開されている関数の名前が確認できるかと。
http://soundengine.jp/wordpress/tips/try/555/
http://blog.livedoor.jp/blackwingcat/archives/557640.html


>DLLの作り方、DLLを読み込む側のルールもいくつかあるのでしょうか。まとめサイトみた
>いなのがありましたら教えていただけると助かります。

サイトなどについては不明ですが…
暗黙的リンクなのかどうか…などもあるかと。
# DLL作った時のlibファイル利用だとたいていは暗黙リンクだったような?
# 公開されているDLLのみでヘッダやlibもない状態からの場合は動的リンク…かな。

なおぞう 2016/02/16(火) 17:17:21
>瀬戸っぷ様

ご返答ありがとうございます。
情報が少なかったようで申し訳ありません。

DLL側はEXPORT宣言以外は何も設定などませんでした。

使う側は同じくVC++です。(本番ではDLLから作成したDLLを呼びます)

自分で作った利用側テストプロジェクトでは、

プロパティ→リンカー→入力に DLLTEST.dllを記述
プロパティ→リンカー→すべてのオプションにDLLTEST.dllを記述


DLLTEST.dllを、「追加」の「既存項目」で読み込ませ、DLLのヘッダ(testdll.h)を
インクルードすることによって、使う側は普通にDLL内部の関数を使えていて、それで良
いかと思っていたのですが、DLLのみで使えないと駄目となりました。


//#include "testdll.h" //←ヘッダをコメントアウトするとtest()関数を認識しなくな
ります。


void CMFCApplication2Dlg::OnBnClickedButton1()
{
 int res = 0;
 res = test(4); //testdll.dllの中の関数を呼び出す
}

resには400が返ってきてました。
ヘッダを外すとDLLの関数を認識しなくなるので、DLLの作り方、呼ぶ側の使い方を改めて
調べ直しているところです。

多分、動的リンクとして扱いたいと思うのですが…はっきりしません。
とりあえず、ごく一般的なDLLの作成(def無し)、利用方法を教えていただきたいと思
い、書き込みさせていただきました。

教えてくださったDependency Walker については、後でDLしたいと思います。

みい 2016/02/16(火) 18:58:02
LoadLibraryではいかがでしょうか。

http://web.kyoto-inet.or.jp/people/ysskondo/chap12.html

ITO 2016/02/16(火) 20:12:03
https://msdn.microsoft.com/ja-jp/library/784bt7z7(v=vs.110).aspx
これかな?

瀬戸っぷ [E-Mail] 2016/02/16(火) 21:56:57
>プロパティ→リンカー→入力に DLLTEST.dllを記述
>プロパティ→リンカー→すべてのオプションにDLLTEST.dllを記述

リンカの入力ファイルって、拡張子DLL指定するものでしたっけ?

tetrapod 2016/02/17(水) 11:08:01
DLL 系ヘッダファイルの書き方

DLL を実装する側(提供する側)と DLL を利用する側とで矛盾が発生しないよう
同一内容のヘッダファイル dlltest.h を提供して使い分けるようにするのが普通。
使う側は dllimport 提供する側は dllexport が必要なので下記のようになる。

---dlltest.h---
#if defined(IMPLEMENT_DLLTEST)
/* 提供する側はこちら */
#define DLLTEST_FUNC __declspec(dllexport)
#else
/* 利用する側はこちら */
#define DLLTEST_FUNC __declspec(dllimport)
#endif

DLLTEST_FUNC int dlltest1(int);

---使う側の user1.c---
#include "dlltest.h" // 単純に include すると IMPLEMENT_DLLTEST が未定義
void testfunc() {
    if (dlltest1(0)==0) ...
}

---提供する側の dlltest.c---
#define IMPLEMENT_DLLTEST // include dlltest.h の前に定義する
#include "dlltest.h"

DLLTEST_FUNC int dlltest1(int value) {
    ...
}

提供側は dlltest.h dlltest.dll dlltest.lib の3点セットを提供。
dlltest.c は提供しない。

利用側は dlltest.h を追加ヘッダに追加 dlltest.lib を追加ライブラリに追加し
dlltest.dll を実行環境に配置

インストーラを作る際には dlltest.dll を依存部品に追加ってことで。

なおぞう 2016/02/17(水) 11:37:53
>みなさま 
アドバイスありがとうございます。

テストプログラムを作り直したら、状況が悪化してしまいました。
単純に dllexportしてるんだから、dllimportすればいいんじゃない。って思い以下の
コードを書いてみたのです。

【DLL側 ヘッダファイル】
#ifdef __cplusplus
#define EXPORT extern "C" __declspec(dllexport)
#else
#define EXPORT __declspec(dllexport)
#endif

#ifndef __EXPORTTEST_H
#define __EXPORTTEST_H

EXPORT int kakezan(int a);//引数で渡された値を100かけて返す

#endif

【DLL側 ソースファイル】
#include "EXPORTTEST.h"

EXPORT int kakezan(int a)//引数で渡された値を100かけて返す
{
    return a * 100;
}

コンパイル→作成したEXPORTTEST.dll をIMPORTTESTのDeubgフォルダにコピー
※tetrapod 様の書き込みを拝見すると、dllだけでは駄目という感じですが。

【使う側 ヘッダファイル】
●EXPORTTEST.dllをプロジェクトに追加
●プロジェクトのプロパティ 「追加の依存ファイル」に$(TargetDir)/EXPORTTEST.dll記載


#ifdef __cplusplus
#define IMPORT extern "C" __declspec(dllimport)
#else
#define EXPORT __declspec(dllimport)
#endif


#ifndef __IMPORTTEST_H
#define __IMPORTTEST_H

IMPORT int kakezan(int a);//引数で渡された値を100かけて返す

#endif

【使う側 ソースファイル】
int _tmain(int argc, _TCHAR* argv[])
{
    
    int res = kakezan(20);
    printf("KAKEZAN KEKKA %d\n", res);

    
    return 0;
}

という感じでコンパイルしてみると、
error LNK1107: ファイルが無効であるか、または壊れています: 0x300 を読み取れ
ません。    EXPORTTEST.dll    1    1    IMPORTTEST

なんてエラーが…。
dllが壊れるほど複雑な作り方はしてないと思うのですが、一体どこが悪いのでしょうか。

EXPORTTEST.dllを S2013 Toolsコマンドプロンプトでdumpbin /exportsしたところ、
ordinal     hint       RVA       name
1            0      000110D7  kakezan = @ILT+210(_kakezan)

という結果がでました。
これは、EXPORT関数としてちゃんと定義できてると判断して良いという事ですよね。

なおぞう 2016/02/17(水) 11:44:57
…あれ。dllimportの宣言(?)もDLL側で書くのかな…。間違えている気がしてきました。

LoadLibraryは、サンプルを見たのですが、お恥ずかしい事に、使い方が理解できません
でした。

YuO [E-Mail] 2016/02/17(水) 12:22:52
すでに指摘が入っていますが,「追加の依存ファイル」に書くのは
・インポートライブラリ (拡張子が .lib )
であって,
・DLL本体 (拡張子が .dll )
ではありません。
ref) https://msdn.microsoft.com/ja-jp/library/ba1z7822.aspx

EXPORTTESTのビルド時に生成されているであろうEXPORTTEST.libを依存ファイルとして追加する
ことで,
IMPORTTESTのビルドができるはずです。

# ビルドにDLLは不要,実行時にLIBは不要。

なおぞう 2016/02/17(水) 13:36:44
>YuO 様
ありがとうございます。

使う側のプロパティの「追加の依存ファイル」にEXPORTTEST.libを記載することによって
動きました。

なぜ、前回作っていたテストプログラムでは追加の依存ファイルに EXPORTTEST.dllで動
いていたのかの謎が残りますが、正しい設定方法が分かって良かったです。

【まとめ】
●DLL側の作成方法は問題なし

●呼び側の設定
呼び側は、DLLのlib,dll,hが必要
プロパティで依存ファイルとして設定するのはlib 
DLLのヘッダのインクルードが必要

※ソース中にIMPORT宣言は不要
コンパイルが済んだexeを動かす際にはdllのみが必要

という理解で間違いないでしょうか。


しかし、今回は 呼び側に与えられるのはdllのみという設定(今、dllのスタブを作って
いるところです)なので、libやhを使わない方法は無いでしょうか。

YuO [E-Mail] 2016/02/17(水) 13:48:02
DLLだけでも、一応インポートライブラリは作成できます。

1. dumpbin等でエクスポートされた名前の一覧を取得する
2. 取得した名前を加工してDEFファイルを作る
3. libコマンドに作成したDEFファイルを渡してインポートライブラリを作成する


ただし、DLL等のバイナリからヘッダファイルを作成するのは、基本的に無理です。
型等の情報がないため、まともなヘッダファイルを作れません。
# C++のマングルされた名前を読み解けば、一応ある程度までヘッダファイルを作れます
が……。

なおぞう 2016/02/17(水) 14:07:34
>YuO様
早急な回答ありがとうございます。
defを用意しないでできないかという前提条件があるのですが。

今、defを用意しない EXPORTTEST2を作って普通にコンパイルして作成したdllを利用側
にコピーしました。
【EXPORTTEST2】
#ifdef __cplusplus
#define EXPORT extern "C" __declspec(dllexport)
#else
#define EXPORT __declspec(dllexport)
#endif


#ifndef __EXPORTTEST2_H
#define __EXPORTTEST2_H

EXPORT int warizan(int a);//引数で渡された値を2で割って返す

#endif



利用側は、dllをプロジェクトに追加するだけで、依存ファイル(lib)の設定なども無し
ソース内に、以下のように記述

typedef  int(*FUNC2)(int);

int _tmain(int argc, _TCHAR* argv[])
{

    HMODULE hModule2 = LoadLibrary(_T("EXPORTTEST2.dll"));
    FUNC lpFunc2 = (FUNC2)GetProcAddress(hModule2, "warizan");

    int res = (*lpFunc2)(100);
    printf("WARIZAN KEKKA %d\n", res);

    return 0;
}

正しく動きました。
defを用意しないで作ったDLLを上記のように使った場合では使っているうちに何かしら不
具合が出るでしょうか。
利用するDLL内の関数分typdefとか関数のアドレス取得とか面倒な気もするのですが、DLL
を使う上のルールという事で納得するしかないのでしょうか。

また、DEFを作ればこのような手間はなく、直接関数名を呼べるというのであればDEFを作
ることも考えたいと思いますが、私がネットで見たDEFを作った方法を紹介しているサイ
トでは上記と同様のコードで書かれていました。

tetrapod 2016/02/17(水) 15:13:12
利用側に dlltest.h と dlltest.lib を提供するってことは
例示 typedef/キャスト をコンパイラがやってくれるということだ。
コンパイル時点で各種バグが指摘できて良いことばかり。

利用側に dlltest.h と dlltest.lib を提供しないって事は
利用側で適切な typedef とキャストをする必要があるってこと。
コンパイラによるバグ指摘もなくなってしまう。
どうなれば適切なのかは提供側にしか情報が無いわけで、
提供側はその辺を日本語で記述したマニュアルを作成して提供する義務がある。
その日本語マニュアルがきっちり読み取れる利用者なら、
ヘッダファイルをマニュアルから書き下すことができるわけで
「ヘッダファイルを提供しない」というのは選択肢として限りなく愚策と思う。

政治的理由で「 DLL のみ提供」と(上司が)考えているなら、再考慮を推奨する。

DEF なしで DLL を作ることは何の問題も無いよ。
その DLL を同一名称のまま機能拡張したいとか考えない範囲であれば、だけど。
DLL Hell で検索。

みい 2016/02/17(水) 15:36:10
利用側の最後にFreeLibraryを入れて下さい。

なおぞう 2016/02/17(水) 15:51:37
>tetrapod様

説明ありがとうございます。
簡単で、使いやすくするためには h,lib.dllを提供してもらうのが一番良いという事で
すね。
h.lib.dllの3点セットでもらえるかどうかはっきりしていないのでそこをまずははっきり
させたいと思います。どうしてもdllのみという場合は、利用側で適切な typedef とキャ
ストをする必要があるってことを理解の上ってことですね。
今回の質問でみなさんから教えて戴くまで、dllのみで使えるものだとばかり思っていま
した。勉強になりました。ありがとうございます。

私の周りはC#の開発ばかりでして、VC++で作成したDLLをC#で使う場合、必要なのはdllだ
けで良くて、なんだかすごく簡単にimportできるみたいなのです。

なおぞう 2016/02/17(水) 15:52:38
>みい様

FreeLibraryですか。
調べてみます。ありがとうございます。

なおぞう 2016/02/17(水) 16:03:12
>みい様

GetProcAddressに失敗した場合は、必ずFreeLibraryを呼び出し、DLLを解放します。
ですね。

利用側で typedef とキャストをしてdll内の関数を使うケースの場合に、GetProcAddres
で失敗したら、FreeLibraryを忘れずに。ってことで良いでしょうか。

みい 2016/02/17(水) 17:06:13
FreeLibraryはLoadLibraryと対になります。
LoadLibraryで確保したdllを、最後にFreeLibraryで解放します。

みい 2016/02/17(水) 17:08:17
GetProcAddresの成否ではなく、
LoadLibraryに成功した場合DLLを使い終わったら
必ずFreeLibraryを呼び出し解放します。

なおぞう 2016/02/17(水) 17:41:51
>みい様

正確な情報を教えていただきありがとうございます。
GetProcAddressを使ったら忘れずにLoadLibraryを呼び出します。

みい 2016/02/17(水) 17:55:24
念のため…

GetProcAddressで取得した関数アドレスを使用する間は
FreeLibraryしてはいけません。

なおぞう 2016/02/17(水) 18:03:49
>みい様
わざわざ、ありがとうございます。
まだまだ初心者ですが、さすがに書き間違いしない限り大丈夫です。

スレッドの時は終わりを確認しないでクローズしてますが、スレッド走らせてすぐに制御
を返したいからスレッド終わるの待っていられないし、良いのだと思っています…。
HANDLE hThread = (HANDLE)_beginthreadex(0, 0U, TEST,0 0, &threadID);
CloseHandle(hThread);

YuO [E-Mail] 2016/02/17(水) 21:43:35
> 私の周りはC#の開発ばかりでして、VC++で作成したDLLをC#で使う場合、必要なのはdllだ
> けで良くて、なんだかすごく簡単にimportできるみたいなのです。

C#からネイティブDLL関数呼び出すの,そんなに簡単ではないですよ。
https://msdn.microsoft.com/ja-jp/library/26thfadc.aspx
とか
https://msdn.microsoft.com/ja-jp/library/eaw10et3.aspx
とかをある程度理解しておかないと,痛い目に遭いますから。
# もちろん,C++/CLIのマネージクラスはCLIのメタデータがあるため,簡単に使えますが。

基本的に,C#からネイティブDLL関数を呼び出すにも,ヘッダファイルはないと困ります。
というよりも,ヘッダファイルの情報がないとメソッドを書けません。
DLLからわかるのは基本的に関数名だけなので,引数の数や型,戻り値の型がわからないためで
す。

これは,
> どうしてもdllのみという場合は、利用側で適切な typedef とキャ
> ストをする必要があるってことを理解の上ってことですね。
に書かれている,「適切な typedef」ってどうやって知りますか,ということでもあります。

なおぞう 2016/02/18(木) 10:48:53
>YuO様

URLの紹介ありがとうございます。
一読しましたが、初見の単語がありすぎて理解が追い付きません。

C#の方だと、VCで作ったdllだけをコピーして、クラス内で


 [DllImport("EXPORTTEST2.dll", CallingConvention = CallingConvention.Cdecl)]
 static extern int warizan(int i);

って書いておけば使えると思っていたのですが、違うのでしょうか。C#の方も勉強しない
といけませんね…。

tetrapod 2016/02/18(木) 16:14:20
そういう単純型を取り扱うだけなら提示の方法でできるっす。
marshal が必要な型になると C# のほうも知っておかないと無理っす。

... って今まで dll 公開関数を __stdcall にしてなかったのはわざと?
内部で使っている作業用関数は __stdcall だろうが __cdecl だろうがどうでもいいけど
公開している入口関数は __stdcall のほうが一般的だよ。

なおぞう 2016/02/18(木) 16:46:29
>tetrapod 様

私がど素人だというのは、怒涛の質問投稿でご存知だとは思いますが、「DLLを作る・そ
れを使う」という作業が来て、方法をネットで調べて見つかったのを使っているので、ど
れが良いとかどれが一般的とか知らず、コーディングしている状態なのです。

__stdcallの方が一般的という情報ありがとうございます。


最近は初めての作業内容ばかりで迷走してます。

YuO [E-Mail] 2016/02/19(金) 01:14:14
>  [DllImport("EXPORTTEST2.dll", CallingConvention = CallingConvention.Cdecl)]
>  static extern int warizan(int i);
> って書いておけば使えると思っていたのですが、違うのでしょうか。C#の方も勉強しない
> といけませんね…。

warizan関数の引数がint型1つで,戻り値の型がint型,呼び出し規約がcdeclという情報はどう
やって知ったのでしょうか。
# 通常はそれを知るための方法はヘッダファイルを調査する,であるわけですが。
これは,
> 「適切な typedef」ってどうやって知りますか,ということでもあります。
と同じ質問でもあります。

なおぞう 2016/02/19(金) 09:37:07
>YuO様

説明ありがとうございます。

今回のテストでは、自分でDLLを作って自分で使うから、dllの持つ関数の引数も戻り値も
分かっており、使う側のコードを書けているってことですね。

dllだけを提供する(してもらう)場合はdllが持つ関数情報を知るためにヘッダか関数仕
様書が無いと駄目なのですね。

ITO 2016/02/19(金) 17:07:39
https://msdn.microsoft.com/ja-jp/library/784bt7z7.aspx
これは、見てますか?
>typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);
 これを必要な関数分作らないといけないですね。
実際にこれを作るとなると、結果的に自分でヘッダーファイルを作ることになりますね。

DLLの開発者から、extern C 又は extern C++の宣言部分だけテキストデータで
もらえると楽ですよね。

>typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);
この宣言部分は開発者に掲示をお願いしてもいいと思いますね。
 
 

なおぞう 2016/02/19(金) 19:41:31
>ITO様

はい。必要な関数分typedefするのは面倒だなぁと思っていたところです。
関数仕様書は戴いているので、自分で書くことになりそうです。

ITO 2016/02/20(土) 11:20:44
>関数仕様書は戴いているので、自分で書くことになりそうです。
FAXでもいいので、定義してある部分をリストでもらえると行き違いがなく
済みそうです。
納品前に変更なんて少なからず可能性としてありえます。

なおぞう 2016/02/22(月) 10:25:54
[[解決]]
>ITO様

アドバイスありがとうございます。
リリース前に関数定義をちゃんと確認するようにします。

>みなさん
dllの使い方について、いろいろと教えていただきありがとうございました。

古いスレッドには追加発言できません。