DLLから関数を動的にロードします


88

私は.dllファイルを少し見ていて、それらの使用法を理解していて、それらの使用方法を理解しようとしています。

funci()という名前の整数を返す関数を含む.dllファイルを作成しました

このコードを使用して、私は.dllファイルをプロジェクトにインポートしました(苦情はありません):

#include <windows.h>
#include <iostream>

int main() {
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop  \\fgfdg\\dgdg\\test.dll");

  if (hGetProcIDDLL == NULL) {
    std::cout << "cannot locate the .dll file" << std::endl;
  } else {
    std::cout << "it has been called" << std::endl;
    return -1;
  }

  int a = funci();

  return a;
}

# funci function 

int funci() {
  return 40;
}

ただし、.dllをインポートしたと思われるこの.cppファイルをコンパイルしようとすると、次のエラーが発生します。

C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp||In function 'int main()':|
C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp|16|error: 'funci' was not     declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|

.dllがヘッダーファイルとは異なることを知っているので、このような関数をインポートできないことはわかっていますが、試したことを示すために思いつくことができる最善の方法です。

私の質問は、hGetProcIDDLLポインタを使用して.dll内の関数にアクセスするにはどうすればよいかということです。

私はこの質問が理にかなっていることを願っています、そして私はまだいくつかの間違った木を吠えていません。


静的/動的リンクを検索します。
ミッチウィート

ありがとう、私はこれを調べます

コードをインデントしますが、ここに押し込むとフォーマットが混乱するため、すべて4行でインデントされます

回答:


152

LoadLibraryあなたが思っていることをしません。DLLを現在のプロセスのメモリにロードしますが、その中で定義されている関数を魔法のようにインポートしませ。関数呼び出しはコンパイル時にリンカーによって解決され、LoadLibrary実行時に呼び出されるため、これは不可能です(C ++は静的に型指定された言語であることに注意してください)。

動的にロードされる関数のアドレスを取得するには、別のWinAPI関数が必要ですGetProcAddress

#include <windows.h>
#include <iostream>

/* Define a function pointer for our imported
 * function.
 * This reads as "introduce the new type f_funci as the type: 
 *                pointer to a function returning an int and 
 *                taking no arguments.
 *
 * Make sure to use matching calling convention (__cdecl, __stdcall, ...)
 * with the exported function. __stdcall is the convention used by the WinAPI
 */
typedef int (__stdcall *f_funci)();

int main()
{
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop\\test.dll");

  if (!hGetProcIDDLL) {
    std::cout << "could not load the dynamic library" << std::endl;
    return EXIT_FAILURE;
  }

  // resolve function address here
  f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "funci");
  if (!funci) {
    std::cout << "could not locate the function" << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "funci() returned " << funci() << std::endl;

  return EXIT_SUCCESS;
}

また、DLLから関数を正しくエクスポートする必要があります。これは次のように実行できます。

int __declspec(dllexport) __stdcall funci() {
   // ...
}

Lundinが指摘しているように、ライブラリのハンドルが不要になった場合は、ハンドルを解放することをお勧めします。これにより、他のプロセスが同じDLLへのハンドルをまだ保持していない場合、アンロードされます。


ばかげた質問のように聞こえるかもしれませんが、f_funciのタイプは何ですか/すべきですか?

8
それ以外は、答えは素晴らしく、簡単に理解できます

6
ことに注意してくださいf_funci実際には種類がある(というよりも、持っているタイプ)。この型はf_funci、「int引数をとらずに返す関数へのポインタ」として読み取られます。Cの関数ポインタの詳細については、newty.de / fpt /index.htmlを参照してください
Niklas B.

返信ありがとうございます。funciは引数をとらず、整数を返します。質問を編集して、コンパイルされた関数を表示しましたか?.dllに。「typedefintf_funci)();」を含めて実行しようとしたとき このエラーが発生しました:C:\ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp ||関数 'int main()':| C:\ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp | 18 |エラー:引数「2」の「int()()」を「constCHAR *」に変換できません(* GetProcAddress(HINSTANCE__ 、 const CHAR))() '| || ===ビルドが完了しました:1エラー、0警告=== |

