programing tip

런타임에 DLL 경로 가져 오기

itbloger 2021. 1. 10. 16:55
반응형

런타임에 DLL 경로 가져 오기


코드 내에서 dll의 디렉토리 (또는 파일) 경로 를 얻고 싶습니다 . (프로그램의 .exe 파일 경로가 아님)

내가 찾은 몇 가지 방법을 시도했습니다
GetCurrentDir.-현재 디렉토리 경로를 가져옵니다.
GetModuleFileName-실행 파일의 경로를 가져옵니다.

그렇다면 코드가 어떤 dll에 있는지 어떻게 알 수 있습니까?
C #과 비슷한 것을 찾고 있습니다.Assembly.GetExecutingAssembly


GetModuleHandleEx함수 를 사용하고 DLL의 정적 함수에 대한 핸들을 가져올 수 있습니다 . 여기에서 자세한 정보를 찾을 수 있습니다 .

그 후에 GetModuleFileName방금 얻은 핸들에서 경로를 가져 오는 데 사용할 수 있습니다 . 해당 통화에 대한 자세한 정보는 여기에 있습니다 .

완전한 예 :

char path[MAX_PATH];
HMODULE hm = NULL;

if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 
        GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
        (LPCSTR) &functionInThisDll, &hm) == 0)
{
    int ret = GetLastError();
    fprintf(stderr, "GetModuleHandle failed, error = %d\n", ret);
    // Return or however you want to handle an error.
}
if (GetModuleFileName(hm, path, sizeof(path)) == 0)
{
    int ret = GetLastError();
    fprintf(stderr, "GetModuleFileName failed, error = %d\n", ret);
    // Return or however you want to handle an error.
}

// The path variable should now contain the full filepath for this DLL.

EXTERN_C IMAGE_DOS_HEADER __ImageBase;

....

WCHAR   DllPath[MAX_PATH] = {0};
GetModuleFileNameW((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));

GetModuleFileName()DLL의 코드 내부에서 잘 작동합니다. NULL호출 프로세스의 파일 이름을 가져 오므로 첫 번째 매개 변수를로 설정하지 마십시오 . 대신 DLL의 실제 모듈 인스턴스를 지정해야합니다. DLL DllEntryPoint()함수 의 입력 매개 변수로 가져 오면 나중에 필요할 때 사용할 수 있도록 변수에 저장하면됩니다.


GetModuleFileName 함수를 사용해보십시오 .


다음은 가장 많이 투표 한 답변의 유니 코드 수정 버전입니다.

CStringW thisDllDirPath()
{
    CStringW thisPath = L"";
    WCHAR path[MAX_PATH];
    HMODULE hm;
    if( GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 
                            GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                            (LPWSTR) &thisDllDirPath, &hm ) )
    {
        GetModuleFileNameW( hm, path, sizeof(path) );
        PathRemoveFileSpecW( path );
        thisPath = CStringW( path );
        if( !thisPath.IsEmpty() && 
            thisPath.GetAt( thisPath.GetLength()-1 ) != '\\' ) 
            thisPath += L"\\";
    }
    else if( _DEBUG ) std::wcout << L"GetModuleHandle Error: " << GetLastError() << std::endl;

    if( _DEBUG ) std::wcout << L"thisDllDirPath: [" << CStringW::PCXSTR( thisPath ) << L"]" << std::endl;       
    return thisPath;
}

Delphi 사용자의 경우 :

SysUtils.GetModuleName(hInstance);              //Works; hInstance is a special global variable
SysUtils.GetModuleName(0);                      //Fails; returns the name of the host exe process
SysUtils.GetModuleName(GetModuleFilename(nil)); //Fails; returns the name of the host exe process

Delphi에 SysUtils.GetModuleName이없는 경우 다음과 같이 선언됩니다.

function GetModuleName(Module: HMODULE): string;
var
   modName: array[0..32767] of Char; //MAX_PATH is for a single filename; paths can be up to 32767 in NTFS - or longer.
begin
   {
      Retrieves the fully qualified path for the file that contains the specified module. 
      The module must have been loaded by the current process.
   }
   SetString(Result, modName, GetModuleFileName(Module, modName, Length(modName)));
end;

