dll动态加载与卸载

这里的动态加载指的是:程序编译时不需要任何dll相应的lib进行链接,程序本身通过相关函数加载和卸载dll,并使用其中的函数。
原理如下:动态库导出一个函数newInstance,该函数返回一个虚基类B指针,指针指向类D对象,动态库内的类D继承于B,并重写了相关方法。动态加载时,外部通过LoadLibraryEx获取句柄,通过GetProcAddress方法传入句柄和newInstance字符串得到newInstance方法地址(函数指针),然后调用该方法得到指向D对象的虚基类指针。调用虚基类的方法,最终调用D类内重写的方法。

代码如下:

//MyPlugin.cpp
IPlugin* newInstance()
{
    return new MyPlugin();
}
//IPlugin.h
class IPlugin
{
    Q_OBJECT

public:
    virtual ~IPlugin() {};
    virtual void doSomething() = 0; 
}
//MyPlugin.h
extern "C" PLUGIN_EXPORT IPlugin* newInstance();
extern "C" PLUGIN_EXPORT void deleteInstance(IPlugin* instance);

class MyPlugin : public IPlugin
{
public:
    UnityPlugin();
    ~UnityPlugin();
    
    virtual void doSomething() override;
}
class PluginManager
{
public:
    static bool loadPlugin(const QString& pluginPath, PluginInstanceInfo& pluginInfo);
    static bool freePlugin(PluginInstanceInfo& pluginInfo);
};
#include "PluginManager.h"

//句柄和类对象信息保存在pluginInfo引用中
bool PluginManager::loadPlugin(const QString& pluginPath, PluginInstanceInfo& pluginInfo)
{
    const char* const newInstanceName = "newInstance";
    QString filepath = pluginPath;
    filepath.replace("/", "\\");
    QByteArray ba = filepath.toLocal8Bit();
    const char* s = ba.data();
    HMODULE hDll = ::LoadLibraryEx(s, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

    if (!hDll)
    {
        LT_ERROR(QString("[cannot load dll]%1 %2").arg(filepath).arg(GetLastError()));
        return false;
    }

    typedef IPlugin*(*FunNewInstance)();//函数指针类型别名
    FunNewInstance newInstance = (FunNewInstance)GetProcAddress(hDll, newInstanceName);

    if (!newInstance)
    {
        LT_ERROR(QString("[cannot getProcAddress][path]%1[procName]%2").arg(pluginPath).arg(newInstanceName));
        return false;
    }

    pluginInfo.hDll = hDll;
    pluginInfo.instance = newInstance();
    return true;
}

bool PluginManager::freePlugin(PluginInstanceInfo& pluginInfo)
{
    const char* const deleteInstanceName = "deleteInstance";
    typedef void(*FunDeleteInstance)(IPlugin*);
    FunDeleteInstance deleteInstance = (FunDeleteInstance)GetProcAddress(pluginInfo.hDll, deleteInstanceName);

    if (!deleteInstance)
    {
        LT_ERROR(QString("[cannot getProcAddress][path]%1[procName]%2").arg(pluginInfo.pluginPath).arg(deleteInstanceName));
        return false;
    }

    deleteInstance(pluginInfo.instance);
    return FreeLibrary(pluginInfo.hDll);
}

关键函数:

  • LoadLibraryEx加载dll,LOAD_WITH_ALTERED_SEARCH_PATH让系统dll的搜索顺序从当前dll目录下开始,该方法会强制加载该dll关联得同目录下得所有dll,例如A.dll 依赖同目录下得 B.dll ,系统目录下也有B.dll,则使用LoadLibraryEx(A.dll, NULL, LOAD_WITH_ALTERED_SEARCH_PATH)避免加载到系统目录下得B.dll
  • GetProcAddress根据函数名检索dll中的函数地址。成功调用后,返回一个函数指针,可以像普通函数一样直接调用。本例中函数调用形式为IPlugin*()
  • FreeLibrary卸载dll。非零表示成功,零表示失败,GetLastError获取具体的错误原因