web-dev-qa-db-ja.com

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

私は.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内の関数にアクセスする方法です。

この質問が理にかなっていることを願っています。間違ったツリーを再度表示することはもうありません。

75
user969416

LoadLibraryは、あなたが思っていることをしません。 DLLを現在のプロセスのメモリにロードしますが、その中で定義された関数をnot魔法のようにインポートしません!これは不可能です。関数呼び出しはコンパイル時にリンカーによって解決され、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;
}

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

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

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

135
Niklas B.

既に投稿された回答に加えて、すべての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 unionメンバーを介してそれらを呼び出すことができます。

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

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)

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

int result = dll_add(1, 1);

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

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

29
Lundin