비슷한 기능을 하나의 .dll로 만들고 싶지만 __ImageBase를 사용할 수 없다는 점을 제외하면 비슷한 것을 달성하고 싶었습니다. 나는 접근 방식을 사용하여 재정의하려고 시도했습니다.

GetDllPath( HMODULE hDll = (HMODULE) __ImageBase)

그러나 그것은 우리도 효과가 없었습니다. (어떤 이유로 그 후에 응용 프로그램 경로를 반환합니다.)

그런 다음 나는 VirtualQuery를 사용하지 않고 함수 포인터를 사용하고 거기에서 HMODULE을 얻는 이유를 알아 냈습니다. 그러나 다시-호출자의 함수 포인터를 얻는 방법은 무엇입니까?

이제 호출 스택 결정으로 돌아갑니다. 모든 더러운 세부 사항으로 귀찮게하지 않고 참조 된 링크의 링크를 따르십시오.

다음은 전체 코드 스냅 샷입니다.

//
//  Originated from: https://sourceforge.net/projects/diagnostic/
//
//  Similar to windows API function, captures N frames of current call stack.
//  Unlike windows API function, works with managed and native functions.
//
int CaptureStackBackTrace2( 
    int FramesToSkip,                   //[in] frames to skip, 0 - capture everything.
    int nFrames,                        //[in] frames to capture.
    PVOID* BackTrace                    //[out] filled callstack with total size nFrames - FramesToSkip
)
{
#ifdef _WIN64
    CONTEXT ContextRecord;
    RtlCaptureContext(&ContextRecord);

    UINT iFrame;
    for (iFrame = 0; iFrame < (UINT)nFrames; iFrame++)
    {
        DWORD64 ImageBase;
        PRUNTIME_FUNCTION pFunctionEntry = RtlLookupFunctionEntry(ContextRecord.Rip, &ImageBase, NULL);

        if (pFunctionEntry == NULL)
        {
            if (iFrame != -1)
                iFrame--;           // Eat last as it's not valid.
            break;
        }

        PVOID HandlerData;
        DWORD64 EstablisherFrame;
        RtlVirtualUnwind(0 /*UNW_FLAG_NHANDLER*/,
            ImageBase,
            ContextRecord.Rip,
            pFunctionEntry,
            &ContextRecord,
            &HandlerData,
            &EstablisherFrame,
            NULL);

        if(FramesToSkip > (int)iFrame)
            continue;

        BackTrace[iFrame - FramesToSkip] = (PVOID)ContextRecord.Rip;
    }
#else
    //
    //  This approach was taken from StackInfoManager.cpp / FillStackInfo
    //  http://www.codeproject.com/Articles/11221/Easy-Detection-of-Memory-Leaks
    //  - slightly simplified the function itself.
    //
    int regEBP;
    __asm mov regEBP, ebp;

    long *pFrame = (long*)regEBP;               // pointer to current function frame
    void* pNextInstruction;
    int iFrame = 0;

    //
    // Using __try/_catch is faster than using ReadProcessMemory or VirtualProtect.
    // We return whatever frames we have collected so far after exception was encountered.
    //
    __try {
        for (; iFrame < nFrames; iFrame++)
        {
            pNextInstruction = (void*)(*(pFrame + 1));

            if (!pNextInstruction)     // Last frame
                break;

            if (FramesToSkip > iFrame)
                continue;

            BackTrace[iFrame - FramesToSkip] = pNextInstruction;
            pFrame = (long*)(*pFrame);
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

#endif //_WIN64
    iFrame -= FramesToSkip;
    if(iFrame < 0)
        iFrame = 0;

    return iFrame;
} //CaptureStackBackTrace2



//
//  Gets .dll full path or only directory.
//
CStringW GetDllPath( bool bPathOnly /* = false */ )
{
    void* pfunc = &GetDllPath;
    wchar_t path[MAX_PATH] = { 0 };
    MEMORY_BASIC_INFORMATION info;
    HMODULE hdll;

    CaptureStackBackTrace2(1, 2, &pfunc);

    // Get the base address of the module that holds the current function
    VirtualQuery(pfunc, &info, sizeof(MEMORY_BASIC_INFORMATION));

    // MEMORY_BASIC_INFORMATION::AllocationBase corresponds to HMODULE
    hdll = (HMODULE)info.AllocationBase;

    // Get the dll filename
    if ( !GetModuleFileName( hdll, path, MAX_PATH ) )
        return L"";

    if ( bPathOnly )
    {
        wchar_t* p = wcsrchr( path, '\\' );
        if ( p )
            *p = 0;
    }

    return path;
} //GetDllPath

다음 dll 진입 점을 구현 한 경우 : (일반적으로 dllmain.cpp)

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

간단하게 다음을 수행 할 수 있습니다.

switch (ul_reason_for_call)
{ 
case DLL_PROCESS_ATTACH:
{
    TCHAR dllFilePath[512 + 1] = { 0 };
    GetModuleFileNameA(hModule, dllFilePath, 512)
}
    break;
case DLL_THREAD_ATTACH: break;
...

그러면 dllFilePath에 현재 dll 코드가로드 된 경로가 포함됩니다. 이 경우 hModule은 dll을로드하는 프로세스에 의해 전달됩니다.


Imho, Remy Lebau’s answer is the best, but lacks like all other answers to render the directory of the DLL. I quote the original question: “I want to get a dll's directory (or file) path from within its code. (not the program's .exe file path).” I will develop two functions inside the DLL, the first will return its fully qualified name, the second its fully qualified path. Assume the fully qualified name of the DLL is “C:\Bert\Ernie.dll”, then the functions return “C:\Bert\Ernie.dll” and “C:\Bert” respectively. As Remy and Jean-Marc Volle pointed out, the DLL entry function DllMain usually contained in dllmain.cpp provides the handle to the DLL. This handle is often necessary, so it will be saved in a global variable hMod:

HMODULE hMod;
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
  switch (ul_reason_for_call)
  {
    case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH:
       case DLL_PROCESS_DETACH:
     break;
  }
  hMod = hModule;
  return TRUE;
}

Now in the file TestDll.cpp I define the function GetFullyQualifiedNameOfDLL(wchar_t* PathFile) that renders the fully qualified name, in our example “C:\Bert\Ernie.dll” and the function GetFullyQualifiedPathToDLL(wchar_t * Path) returning only the path, here “C:\Bert”

// TestDll.cpp : Defines the exported functions for the DLL application.
#include "stdafx.h"

extern HMODULE hMod;
extern "C"  __declspec(dllexport) DWORD _stdcall GetFullyQualifiedNameOfDLL(wchar_t * PathFile)
{
  return ::GetModuleFileNameW(hMod, PathFile, MAX_PATH);
}

extern "C"  __declspec(dllexport) DWORD _stdcall GetFullyQualifiedPathToDLL(wchar_t * Path)
{
  wchar_t PathFile[MAX_PATH];

  if (GetFullyQualifiedNameOfDLL(PathFile) == 0)
  {
    return 0;
  }

  wchar_t *pszFileName;
  DWORD FLen = ::GetFullPathNameW(PathFile, MAX_PATH, Path, &pszFileName);
  if (FLen == 0 || FLen >= MAX_PATH)
  {
    return 0;
  }

  *pszFileName = 0;
  return 1;
}

These functions can be used inside the DLL. A user of this DLL can call these functions as follows:

void _tmain(int argc, TCHAR *argv[])
{
  wchar_t PathFile[MAX_PATH], Path[MAX_PATH];
  GetFullyQualifiedNameOfDLL(PathFile);
  wprintf(L"Fully qualified name: %s\n", PathFile);

  //Split the fully qualified name to get the path and name
  std::wstring StdPath = PathFile;
  size_t found = StdPath.find_last_of(L"/\\");
  wprintf(L"Path: %s, name: %s\n", StdPath.substr(0, found).c_str(), StdPath.substr(found + 1).c_str());

  GetFullyQualifiedPathToDLL(Path);
  wprintf(L"Path: %s\n", Path);
}

HMODULE hmod = GetCurrentModule();
TCHAR szPath[MAX_PATH + 1] = 0;
DWORD dwLen = GetModuleFileHName(hmod, szPath, MAX_PATH);

ReferenceURL : https://stackoverflow.com/questions/6924195/get-dll-path-at-runtime

반응형