dllexportを使用してDLLから関数をエクスポートする


105

C ++ Windows DLLから関数をエクスポートする簡単な例を教えてください。

ヘッダー、.cppファイル、および.defファイル(絶対に必要な場合)を確認したい。

エクスポートされた名前を装飾しないでください。最も標準的な呼び出し規約(__stdcall?)を使用したいと思います。ファイル__declspec(dllexport)を使用する必要はありません.def

例えば:

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

名前にアンダースコアや数値(バイトカウント)が追加されないようにしています。

同じヘッダーをサポートせずdllimportdllexport使用してもかまいません。C ++クラスメソッドのエクスポートに関する情報は必要ありません。Cスタイルのグローバル関数だけです。

更新

呼び出し規約を含めない(およびを使用するextern "C")と、希望どおりのエクスポート名が得られますが、それはどういう意味ですか?pinvoke(.NET)、declare(vb6)、およびGetProcAddress予想されるデフォルトの呼び出し規約は何ですか?(GetProcAddressこれは、呼び出し元が作成した関数ポインターに依存すると思います)。

このDLLをヘッダーファイルなしで使用したいので#defines、呼び出し元がヘッダーを使用できるようにするために、それほど多くの機能を必要としません。

私は*.defファイルを使用しなければならないという答えで大丈夫です。


私は覚えていないかもしれませんが、私は次のように考えています。a)extern C関数のパラメーター型を表す装飾を削除しますが、関数の呼び出し規約を表す装飾は削除しません。b)すべての装飾を削除するには、DEFファイルで(装飾されていない)名前を指定する必要があります。
ChrisW、2009

これも私が見ていたものです。たぶん、これを本格的な答えとして追加する必要がありますか?
アードバーク

回答:


134

プレーンCエクスポートが必要な場合は、C ++ではなくCプロジェクトを使用してください。C ++ DLLは、すべてのC ++ ism(名前空間など)の名前変換に依存しています。C / C ++-> Advancedのプロジェクト設定に移動することで、コードをCとしてコンパイルできます。コンパイラスイッチ/ TPおよび/ TCに対応する「Compile As」オプションがあります。

C ++を使用してlibの内部を記述したいが、C ++の外部で使用するためにいくつかの関数を展開せずにエクスポートする場合は、以下の2番目のセクションを参照してください。

VC ++でのDLLライブラリのエクスポート/インポート

あなたが本当にやりたいことは、DLLプロジェクトのすべてのソースファイルに含まれるヘッダーに条件付きマクロを定義することです。

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

次に、エクスポートしたい関数で使用しますLIBRARY_API

LIBRARY_API int GetCoolInteger();

ライブラリビルドプロジェクトで定義を作成します。LIBRARY_EXPORTSこれにより、関数がDLLビルド用にエクスポートされます。

LIBRARY_EXPORTSDLLを使用するプロジェクトでは定義されないため、そのプロジェクトにライブラリのヘッダーファイルが含まれている場合は、代わりにすべての関数がインポートされます。

ライブラリがクロスプラットフォームになる場合、Windows以外ではLIBRARY_APIを何も定義しないことができます。

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

dllexport / dllimportを使用する場合、DEFファイルを使用する必要はありません。DEFファイルを使用する場合、dllexport / dllimportを使用する必要はありません。2つのメソッドは同じタスクをさまざまな方法で実行します。dllexport/ dllimportが2つのうちの推奨されるメソッドであると思います。

LoadLibrary / PInvokeのC ++ DLLからマングルされていない関数をエクスポートする

LoadLibraryとGetProcAddressを使用するためにこれが必要な場合、または別の言語(つまり、.NETからのPInvoke、またはPython / RのFFIなど)からインポートする場合extern "C"は、dllexportでインラインを使用して、C ++コンパイラに名前を壊さないように指示できます。また、dllimportの代わりにGetProcAddressを使用しているため、上からifdefダンスを実行する必要はなく、単純なdllexportを使用します。

コード:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

Dumpbin / exportsでのエクスポートの様子は次のとおりです。

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

したがって、このコードは正常に機能します。

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);

1
extern "C"は、c ++スタイル名のマングリングを削除したようです。インポートとエクスポート全体(質問に含めないことをお勧めします)は、私が実際に求めているものではありません(ただし、その良い情報です)。私はそれが問題を曇らせるだろうと考えました。
アードバーク

LoadLibraryとGetProcAddressが必要だと私が思う唯一の理由は...これはすでに対処済みです。回答の本文で詳しく説明します...
joshperry 2009

