0

起動引数を解析し、いくつかの DLL から "起動" する DLL の 1 つを選択する、非常に小さなプログラムを作成したいと考えています。

DLL として "実行" したいアプリケーションをアプリケーションとして作成し、Visual Studio プロジェクトのプロパティを変更して代わりに DLL としてビルドしました。必要な機能を得るには、LoadLibrary と GetProcAddress を同時に使用する必要があることはわかっていますが、多くのユース ケースは実際にはこの性質のものではないため、これに関する明確で包括的なドキュメントを見つけるのに苦労しています。また、プロジェクトとプラットフォームの制限に基づいて、このルートを使用する必要があります。

私はこのページを見つけました 、いくつかの情報がありますが、私の目的に適応するには十分に明確ではありません.

編集:ここが私が今いるところです。

メイン関数のシグネチャが次のような DLL プロジェクトがあります。

__declspec(dllexport) int cdecl main(int argc, char *argv[])

また、DLL をロードして上記の関数を実行しようとするアプリケーション プロジェクトは次のようになります。

typedef int (CALLBACK* LPFNDLLFUNC1)(int, char *);

...

        HMODULE dllHandle = NULL;
        BOOL freeResult, runTimeLinkSuccess = FALSE;
        LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer  
        if (args->IsEmpty())
        {
            dllHandle = LoadLibrary(L"TrueApplication.dll");
            if (NULL != dllHandle)
            {
                lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(dllHandle, "main");
                if (lpfnDllFunc1)
                {
                    int retVal = lpfnDllFunc1(0, "1");
                }

現在、LoadLibrary 呼び出しは機能しますが、GetProcAddress は機能しません。

4

3 に答える 3

2

まず、プロジェクト タイプを実行可能から DLL に変更するだけでは、DLL を作成するのに十分ではありません。API を作成するには、いくつかのシンボルをエクスポートする必要もあります。少なくとも、エクスポートする関数を で装飾する必要があります__declspec(dllexport)。ただし、互換性のある引数を持つextern "C"関数を意味する C API をエクスポートすることをお勧めします。Cしたがって、エクスポートする関数の前にextern "C" __declspec(dllexport).

それが完了したら、次のように DLL を動的にロードできます。

   const char* dllname = "myfile.dll";
   h = LoadLibrary(dllname);
   if( h == nullptr )
   {
       /*handle error*/
   }

   using myfunc_type = bool (*)(int x, double y); //example
   auto myfunc = reinterpret_cast<myfunc_type>(GetProcAddress(h, "myfunc"));       
   //......
   myfunc(x,y); //call the imported function

このソリューションは、Jerry Coffin によって示されている静的な読み込みよりも多くの作業/delayloadが必要ですが、利点があります。DLL が必要であるが見つからない場合、Windows からのメッセージに依存する代わりに、独自のエラー メッセージをユーザーに提供できます (これは多くの場合、非技術者には受け入れられません)。また、独自のカスタム エラー メッセージとともに API バージョンの検証を API に含めることもできます。

編集:このように変更すると、コードサンプルが機能します

extern "C" __declspec(dllexport) int main(int argc, char *argv[]){...}
typedef int (* LPFNDLLFUNC1)(int, char **);
于 2016-12-02T19:38:24.387 に答える
2

DLL 内の機能を使用して呼び出す必要はありません。LoadLibraryGetProcAddress

多くの場合、それぞれ独自のエントリ ポイントを持つ DLL を作成します。ここでは、コマンド ラインを解析し、DLL を選択し、引数なしでそのエントリ ポイントを呼び出すとします。次のような結果になります。

void DLL_a();
void DLL_b();
void DLL_c();

int main(int argc, char **argv) { 
    // we'll assume DLL_a is the default:
    if (argc < 2) 
        DLL_a();

    // For now, we'll do a *really* trivial version of parsing the command line
    // to choose the right DLL:
    if (argv[1][0] == 'A')
        DLL_a();
    else if (argv[1]][0] == 'B')
        DLL_b();
    else if (argv[1][0] == 'C')
        DLL_c();
    else {
        std::cerr << "Unrecognized argument\n";
        return 1;
    }
}

メインをリンクするときは、.lib各 DLL に対応する を指定します。おそらく/delayload、リンカーにフラグを指定する必要があります。これは、DLL 内の関数が実際に呼び出されるまで、DLL がロードされないことを意味します。たとえば、DLL A のみを含むプログラムの機能を縮小したバージョンを配布する場合、DLL B からの機能がない限り、(ユーザーのシステムに DLL B または C が存在しなくても) 実行できます。または C が呼び出されます。を指定しない場合/delayload、ローダーは、プログラムの起動時にすべての DLL を RAM にマップしようとし、それらDllMainを実行して使用するために初期化し、依存するすべての DLL に対して同じことを再帰的に実行します。

/delayloadにはもう 1 つの利点があります。他の DLL が使用されていない場合、アドレスへのマッピングをまったく回避できます。特定の呼び出しは 1 つの DLL のみを使用するように思われるため、おそらくあなたの場合は勝利です。

于 2016-12-02T19:25:25.193 に答える
2

これを行う必要はありませGetProcAddress (...)が、コンパイラがシンボル名を生成する方法を理解すれば、その方法 (オプション 2) の方が簡単です。


オプション1

DllMain はメイン スレッドを生成します

DllMain 内で複雑なことを行わないでください。ソフトウェアがデッドロックする可能性があります。

DLL には、独自のエントリ ポイントがあります (そして、出口ポイントとスレッド アタッチ ポイント ... これは非常に忙しい機能です)。DLL を呼び出すだけで、プロセス アタッチのためLoadLibrary (...)に少なくとも 1 つの呼び出しが発生します。DllMain (...)

BOOL
APIENTRY
DllMain ( HMODULE hModule,
          DWORD   ul_reason_for_call,
          LPVOID  lpReserved )

ul_reason_for_call == DLL_PROCESS_ATTACH実際には、プログラムのメイン関数であるかのように、DllMain を実行する命令として扱うことができます。

さて、実際にここでプログラム ループを開始するべきではありません...DllMain実行するたびに、非常に重要なオペレーティング システム ロック (DLL ローダー) が保持されているため、通常のプログラム操作に戻ることによってそれを解放する必要があります。

つまりDllMain、プログラムのエントリポイントとして使用する場合は、スレッドを生成する必要があり、mainそのスレッドが終了するまで元のメソッドを返してはなりません...


オプション #2

DLL はmain関数をエクスポートします。

呼び出し規則に十分注意してください。コンパイラはシンボルの名前を変更し、GetProcAddress直感的ではない DLL 内の関数を見つけます。

DLL で、以下をエクスポート mainします。

__declspec (dllexport)
int
__cdecl main (int argc, char *argv [])
{
  printf ("foobar");
  return 0;
}

プログラムで、DLL からインポートします。 main

// Need a typedef for the function you are going to get from the DLL
typedef int (__cdecl *main_pfn)(int argc, char *argv[]);

int main (int argc, char *argv[])
{
  HMODULE hModMyDLL = LoadLibraryA ("MyDll.dll");

  if (hModMyDLL != 0) {
    //
    // The preceding underscore deals with automatic decorations
    //   the compiler added to the __cdecl function name.
    //
    //  It is possible to do away with this completely if you use a .def
    //    file to assign export names and ordinals manually, but then you
    //      lose the ability to tell a function's calling convention by its
    //        name alone.
    //
    main_pfn MyMain = (main_pfn)
      GetProcAddress (hModMyDLL, "_main");

    // Call the main function in your DLL and return when it does
    if (MyMain != nullptr)
      return MyMain (argc, argv);
  }

  return -1;
}

どちらのアプローチにもメリットがあります。

からスレッドを生成DllMainすると、ロードする DLL がどのように実装されているかをまったく知る必要がなくなりますが、main関数が決して戻らないように設計する必要もあります。DLL は を呼び出しますExitProcess (...)

関数をエクスポートし、後でそれらを名前でインポートすることで、Windows DLL ローダーロックを回避することができます。.defただし、ファイルを使用してエクスポートされたシンボルに明示的に名前を付けない場合、コンパイラは_...( __cdecl ) や...@n( __stdcall ) などの装飾を名前に追加しますGetProcAddress

于 2016-12-02T22:35:30.033 に答える