【问题标题】:SetDllDirectory LoadLibrary inside a DLLDLL 中的 SetDllDirectory LoadLibrary
【发布时间】:2014-09-19 21:26:00
【问题描述】:

我能否在 C++ DLL 中使用 C++ SetDllDirectoryLoadLibrary 命令来加载另一个 DLL?我试过这样使用它们:

可执行文件调用第一个 DLL, 然后第一个 DLL 加载第二个 DLL, 然后第二个 DLL 进行计算...

但是当我运行可执行文件时,我收到以下错误消息:

此应用程序已请求运行时以不寻常的方式终止它。请联系应用程序支持团队了解更多信息。

直接链接到可执行文件时,第二个 DLL 工作正常!

这是我的可执行文件中的代码:

#include <windows.h>
#include <iostream>

int main(){
    HINSTANCE hDLL_Link=NULL;
    SetDllDirectory((LPCWSTR)L"C:\\Users\\MC\\Documents\\2014_07_01a_FDD_VC2008\\test_call_DLL\\EXE_calls_Link_DLL\\Release");
    hDLL_Link=LoadLibrary((LPCWSTR)L"Link_DLL.dll");
    if(hDLL_Link==NULL) std::cout<<"did not load"<<'\n';
    typedef void (*Ptr_OPS_Link)();
    Ptr_OPS_Link Ptr_OPS_Link_0;
    Ptr_OPS_Link_0=(Ptr_OPS_Link)GetProcAddress(hDLL_Link,"OPS_Link");
    Ptr_OPS_Link_0();
    FreeLibrary(hDLL_Link);
    system("pause");
}

这是第一个 DLL 中的代码:

#include "Link.h"

extern "C" __declspec(dllexport)
void OPS_Link(){
    Link*Link_Ptr_Object=NULL;
    if(Link_Ptr_Object==NULL){
        Link_Ptr_Object=new Link();
    }
    if(Link_Ptr_Object==NULL){
        //can not throw inside __declspec(dllexport) functions marked extern "C" that's why std::cout is implemented:
        std::cout<<"Error: could not link to FDD DLL"<<'\n';
        system("pause");
    }
    delete Link_Ptr_Object;
    Link_Ptr_Object=NULL;
}

Link::Link()
:m_void_Ptr_ObjectByDLL(NULL){
    HINSTANCE hDLL=NULL;//handle to DLL
    SetDllDirectory((LPCWSTR)L"C:\\Software\\Octave-3.6.1\\bin\\");
    hDLL=LoadLibrary((LPCWSTR)L"C:\\Users\\MC\\Documents\\2014_07_01a_FDD_VC2008\\Executable\\Release\\FDD_DLL.dll");
    if(hDLL==NULL){
        throw "DLL loading could not be done";
    }else if(hDLL!=NULL){
        typedef void (*Ptr_OPS_FDD)(std::string, int, int);
        Ptr_OPS_FDD Ptr_OPS_FDD_0;//pointer to procedure inside DLL
        Ptr_OPS_FDD_0=NULL;
        Ptr_OPS_FDD_0=(Ptr_OPS_FDD)GetProcAddress(hDLL,"OPS_FDD");
        if(Ptr_OPS_FDD_0==NULL){
            FreeLibrary(hDLL);
            throw "DLL exported function address could not be determined";
        }else{
            //run the procedure inside DLL:
            Ptr_OPS_FDD_0("FDD_INPUT_Truss_Bridge_Data2_Ambient_Inch_11Channels_77824Samples_SamplingFreq_256Hz.txt",11,256);//LabScaleTruss
            //Ptr_OPS_FDD_0("FDD_INPUT_Ambient_EW_15Channels_3000Samples_SamplingFreq_20Hz.txt",15,20);//AmbientEW
            //Ptr_OPS_FDD_0("FDD_INPUT_Meriden_3Channels(3_5_8)_3686400Samples_SamplingFreq_2048Hz.txt",3,2048);//MeridenBridge
            FreeLibrary(hDLL);
        }
    }
}

【问题讨论】:

  • 您的 main() 函数不会检查该函数是否存在。此外,如果无法加载库,您的 main() 函数会忽略错误——相反,您所做的只是 cout。此外,不要像您正在做的那样跨模块边界传递诸如std::string 之类的参数。在 SO 上搜索不这样做的原因typedef void (*Ptr_OPS_FDD)(std::string, int, int);

标签: c++ dll dependencies loadlibrary setdlldirectory


【解决方案1】:

我解决了这个问题,并在这里展示了如何:

我更改了可执行文件和第一个 DLL 中的代码,如下所示,以考虑错误处理,并且我添加了“return 0;”现在可执行文件链接到第一个 DLL 并且它工作完美......实际上问题是@ 987654322@ 需要 return 一些东西...我在 DLL 边界处将所有“std::string”替换为“char*”...顺便说一句,我想开发两个 DLL 的原因是在第一个中使用“SetDllDirectory”是我想用 C# GUI 调用 DLL,问题是 C# 中没有可用的“SetDllDirectory”命令,因此,我想出了开发两个 DLL,在第一个 DLL 中,我将使用“SetDllDirectory”来处理所需的依赖项(DLL 依赖于 Octave 和 Octave Bin 目录),然后我开发了一个执行实际计算的第二个 DLL...我知道有一些方法,如“[DllImport("Kernel32.dll")]”,从那里我们可以在 C# 中使用“SetDllDirectory”,但这种方法看起来很痛苦。

可执行文件中的更正代码:

    #include <windows.h>
#include <iostream>