EXTERN_DLL_EXPORT == extern "C" __declspec(dllexport)ですか?それはSDKにありますか?
アードバーク

3
モジュール定義ファイルをプロジェクトのリンカー設定に追加することを忘れないでください-「プロジェクトに既存の項目を追加する」だけでは十分ではありません!
ジミー

1
これを使用してVSでDLLをコンパイルし、.Cを使用してRから呼び出しました。すごい!
Juancentro、2015

33

C ++の場合:

私は同じ問題に直面したばかりで、__stdcall(またはWINAPIとの 両方を使用したときに問題が発生することは言及する価値があると思います extern "C"

ご存知のextern "C"ように、次のように装飾を削除します。

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

装飾されていないシンボル名を取得します。

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

ただし、_stdcall(=マクロWINAPIは呼び出し規約を変更します)名前も装飾するため、両方を使用すると次のようになります。

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

extern "C"シンボルが装飾されているため(_ @bytesで)、の利点が失われます

これはx86アーキテクチャでのみ発生することに注意してください。これ__stdcallは、x64 では規約が無視されるためです(msdnx64アーキテクチャでは、規約により、引数は可能な場合はレジスタに渡され、後続の引数はスタックに渡されます)。

x86とx64の両方のプラットフォームをターゲットにしている場合、これは特に注意が必要です。


2つのソリューション

  1. 定義ファイルを使用してください。ただし、これによりdefファイルの状態を維持する必要があります。

  2. 最も簡単な方法:マクロを定義します(msdnを参照):

#define EXPORTコメント(リンカー、 "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

次に、関数本体に次のプラグマを含めます。

#pragma EXPORT

完全な例:

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

これにより__stdcall、x86 の規則を維持しながら、x86とx64の両方のターゲットに対して装飾されていない関数がエクスポートされます。__declspec(dllexport) されていないこの場合に必要。


5
この重要なヒントをありがとう。私の64ビットDLLが32ビットDLLと異なる理由はすでに疑問に思いました。あなたの答えは、答えとして受け入れられたものよりもはるかに便利だと思います。
Elmue

1
私はこのアプローチが本当に好きです。__FUNCTION__マクロは関数でのみ機能するため、マクロの名前をEXPORT_FUNCTIONに変更することをお勧めします。
Luis

3

私はまったく同じ問題を抱えていました。私の解決策は、__declspec(dllexport)エクスポートを定義する代わりにモジュール定義ファイル(.def)を使用することでした(http://msdn.microsoft.com/en-us/library/d91k01sh.aspx)。これが機能する理由はわかりませんが、機能します


このに実行されている他の誰にも注:使用して.defモジュールのエクスポートファイルがない仕事を、しかし供給することができることを犠牲にして、extern例えばグローバルデータでは、ユーザーが指定する必要があり、その場合のためのヘッダファイルに定義をextern手動定義を内部の使用そのデータ。(はい、それが必要になる場合があります。)一般的に、特にクロスプラットフォームコードの場合__declspec()は、単にマクロを使用してデータを通常どおりに処理できるようにする方が優れています。
Chris Krycho、2015年

2
その理由は__stdcall、を使用している場合、おそらく装飾が削除__declspec(dllexport)されないためです。.defただし、関数を意志に追加します。
ビョルンLindqvist

1
@BjörnLindqvist+1、これはx86の場合のみであることに注意してください。私の答えを見てください。
Malick

-1

_nakedはあなたが望むものを得るかもしれないと思いますが、それはまた、コンパイラーが関数のスタック管理コードを生成するのを防ぎます。extern "C"はCスタイルの名前の装飾を引き起こします。それを削除すると、_が削除されます。リンカはアンダースコアを追加しませんが、コンパイラは追加します。stdcallは、引数スタックサイズを追加します。

詳細については、http//en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspxを参照して ください

より大きな質問は、なぜそれをしたいのですか?マングルされた名前の何が問題になっていますか?


マングルされた名前は、LoadLibrary / GetProcAddressまたはac / c ++ヘッダーの使用に依存しない他のメソッドを使用して呼び出されたときに醜くなります。
アードバーク

4
これは役に立たないでしょう-非常に特殊な状況でコンパイラが生成したスタック管理コードを削除したいだけです。(__cdeclを使用するだけで、デコレーションを失うことによる害が少なくなります。デフォルトでは、__ declspec(dllexport)には通常の_プレフィックスが__cdeclメソッドに含まれているようには見えません。)
Ian Griffiths

私はそれが役立つだろうとは本当に言っていなかったので、他の影響についての警告と、なぜ彼がそれをしたかったのかと疑問に思った。
Rob K
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.