介绍
,特别是当通过由供应商提供的API控制硬件,也试图避免由于没有系统上可用的某些DLL的应用程序启动过程中的错误时,动态加载的DLL是一种常见的技术。
链接到一个DLL从C不同的方式,已在本网站上的几篇文章。对于那些不完全熟悉的主题,我推荐的文章{A}。这种包装除了避免重复代码,允许更复杂的错误检查。
然而,函数的声明仍然必须从DLL的头提取,保持最新,并访问API的代码需要改变。另一方面所谓的延迟加载并不需要修改任何代码或额外的代码,但它缺乏灵活性,动态加载。
本文提出了一种新的包装类型,规定只有很少的代码更改之间切换动态和静态载荷,但仍提供的所有功能,现有的DLL,包装和更多。背景为什么要使用动态加载?
列出了几个很好的动态加载的原因可能是有用的,甚至是必要的} {A2的,其中大部分是7年后仍然有效。除了有上市的原因,我也遇到过,我需要链接到不同版本的DLL,在应对一些用户输入或根据我访问和硬件版本,相信它或没有,我在那里加载的情况DLL,根据不同的名称两次,以便能够访问两片完全相同的硬件。延迟加载的救援?
延迟加载还详细介绍了其他地方。它在本质上是动态加载过程,它会自动链接器生成所需的代码。这是,如果你想,一个连接器生成的DLL包装。事实上,它需要照顾一些其他两种方法的头痛:没有更改代码是必要的,因为延迟加载的DLL只是一个连接器开关和启用静态加载不同的是,您的应用程序仍然会启动,即使它链接到一个图书馆,这是不是对现行制度或目前进口的象征,这是从可用版本的DLL失踪。从你的代码在运行时访问时,现有的符号被加载。
不利的一面,没有额外的错误检查使用时,您的应用程序崩溃,尽快为您尝试来访问,是不是加载的DLL中(但导入库的一部分)的符号,或如果该DLL可以不被加载当第一个符号访问。以确保不会发生这种情况,你可以调用_ HrLoadAllImportsForDll(看到delayimp.h但小心:LoadLibrary的是大小写不敏感,但这个API)。手动和禁用加载的情况下,并不是所有的符号,可以使用该DLL,或者你可以尝试设置钩子,加载符号失败时,被称为实现更细粒度的错误检查。但是,这涉及到调用的API GetProcAddress函数()比复杂得多,因此编写复杂的代码(不必要的)。
什么是用传统的方式错了吗?
没有。它仅仅是不好的,需要很多的剪切和粘贴,搜索和替换,它很容易出错,尤其是当API的变化是同步DLL(或,如果混合起来的供应商船舶头)。此外,它要求你改变你的代码时,从静态切换到动态链接。
演示代码,包括必要的代码,在您的应用程序中动态加载PSAPI.DLL和说明了这一点。理想的情况下,您将创建一个头文件,说psapi_dynamic.h包含必要的typedef和函数指针的声明。它还包含的声明一个函数,加载的DLL和符号,load_psapi():#ifndef _PSAPI_DYNLINK_H_
#define _PSAPI_DYNLINK_H_
#include <psapi.h>
typedef BOOL
(WINAPI *
EnumProcesses_t)(
__out_bcount(cb) DWORD * lpidProcess,
__in DWORD cb,
__out LPDWORD lpcbNeeded
);
typedef BOOL
(WINAPI *
EnumProcessModules_t)(
__in HANDLE hProcess,
__out_bcount(cb) HMODULE *lphModule,
__in DWORD cb,
__out LPDWORD lpcbNeeded
);
...
extern EnumProcesses_t pEnumProcesses;
extern EnumProcessModules_t pEnumProcessModules;
...
// Try to load the psapi.dll
// returns
// 0 if library was not found,
// 1 if all symbols were loaded and
// 2 if some symbols are NULL
int psapi_load();
如果你是用正则表达式,这个头文件可以从psapi.h建立在几分钟之内。这是一个好主意,以维持多的格式和声明的顺序,因为它是在原来的头。这允许您检查和更新您的typedef一个很好的文件比较工具,使得该解决方案相当维护。
共有,每个符号显示在动态加载代码四次:一次作为一个typedef,一旦在函数指针声明,并两次在相应的cpp文件中,函数指针定义和符号被加载在load_psapi()函数:{C}
添加一个新的/一个函数的名称改变或删除,因此需要编辑在四个不同的地方。的typedef编辑列表中的第一,这是一个好主意,编译器将提醒您其余的工作要做。
在您的代码,你只需更换DLL中的所有函数调用,并在前面加上"P"或任何前缀,你选择了给你的函数指针。...
// Try to load the library
if (0 == psapi_load())
return;
// Check whether the symbol was loaded
if (! pGetProcessImageFileNameW)
return;
TCHAR sName1[1024];
pGetProcessImageFileNameW(GetCurrentProcess(), sName1, 1024);
...
如果我们继续一些符号,即使没有加载上述,我们最好不要忘记检查是否pGetProcessImageFileW是NULL。虽然这些额外的检查和函数(指针)名称的变化使动态加载永久的过渡,该方法提供了许多优势超过延迟或静载:检查文件和读出DLLl版本,以便找出哪些职能是安全的打电话,你可以在运行时检查的可用性和启用或禁用基于结果的功能。DLL的包装
但是,如果你要产生有意义的错误信息或不加载这些功能或变量的默认功能下降(存根),你很快开始编写代码动态加载每个库的同类。这项计划的任何增强,然后手动添加为每个库(或者,更可能)函数和变量存根,需要很多额外的编码。
宏和包装的要求,这是什么DLL的包装做的。在上述{A3},你可以键入类似:{体C3}
一些头psapi_lateload.h。请再次注意,我们一直尽可能的原始格式多,所以我们可以稍后检查,并迅速更新的文件。调用到DLL,现在通过包装对象发生:{的C4}
此包装将每一个功能和Is_EnumProcesses存根()检查是否到位原有的功能或存根。
虽然这是更方便使用较简单的动态加载,它带有一些附加条件。首先,创造psapi_lateload.h是远远超过了之前的参数名,因为必须要除去psapi_dynamic.h工作。取决于使用的宏观参数的数量和必须由专人选择。此外,每个文件在从PSAPI.DLL功能的用于维护自己的包装对象和驻留在整个函数的定义(许多版本)宏,将功能添加到包装涉及到很多代码被写入。
更重要的是,同样的代码之间的隐式和显式负荷开关时,要改变。虽然这似乎是一个小点,它可以是一个相当秀塞,如果,例如,宏的存在依赖于函数名保持不变。作为一个例子,TCHAR的职能psapi.h的版本,可以不再使用搞乱#IFDEF UNICODE的结果... #其他... #ENDIF节,无论你调用函数的字符串参数。dynlink包装
当然,所有的问题提到动态加载以上,或没有现有的包装是没有理由编写一个新的。
我可以很容易地包裹和更新,我可能在任何时间很快,到了本文中的代码的总时间动态加载所有DLL。但它肯定是更多的乐趣,把它写比写一遍又一遍相同的代码动态加载代码。
dynlink的包装有比现有的解决方案具有以下优点:它不需要静态加载的DLL编写的现有代码的变化。因此,它鼓励你切换到动态加载,使您的支持,例如,有一定的SDK的不同版本。因此,它可以让静态和动态连接通过改变单一的#define之间切换。这使得不可用符号显示在链接时,并有助于保持你的代码和DLL同步。它可用于整个一个大的项目没有任何开销。一个单一的搜索和替换头文件是足够的大多数API。你再维持原头用最小的变化,用一个简单的交互式文件比较工具,这是很容易更新版本改变。在运行时,它提供了丰富的错误信息。切换到静态链接时,所有的错误检查是成功的,允许你添加错误检查代码,即使你加载方法之间切换。使用代码
使用的包装包括三个步骤:准备修改后的头
复制原头,例如,psapi.h到psapix.h,添加宏定义库的名称,包装与DYNLINK_DECLARE或DYNLINK_DECLARE_EX宏的所有功能和变量声明。这需要采用传统的方式类似的搜索和替换技能。的psapix.h开始看起来像这样:{C5的}
的正则表达式来改变声明的评论。如果你想加载库,根据不同的名字几次,你必须投入更多一点的工作:"DYNLINK_DECLARE宏已被超出#:IFNDEF _PSAPI_H_(最好不动,声明周围的保护部分,所以我们可以保持密切原来的格式和头文件的结构)。见例如psapixx.h。链接到dynlink.lib
链接到dynlink.lib。或者,您可以定义DYNLINK_NO_AUTO(或删除的#pragma注释(LIB,";) dynlink.libquot从dynlink.h)将dynlink.cpp添加到您的项目。加入负载和错误检查代码
在所有情况下最简单的,你调整你的include语句和符号被定义选择一个文件。在这里,您添加:{5233}
包括修改后的头文件之前。
此外,有几个宏修改包装的行为。他们被描述在dynlink.h和允许定制DLL包裹方式。DYNLINK_PREFIX:前缀的所有功能和变量,例如,为了加载几次相同的库。DYNLINK_USE_STUBS:无论以取代存根不可用的功能。DYNLINK_LINK_IMPLICIT,和DYNLINK_LINK_EXPLICIT:确定加载方法,确切地说是其中之一时,应定义包括dynlink.h。
您可以连接不同的方法和前缀不同的库,但相同的定义必须到位,随处可见,包括具有相同前缀的修改后的头。对于每一个前缀使用,DYNLINK_DEFINE完全被定义在一个文件中时,修改后的头球攻门被列入。
当模式是DYNLINK_LINK_EXPLICIT的,所有的符号都包裹正是因为它包装的函数或变量名为到从CDynlinkSymbol派生对象。该对象可以隐式转换到正确的函数指针或变量引用类型,所以没有代码有改变,只要你保证,无论是存根使用或调用只加载符号。
在同一时间,你可以调用当然成员在CDynlinkSymbol定义功能,这是相当自我解释:{C7-}
当直接调用这些代码将停止工作时切换回静载功能,因为没有成员函数。如果你想你的代码保持有效的两种方法,使用的CDynlinkGetError的类访问错误和状态信息:{C8的}
装载静态时,所有的功能会提示成功加载。 get_name()和get_lib()不静载。
装载和维护库沙爹的代码包含在类型CDynlinkLib的对象,允许微调的行为://! Interface class for dexplicit and implicit linking and loading of dlls
//! and contained symbols.
class CDynlinkLib
{
friend class CDynlinkSymbol;
public:
//! Mode of loading
enum mode
{
//! Explicit loading of function addresses
ex,
//! Explicit loading with stubs
exstub,
//! Implicit loading.
imp
};
//! Create a library interface.
//! @param sName
//! The name of the library. (e.g. psapi for psapi.dll)
//! @param d_mode
//! The mode (see above).
CDynlinkLib(LPCWSTR sName, mode d_mode = ex);
CDynlinkLib(LPCSTR sName, mode d_mode = ex);
//! Get error during library load
//! @param s_err
//! The string to write a formatted error message to
//! @return
//! The error as returned by GetLastError() during the load_library call.
DWORD get_error() const
{
return m_dwError;
}
//! If set to true defined, accessing a variable or function not loaded
//! will cause a c++ exception to be thrown. In fact, the symbol throws a
//! pointer to itself so you could wrap your code accessing the lib into
//! try
//! {
//! code;
//! }
//! catch (CDynlinkSymbol * p)
//! {
//! retrive and display error information;
//! }
//! REMARK: this will also occur if you just check the symbol against NULL. If
//! this is enabled use the is_loaded() function instead to avoid throwing an
//! exception.
#ifndef DYNLINK_NO_THROW
void set_throw(bool b_throw)
{
m_b_throw = b_throw;
}
#endif
//! Load the library and all symbols. For details see above.
//! @param bRetry
//! Retry the load independently of previous results. If not TRUE
//! a loaded library and its symbols will not be touched.
//! @param sName
//! Replace the library name given above.
//! @return
//! State of the loading, see definitions of DYNLINK_ERR ... above
int load(LPCWSTR sName = 0, BOOL bRetry = FALSE);
int load(LPCSTR sName, BOOL bRetry = FALSE);
//! Get loading state.
//! @return
//! State of the loading, see definitions of DYNLINK_ERR ... above
int get_state() const
{
return m_dLoaded;
}
//! Get the loading mode of the library object. See CDynlinkLib::mode for
//! details.
//! @return
//! the mode with which the library was loaded.
mode get_mode() const
{
return m_mode;
}
//! Check whether the library was loaded successfully.
//! @return
//! true if get_state() is DYNLINK_SUCCESS
bool is_loaded() const
{
return (get_state() == DYNLINK_SUCCESS);
}
//! Check whether all symbols can be called in the library without NULLPTR
//! references.
//! @return
//! true if get_state() is either DYNLINK_SUCCESS or DYNLINK_ERR_STUBS
bool is_loaded_or_stub() const
{
return (get_state() == DYNLINK_SUCCESS) || (get_state() == DYNLINK_ERR_STUBS);
}
//! Unload the library. This will fail for an implicitely linked library
//! but should succeed for explicite linkage and delayed loading.
bool free();
//! Add a symbol.
//! @param p_sym
//! Symbol to load as part of this library module
void add(CDynlinkSymbol * p_sym);
//! Get the module handle for a successfully loaded library. NULL if the
//! library was not loaded.
//! REMARK: when implicitely linking, this may return NULL even if the
//! library was successfully loaded but if the module name does not match
HMODULE get_module() const
{
return m_hDll;
}
//! Get the filename of the library loaded. Can fail for implicitely linked
//! libs if the library name is not correct
BOOL get_filename(dynlink_string_w &s_fn) const;
//! Get the filename of the library loaded.
BOOL get_filename(dynlink_string_a &s_fn) const;
protected:
...
};
再次,大部分是不言自明。第一次访问的自动负载(还)没有实现,但可以很容易地在同样的check_crash()功能,它允许配置库等类型CDynlinkSymbol *异常被抛出,如果卸载符号访问。这是示范项目的一部分DynlinkTest应用演示了目前实施的所有选项。请指test_dynlink.cpp。你可以注释/评论的#定义stdafx.h中DYNLINK_LINK_EXPLICIT的的在以之间切换动态和静态载荷。
演示项目还包含了上面介绍的可能是一个很好的起点,即使你决定是不是你的dynlink包装的其他两个方法的代码。它是如何工作
类是微小的,其实施或多或少简单。而宏定义初看起来令人费解,他们是无害的,太:
基本上,DYNLINK_DECLARE得到函数的名称,返回类型,调用约定,参数列表分开。如果明确的连接被启用,它定义(或声明如果DYNLINK_DEFINE没有被定义)一个型CDynlinkFunclt对象; TGT;或CDynlinkVarlt; TGT;模板参数是函数指针或变量的类型,分别。这些类都必须转换运算符太,允许他们使用同样的方式非常原始符号定义在头文件:
变量的类CDynlinkVar需要在构造函数中的存根可选的初始值:
和功能类需要一个可选函数指针作为存根使用:{C11的}
的设计是比每个符号的LateLoad包装设计的一个成员函数更复杂。这主要是由于我的愿望,允许超过一个或几个头文件,这迫使我们每个符号的定义封装成一个对象的定义,增加了施工期间CDynlinkLib对象分散的声明。库对象,例如,循环在运行时它的符号和功能,可以添加两个符号的基类,并没有任何改变有关,更容易出错的宏定义的库对象的方法的优点。