int main(){
    try{
        HINSTANCE hDLL_Link=NULL;
        hDLL_Link=LoadLibrary((LPCWSTR)L"Link_DLL.dll");
        if(hDLL_Link==NULL){
            throw "Link DLL did not load";
        }else{
            typedef void (*Ptr_OPS_Link)();
            Ptr_OPS_Link Ptr_OPS_Link_0=NULL;
            Ptr_OPS_Link_0=(Ptr_OPS_Link)GetProcAddress(hDLL_Link,"OPS_Link");
            if(Ptr_OPS_Link_0==NULL){
                throw "Link DLL exported function not found";
                FreeLibrary(hDLL_Link);
            }else{
                Ptr_OPS_Link_0();
                FreeLibrary(hDLL_Link);
            }
        }
    }
    catch(char*char_Ptr_Exception){
        std::cerr<<"Error: "<<char_Ptr_Exception<<'\n';
    }
    system("pause");
    return 0;
}

第一个 DLL 中的更正代码:

    #include "Link.h"

extern "C" __declspec(dllexport)
void OPS_Link(){
    Link*Link_Ptr_Object=NULL;
    if(Link_Ptr_Object==NULL){
        Link_Ptr_Object=new Link();
    }
    if(Link_Ptr_Object==NULL){
        ////can not throw inside __declspec(dllexport) functions marked extern "C" that's why std::cout is implemented:
        //std::cout<<"Error: could not link to FDD DLL"<<'\n';
        system("pause");
    }
    delete Link_Ptr_Object;
    Link_Ptr_Object=NULL;
}

Link::Link()
:m_void_Ptr_ObjectByDLL(NULL){
    HINSTANCE hDLL=NULL;//handle to DLL
    SetDllDirectory((LPCWSTR)L"C:\\Software\\Octave-3.6.1\\bin\\");
    //path relative to executable (C# executable or C++ executable)
    hDLL=LoadLibrary((LPCWSTR)L"FDD_DLL.dll");
    if(hDLL==NULL){
        throw "FDD DLL did not load";
    }else if(hDLL!=NULL){
        typedef void (*Ptr_OPS_FDD)(char*, int, int);
        Ptr_OPS_FDD Ptr_OPS_FDD_0;//pointer to procedure inside DLL
        Ptr_OPS_FDD_0=NULL;
        Ptr_OPS_FDD_0=(Ptr_OPS_FDD)GetProcAddress(hDLL,"OPS_FDD");
        if(Ptr_OPS_FDD_0==NULL){
            throw "FDD DLL exported function not found";
            FreeLibrary(hDLL);
        }else{
            //run the procedure inside DLL:
            Ptr_OPS_FDD_0("FDD_INPUT_Truss_Bridge_Data2_Ambient_Inch_11Channels_77824Samples_SamplingFreq_256Hz.txt",11,256);//LabScaleTruss
            //Ptr_OPS_FDD_0("FDD_INPUT_Ambient_EW_15Channels_3000Samples_SamplingFreq_20Hz.txt",15,20);//AmbientEW
            //Ptr_OPS_FDD_0("FDD_INPUT_Meriden_3Channels(3_5_8)_3686400Samples_SamplingFreq_2048Hz.txt",3,2048);//MeridenBridge
            FreeLibrary(hDLL);
        }
    }
}

【讨论】:

  • 如果您解决了问题,请将其发布为答案。答案是为了回答问题,而questionsquestions,而不是answers
【解决方案2】:

除了不充分的错误处理和跨模块边界使用标准库之外,还有两件事需要考虑。

我可以在 dll 中使用 SetDllDirectory 来...吗?

是的,你可以,但你不应该! (BUG 等待发生)。

为什么?因为唯一负责改变环境的实体是主应用程序。 库代码(静态或 dll)不知道它将在哪个应用程序中使用。 它可能在某些程序中正常工作,而在其他程序中可能会失败。

我可以在 dll 中使用 C++ LoadLibrary/FreeLibrary 来... 吗?

是的,你可以,但不要在 dllmain 函数中使用它们,因为它会死锁你的程序。

【讨论】:

    【解决方案3】:

    您的代码中有几处可能会导致失败:

    1. 如果无法加载 DLL,请不要退出:
    2. 您正在传递内部使用动态分配的对象,因此将使用堆管理器。

    对于上面的1.,如果找不到库,您的 main() 函数只会执行简单的cout。然而,main 函数并没有退出,而是像找到库一样继续执行。

    对于上面的 2.,将 std::string 作为参数传递给 DLL 函数很容易出错,除非您确切知道自己在做什么,否则不建议这样做。它容易出错的原因是

    • 包含函数调用的 DLL 可能是使用与调用函数的 DLL 不同的选项集构建的。这些不同的选项可能会导致 std::string 的实现方式、它在内存中的布局方式等不同。

    • 包含函数调用的 DLL 可能是由与调用函数的 DLL 不同的编译器版本构建的。同样,std::string 的不同实现也存在同样的问题

    • 使用 std::string 的 DLL 和模块可能不是使用 C 运行时库的 DLL version 构建的。如果未使用运行时库的 DLL 版本构建和链接 DLL/模块,则 DLL 将使用与模块不同的堆。由于使用了不同的内存堆,对 std::string 的任何操作都将无效。

    所以简而言之,除非你能保证

    1. 您正在使用完全相同版本的编译器和编译器选项构建模块和 DLL。
    2. 您正在将所有模块链接到运行时库的 DLL 版本。

    然后将std::string 作为参数传递,一般来说,传递任何维护动态分配内存的对象,都可能或将导致运行时错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-09
      • 1970-01-01
      • 2012-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多