windows的加载dll
隐式加载
- 寻找 dll由系统控制
- 一般会在exe同级目录放着
这个一般没有太多坑,所以,可以慢慢探索
显式加载
- 加载路径自由选择
注意
踩过的坑:
- dll 内容没有改变
- 如果网络传输过,最好打包传输
- dll 加载失败:126
126真的一直在报。
- 这种有几种原因: 原因1: 自己路径不对
- 如果不是自己的路径不对:就这个dll还依赖其他的dll
- 如果其他dll都全了,但是依然还是126。
- 就需要设定工作目录。 这是windows本身的机制问题。
- windows搜索工作目录会先在工作目录下搜索看看能不能找到。
稳妥做法:
- 将加载的dll 以及 它依赖的 dll 全部放在一个 工作目录中
- load的时候 一定要 设定工作目录(绝对路径)
显式加载的小例子
这个例子基于原来封装某厂商SDK的时候,自己在家写的小栗子。可能很捡漏。
1. 载入dll
头文件声明
// type
# ifdef _WIN32
using Handler = HMODULE;
using FuncPtr = FARPROC;
#endif
class CIMOSLoader
{
public:
CIMOSLoader(const std::string& strName);
virtual ~CIMOSLoader();
bool Load();
bool UnLoad();
bool GetFunctionPtr(const std::string& strFunc, FuncPtr& ptrFuc);
bool GetFunctionPtr(const char* strFunc, FuncPtr& ptrFuc);
inline bool IsLoad() const { return (m_handler != nullptr); }
private:
bool loadLibrary();
bool unloadLibrary();
private:
std::string m_Name;
Handler m_handler;
unsigned int m_error;
};
载入dll 的实现
- 最好传进去是绝对路径
- 备份和回复工作目录的原因:
- 为了载入不同路径的dll,(打开你的游戏目录下就看到了各种目录里的dll)
bool CIMOSLoader::loadLibrary()
{
std::string str = m_Name;
//备份当前工作目录
char szCurDir[MAX_PATH] = { 0 };
GetCurrentDirectory(sizeof(szCurDir), szCurDir);
int nLen = str.length();
auto pos = str.find_last_of('\\');
std::string substr = str.substr(0, pos+1);
SetCurrentDirectory(substr.c_str());
m_handler = LoadLibrary(str.c_str());
if (nullptr == m_handler)
{
auto Error = GetLastError();
m_error = Error;
}
//恢复当前工作目录
SetCurrentDirectory(szCurDir);
return m_handler != nullptr;
}
获取函数指针
下面的也很简单,主要就是调用windows的GetProcAddress
弄了个重载,主要是为了以后用起来方便
bool CIMOSLoader::GetFunctionPtr(const std::string& strFunc, FuncPtr& ptrFuc)
{
return GetFunctionPtr(strFunc.c_str(), ptrFuc);
}
bool CIMOSLoader::GetFunctionPtr(const char* strFunc, FuncPtr& ptrFuc)
{
if (!IsLoad())
{
return false;
}
FuncPtr pFun = GetProcAddress(m_handler, strFunc);
if (pFun == nullptr)
{
return false;
}
ptrFuc = pFun;
return true;
}
上面就是主要的了,其他的东西就是比较简单的,一并放出来,有种水字数的感觉,还是慢慢水一波
//卸载dll
bool CIMOSLoader::unloadLibrary()
{
return FreeLibrary((HMODULE)m_handler) != 0;
}
CIMOSLoader::~CIMOSLoader()
{
UnLoad();
}
bool CIMOSLoader::Load()
{
if (IsLoad())
{
UnLoad();
}
if (m_Name.empty())
{
return false;
}
return loadLibrary();
}
bool CIMOSLoader::UnLoad()
{
bool ret = true;
if (IsLoad())
{
ret = unloadLibrary();
if (ret)
{
m_handler = nullptr;
}
}
return ret;
}
2. 使用loader获取函数指针
显式获取函数指针之后,就能使用了。所以我们可以封装一层 函数指针,让我们用起来比较简单点。
声明
//声明自己的函数指针 这边的是sdk的
using PFunc_Initiate = decltype(IMOS_Initiate)*;
using PFunc_SetOption = decltype(IMOS_SetOption)*;
using PFunc_CleanUp = decltype(IMOS_CleanUp)*;
// ...... 下面实际上还要加很多,
class IMOSFuntor
{
public:
IMOSFuntor(const std::string& strSDKName, const std::string& strXPName);
~IMOSFuntor();
bool Init();
bool InitSDK_Func();
private:
std::unique_ptr<CIMOSLoader> m_pImosSdk;
public:
PFunc_Initiate m_pInitiate{ nullptr };
PFunc_SetOption m_pSetOption{ nullptr };
PFunc_CleanUp m_pCleanUp{ nullptr };
PFunc_Encrypt m_pEncrypt{ nullptr };
// 上面声明多少,这里定义多少
获取函数指针
这里获取指针的时候,我就直接用了一个map,用了指针的指针?(主要我是不想一个一个判断) 其实你也可以直接在声明用map,
bool IMOSFuntor::InitSDK_Func()
{
m_pImosSdk->Load();
if (!m_pImosSdk->IsLoad())
{
return false;
}
std::map<std::string, FuncPtr*> mapFunc;
{
mapFunc.insert(std::make_pair("IMOS_Initiate", reinterpret_cast<FuncPtr*>(&m_pInitiate)));
mapFunc.insert(std::make_pair("IMOS_SetOption", reinterpret_cast<FuncPtr*>(&m_pSetOption)));
mapFunc.insert(std::make_pair("IMOS_CleanUp", reinterpret_cast<FuncPtr*>(&m_pCleanUp)));
mapFunc.insert(std::make_pair("IMOS_Encrypt", reinterpret_cast<FuncPtr*>(&m_pEncrypt)));
}
for (auto it = mapFunc.begin(); it != mapFunc.end(); ++it)
{
if (!m_pImosSdk->GetFunctionPtr(it->first, *(it->second)))
{
printf("IMOSFuntor.Init: 获取 %s 地址失败.\n", it->first.c_str());
return false;
}
}
return true;
}
然后就能直接用IMOSFuntor
里面的函数指针了。这样分割在多层,主要是,可能会跨平台,如果跨平台的话,就该更底层的loader。