さて、私はそこでキャストを忘れました(それを編集しました)。ただし、エラーは別のエラーのようです。正しいコードを使用してもよろしいですか?はいの場合、失敗したコードと完全なコンパイラ出力をpastie.orgに貼り付けてください。また、コメントに書き込んだtypedefが間違っています(*エラーの原因となった可能性のあるanがありません)
Niklas B.

34

すでに投稿されている回答に加えて、関数ごとに個別のGetProcAddress呼び出しを記述せずに、関数ポインターを介してすべてのDLL関数をプログラムにロードするために使用する便利なトリックを共有する必要があると思いました。また、OPで試みたように関数を直接呼び出すのも好きです。

ジェネリック関数ポインター型を定義することから始めます。

typedef int (__stdcall* func_ptr_t)();

どのタイプを使用するかはそれほど重要ではありません。次に、そのタイプの配列を作成します。これは、DLLにある関数の量に対応します。

func_ptr_t func_ptr [DLL_FUNCTIONS_N];

この配列には、DLLメモリ空間を指す実際の関数ポインタを格納できます。

次の問題はGetProcAddress、関数名を文字列として期待することです。したがって、DLL内の関数名で構成される同様の配列を作成します。

const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = 
{
  "dll_add",
  "dll_subtract",
  "dll_do_stuff",
  ...
};

これで、ループ内でGetProcAddress()を簡単に呼び出して、各関数をその配列内に格納できます。

for(int i=0; i<DLL_FUNCTIONS_N; i++)
{
  func_ptr[i] = GetProcAddress(hinst_mydll, DLL_FUNCTION_NAMES[i]);

  if(func_ptr[i] == NULL)
  {
    // error handling, most likely you have to terminate the program here
  }
}

ループが成功した場合、現在発生している唯一の問題は関数の呼び出しです。各関数には独自のシグネチャがあるため、以前の関数ポインタtypedefは役に立ちません。これは、すべての関数タイプで構造体を作成することで解決できます。

typedef struct
{
  int  (__stdcall* dll_add_ptr)(int, int);
  int  (__stdcall* dll_subtract_ptr)(int, int);
  void (__stdcall* dll_do_stuff_ptr)(something);
  ...
} functions_struct;

そして最後に、これらを以前から配列に接続するには、ユニオンを作成します。

typedef union
{
  functions_struct  by_type;
  func_ptr_t        func_ptr [DLL_FUNCTIONS_N];
} functions_union;

これで、便利なループを使用してDLLからすべての関数をロードできますが、それらはby_typeユニオンメンバーを介して呼び出すことができます。

しかしもちろん、次のようなものを入力するのは少し面倒です

functions.by_type.dll_add_ptr(1, 1); 関数を呼び出したいときはいつでも。

結局のところ、これが名前に「ptr」接尾辞を追加した理由です。実際の関数名とは異なるものにしておきたかったのです。いくつかのマクロを使用して、厄介な構造体構文を滑らかにし、目的の名前を取得できるようになりました。

#define dll_add (functions.by_type.dll_add_ptr)
#define dll_subtract (functions.by_type.dll_subtract_ptr)
#define dll_do_stuff (functions.by_type.dll_do_stuff_ptr)

そして、voilà、プロジェクトに静的にリンクされているかのように、正しいタイプとパラメーターで関数名を使用できるようになりました。

int result = dll_add(1, 1);

免責事項:厳密に言えば、異なる関数ポインタ間の変換はC標準で定義されておらず、安全ではありません。正式には、ここで行っているのは未定義の動作です。ただし、Windowsの世界では、関数ポインターはタイプに関係なく常に同じサイズであり、それらの間の変換は、私が使用したWindowsのどのバージョンでも予測可能です。

また、理論的には、union / structにパディングが挿入されている可能性があり、これによりすべてが失敗します。ただし、ポインタはたまたまWindowsの配置要件と同じサイズです。static_assert構造体/共用体にパディングがないことを確認するためのAは、まだ正常である可能性があります。


1
このCスタイルのアプローチは機能します。しかし、#definesを回避するためにC ++構造を使用することは適切ではないでしょうか?
ハーパー2014年

