我如何在运行时 "start" 像可执行文件一样的 DLL?
How can I "start" a DLL like an executable at runtime?
我想编写一个非常非常小的程序来解析启动参数并从几个 DLL 中选择一个 "boot into."
我已经编写了一个应用程序,我希望将其 "run" 作为 DLL 编写,方法是将其编写为应用程序,然后更改 Visual Studio 项目属性以将其构建为 DLL .我知道我需要同时使用 LoadLibrary 和 GetProcAddress 来获得我想要的功能,但我很难找到关于此的清晰而全面的文档,因为很多用例并不是真正的这种性质。另外,我必须根据项目和平台限制走这条路。
我找到了this page,里面有一些信息,但是不够清楚,我要适应我的目的。
编辑:这是我现在所在的位置。
我有一个 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 无效。
您不必使用LoadLibrary
和GetProcAddress
来调用DLL中的功能。
更常见的是,您会创建自己的 DLL,每个 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;
}
}
当你 link 你的 main 时,你将指定对应于每个 DLL 的 .lib
,并且你可能想要将 /delayload
标志指定给 link呃。这意味着在实际调用 DLL 中的函数之前不会加载 DLL。例如,如果您想分发仅包含 DLL A 的程序的缩减功能版本,它仍然可以 运行(用户系统上不存在 DLL B 或 C)只要不曾经调用过 DLL B 或 C 中的函数。如果你不指定/delayload
,加载器会在程序启动时尝试将所有的DLL映射到RAM,运行它们的DllMain
来初始化它们以供使用,递归地做同样的事情对于它们所依赖的所有 DLL,等等
/delayload
还有一个优点:它避免将其他 DLL 映射到从未使用过的地址。听起来任何给定的调用都只会使用一个 DLL,所以这对你来说可能是一个胜利。
首先,将项目类型从可执行文件更改为DLL 不足以制作DLL。您还需要导出一些符号来创建您的 API。至少,你需要用 __declspec(dllexport)
修饰你导出的函数。但是,我建议您导出 C API,即具有 C
兼容参数的 extern "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 **);
您 不需要 需要 GetProcAddress (...)
来执行此操作,尽管一旦您了解了编译器如何生成符号名称,这种方法(选项 #2)就会更简单。
Option #1
DllMain Spawns a Main Thread
Never do anything complicated inside of DllMain, you may deadlock your software.
DLL 有自己的入口点(以及出口点和线程附加点……这是一个非常繁忙的功能)。仅在您的 DLL 上调用 LoadLibrary (...)
至少会调用一次 DllMain (...)
以进行进程附加。
BOOL
APIENTRY
DllMain ( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
您实际上可以将 ul_reason_for_call == DLL_PROCESS_ATTACH
视为执行 DllMain 的授权,就好像它是您程序的主要功能一样。
现在,你绝对不应该在这里真正开始一个程序循环...任何时候 DllMain
运行它持有一个非常重要的操作系统锁(DLL 加载器),你需要在 [=81] 之前释放它=]正常程序运行。
这意味着如果您想使用 DllMain
作为程序的入口点,它需要生成一个线程,并且您的原始 main
方法在该线程完成之前不能 return ...
Option #2
DLL Exports a main
Function.
Be very mindful of calling convention, the compiler will rename the symbols for you and make locating functions in a DLL with GetProcAddress
less than intuitive.
在你的 DLL 中,export 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
函数设计为永远不会 return -- DLL 将调用 ExitProcess (...)
.
导出函数并稍后按名称导入它们可以避免在 Windows DLL 加载器锁上犹豫不决。但是,如果您不使用 .def
文件显式命名导出的符号,编译器将添加诸如 _...
(__cdecl) 或 ...@n
(__stdcall) 的名称,您必须学习这些约定才能使用 GetProcAddress
.[=33= 做任何有用的事情]
我想编写一个非常非常小的程序来解析启动参数并从几个 DLL 中选择一个 "boot into."
我已经编写了一个应用程序,我希望将其 "run" 作为 DLL 编写,方法是将其编写为应用程序,然后更改 Visual Studio 项目属性以将其构建为 DLL .我知道我需要同时使用 LoadLibrary 和 GetProcAddress 来获得我想要的功能,但我很难找到关于此的清晰而全面的文档,因为很多用例并不是真正的这种性质。另外,我必须根据项目和平台限制走这条路。
我找到了this page,里面有一些信息,但是不够清楚,我要适应我的目的。
编辑:这是我现在所在的位置。
我有一个 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 无效。
您不必使用LoadLibrary
和GetProcAddress
来调用DLL中的功能。
更常见的是,您会创建自己的 DLL,每个 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;
}
}
当你 link 你的 main 时,你将指定对应于每个 DLL 的 .lib
,并且你可能想要将 /delayload
标志指定给 link呃。这意味着在实际调用 DLL 中的函数之前不会加载 DLL。例如,如果您想分发仅包含 DLL A 的程序的缩减功能版本,它仍然可以 运行(用户系统上不存在 DLL B 或 C)只要不曾经调用过 DLL B 或 C 中的函数。如果你不指定/delayload
,加载器会在程序启动时尝试将所有的DLL映射到RAM,运行它们的DllMain
来初始化它们以供使用,递归地做同样的事情对于它们所依赖的所有 DLL,等等
/delayload
还有一个优点:它避免将其他 DLL 映射到从未使用过的地址。听起来任何给定的调用都只会使用一个 DLL,所以这对你来说可能是一个胜利。
首先,将项目类型从可执行文件更改为DLL 不足以制作DLL。您还需要导出一些符号来创建您的 API。至少,你需要用 __declspec(dllexport)
修饰你导出的函数。但是,我建议您导出 C API,即具有 C
兼容参数的 extern "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.
编辑:如果您这样更改代码示例,它就会起作用
extern "C" __declspec(dllexport) int main(int argc, char *argv[]){...}
typedef int (* LPFNDLLFUNC1)(int, char **);
您 不需要 需要 GetProcAddress (...)
来执行此操作,尽管一旦您了解了编译器如何生成符号名称,这种方法(选项 #2)就会更简单。
Option #1
DllMain Spawns a Main Thread
Never do anything complicated inside of DllMain, you may deadlock your software.
DLL 有自己的入口点(以及出口点和线程附加点……这是一个非常繁忙的功能)。仅在您的 DLL 上调用 LoadLibrary (...)
至少会调用一次 DllMain (...)
以进行进程附加。
BOOL
APIENTRY
DllMain ( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
您实际上可以将 ul_reason_for_call == DLL_PROCESS_ATTACH
视为执行 DllMain 的授权,就好像它是您程序的主要功能一样。
现在,你绝对不应该在这里真正开始一个程序循环...任何时候 DllMain
运行它持有一个非常重要的操作系统锁(DLL 加载器),你需要在 [=81] 之前释放它=]正常程序运行。
这意味着如果您想使用 DllMain
作为程序的入口点,它需要生成一个线程,并且您的原始 main
方法在该线程完成之前不能 return ...
Option #2
DLL Exports a
main
Function.Be very mindful of calling convention, the compiler will rename the symbols for you and make locating functions in a DLL with
GetProcAddress
less than intuitive.
在你的 DLL 中,export 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
函数设计为永远不会 return -- DLL 将调用 ExitProcess (...)
.
导出函数并稍后按名称导入它们可以避免在 Windows DLL 加载器锁上犹豫不决。但是,如果您不使用 .def
文件显式命名导出的符号,编译器将添加诸如 _...
(__cdecl) 或 ...@n
(__stdcall) 的名称,您必须学习这些约定才能使用 GetProcAddress
.[=33= 做任何有用的事情]