Cでよく++ 11 @harperあなたが使用することができauto dll_add = ...ますが、C ++ 03で、私は、タスクを簡素化するだろうと考える可能性が全く構築物が存在しない(と私はまた、任意の特定の問題が表示されていない#define、ここでS)
ニクラスB.を

これはすべてWinAPI固有であるため、独自にtypedefする必要はありませんfunc_ptr_t。代わりにFARPROC、の戻り値の型であるを使用できますGetProcAddress。これにより、GetProcAddress呼び出しにキャストを追加せずに、より高い警告レベルでコンパイルできるようになります。
エイドリアンマッカーシー

@NiklasB。auto一度に使用できる関数は1つだけであり、ループ内で1回だけ実行するという考えは無効になります。しかし、配列std :: functionの何が問題になっていますか
Francesco Dondi 2016年

1
@Francesco std :: functionタイプは、funcptrタイプと同じように異なります。可変個引数テンプレートが役立つと思います
Niklas B.

1

これは必ずしもホットなトピックではありませんが、dllがインスタンスを作成してDLLとして返すことを可能にするファクトリクラスがあります。それは私が探しに来たものですが、正確に見つけることができませんでした。

それは、のように呼ばれます

IHTTP_Server *server = SN::SN_Factory<IHTTP_Server>::CreateObject();
IHTTP_Server *server2 =
      SN::SN_Factory<IHTTP_Server>::CreateObject(IHTTP_Server_special_entry);

ここで、IHTTP_Serverは、別のDLLまたは同じDLLで作成されたクラスの純粋な仮想インターフェイスです。

DEFINE_INTERFACEは、クラスIDにインターフェイスを与えるために使用されます。インターフェイス内に配置します。

インターフェイスクラスは次のようになります。

class IMyInterface
{
    DEFINE_INTERFACE(IMyInterface);

public:
    virtual ~IMyInterface() {};

    virtual void MyMethod1() = 0;
    ...
};

ヘッダーファイルはこんな感じ

#if !defined(SN_FACTORY_H_INCLUDED)
#define SN_FACTORY_H_INCLUDED

#pragma once

ライブラリーは、このマクロ定義にリストされています。ライブラリ/実行可能ファイルごとに1行。別の実行可能ファイルを呼び出すことができれば、すばらしいでしょう。

#define SN_APPLY_LIBRARIES(L, A)                          \
    L(A, sn, "sn.dll")                                    \
    L(A, http_server_lib, "http_server_lib.dll")          \
    L(A, http_server, "")

次に、dll / exeごとにマクロを定義し、その実装を一覧表示します。Defは、それがインターフェースのデフォルトの実装であることを意味します。デフォルトでない場合は、それを識別するために使用されるインターフェースに名前を付けます。つまり、specialであり、名前はIHTTP_Server_special_entryになります。

#define SN_APPLY_ENTRYPOINTS_sn(M)                                     \
    M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, def)                   \
    M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, special)

#define SN_APPLY_ENTRYPOINTS_http_server_lib(M)                        \
    M(IHTTP_Server, HTTP::server::server, http_server_lib, def)

#define SN_APPLY_ENTRYPOINTS_http_server(M)

ライブラリがすべてセットアップされると、ヘッダーファイルはマクロ定義を使用して必要なものを定義します。

#define APPLY_ENTRY(A, N, L) \
    SN_APPLY_ENTRYPOINTS_##N(A)

#define DEFINE_INTERFACE(I) \
    public: \
        static const long Id = SN::I##_def_entry; \
    private:

namespace SN
{
    #define DEFINE_LIBRARY_ENUM(A, N, L) \
        N##_library,

これにより、ライブラリの列挙型が作成されます。

    enum LibraryValues
    {
        SN_APPLY_LIBRARIES(DEFINE_LIBRARY_ENUM, "")
        LastLibrary
    };

    #define DEFINE_ENTRY_ENUM(I, C, L, D) \
        I##_##D##_entry,

これにより、インターフェイス実装の列挙型が作成されます。

    enum EntryValues
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_ENUM)
        LastEntry
    };

    long CallEntryPoint(long id, long interfaceId);

これはファクトリクラスを定義します。ここではそれほど多くはありません。

    template <class I>
    class SN_Factory
    {
    public:
        SN_Factory()
        {
        }

        static I *CreateObject(long id = I::Id )
        {
            return (I *)CallEntryPoint(id, I::Id);
        }
    };
}

#endif //SN_FACTORY_H_INCLUDED

次に、CPPは、

#include "sn_factory.h"

#include <windows.h>

外部エントリポイントを作成します。これは、depends.exeを使用して存在することを確認できます。

extern "C"
{
    __declspec(dllexport) long entrypoint(long id)
    {
        #define CREATE_OBJECT(I, C, L, D) \
            case SN::I##_##D##_entry: return (int) new C();

        switch (id)
        {
            SN_APPLY_CURRENT_LIBRARY(APPLY_ENTRY, CREATE_OBJECT)
        case -1:
        default:
            return 0;
        }
    }
}

マクロは、必要なすべてのデータを設定します。

namespace SN
{
    bool loaded = false;

    char * libraryPathArray[SN::LastLibrary];
    #define DEFINE_LIBRARY_PATH(A, N, L) \
        libraryPathArray[N##_library] = L;

    static void LoadLibraryPaths()
    {
        SN_APPLY_LIBRARIES(DEFINE_LIBRARY_PATH, "")
    }

    typedef long(*f_entrypoint)(long id);

    f_entrypoint libraryFunctionArray[LastLibrary - 1];
    void InitlibraryFunctionArray()
    {
        for (long j = 0; j < LastLibrary; j++)
        {
            libraryFunctionArray[j] = 0;
        }

        #define DEFAULT_LIBRARY_ENTRY(A, N, L) \
            libraryFunctionArray[N##_library] = &entrypoint;

        SN_APPLY_CURRENT_LIBRARY(DEFAULT_LIBRARY_ENTRY, "")
    }

    enum SN::LibraryValues libraryForEntryPointArray[SN::LastEntry];
    #define DEFINE_ENTRY_POINT_LIBRARY(I, C, L, D) \
            libraryForEntryPointArray[I##_##D##_entry] = L##_library;
    void LoadLibraryForEntryPointArray()
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_POINT_LIBRARY)
    }

    enum SN::EntryValues defaultEntryArray[SN::LastEntry];
        #define DEFINE_ENTRY_DEFAULT(I, C, L, D) \
            defaultEntryArray[I##_##D##_entry] = I##_def_entry;

    void LoadDefaultEntries()
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_DEFAULT)
    }

    void Initialize()
    {
        if (!loaded)
        {
            loaded = true;
            LoadLibraryPaths();
            InitlibraryFunctionArray();
            LoadLibraryForEntryPointArray();
            LoadDefaultEntries();
        }
    }

    long CallEntryPoint(long id, long interfaceId)
    {
        Initialize();

        // assert(defaultEntryArray[id] == interfaceId, "Request to create an object for the wrong interface.")
        enum SN::LibraryValues l = libraryForEntryPointArray[id];

        f_entrypoint f = libraryFunctionArray[l];
        if (!f)
        {
            HINSTANCE hGetProcIDDLL = LoadLibraryA(libraryPathArray[l]);

            if (!hGetProcIDDLL) {
                return NULL;
            }

            // resolve function address here
            f = (f_entrypoint)GetProcAddress(hGetProcIDDLL, "entrypoint");
            if (!f) {
                return NULL;
            }
            libraryFunctionArray[l] = f;
        }
        return f(id);
    }
}

各ライブラリには、この「cpp」と、各ライブラリ/実行可能ファイルのスタブcppが含まれています。特定のコンパイル済みヘッダーのもの。

#include "sn_pch.h"

このライブラリをセットアップします。

#define SN_APPLY_CURRENT_LIBRARY(L, A) \
    L(A, sn, "sn.dll")

メインCPPのインクルード。私はこのcppが.hである可能性があると思います。しかし、これを行うにはさまざまな方法があります。このアプローチは私のために働いた。

#include "../inc/sn_factory.cpp"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.