DLL(三)显示链接

隐式链接虽然实现较为简单,但除了必须的.dll文件之外还需要DLL的.h文件(虽然不一定必须)和.lib文件。这在那些只提供.dll文件的场合就无法使用,而这种情况下我们只能用显示链接了。

显示链接:

是有应用程序在运行过程,由其空间中的线程决定是否调用DLL中的输出函数。

void Cdll_testDlg::OnBnClickedButton1()
{
    
// TODO: 在此添加控件通知处理程序代码
    HINSTANCE hInst;
    hInst
=LoadLibrary(L"dll1.dll");
    typedef 
int (*ADDPTR)(int a,int b);//定义一个函数指针
    ADDPTR add; //add函数指针变量
    if(hInst!=NULL){//载入DLL成功
        add=(ADDPTR)GetProcAddress(hInst,"add");//去除dll中函数名为add的这个函数的地址
        
//如果发生了函数的名字改编这里是不是就会失败啊!
        if(NULL!=add){//找到DLL中的add函数
            CString str;
            
/*
            注意:因为这里编译器默认定义了unicode了吧,解决办法如下
            Format ( L"%c",s ) ; 
            或者Format ( _T("%c"),s ) ;
            
*/
            str.Format(L
"5+3=%d",add(5,3));
            MessageBox(str);
        }
else{
            MessageBox(L
"获取函数地址失败");
        }
    }
else{
        MessageBox(L
"加载DLL失败");
    }
}

 

把dll1.dll拷贝到当前目录下发现加载DLL没有问题,但是提示获取函数地址失败,我想到难道是dll1.dll中的add函数名被改编了,非常有可能。

我看了一下dll1的工程的代码发现果然如此就一个dll1.cpp,而且代码是下面这样的

_declspec(dllexport) int add(int a,int b)
{
    
return a+b;
}

_declspec(dllexport) 
int subtract(int a,int b)
{
    
return a-b;
}

dumpbin了一下,当然会像下面这样了!

这样当然会名字改变,我又不想用extern"C"的方式来做,因为考虑到通用性dll中的函数调用应该做出_stdcall而不是用默认的_cdecal

那样的话即使用extern"C"也没法阻止名字改编了,所以我们就应该用.def文件。

于是定义了dll1.def文件加到工程中,并在项目属性的连接器那加上了.def(方法看DLL(一)),dll1.cpp改成下面这样

/*_declspec(dllexport)*/ int add(int a,int b)
{
    
return a+b;
}

/*_declspec(dllexport)*/ int subtract(int a,int b)
{
    
return a-b;
}

然后再dumpbin,这下输出就是

没有发生名字改编这下dll_test程序应该可以正常运行了吧,果然不出所料,成功的输出了

为什么用显示连接呢?

1)隐式链接需要.dll .lib .h而显示连接原则上只需要.dll

2) 我们看到的一些程序通常都不是一个.dll而是多个.dll,那如果做成隐式链接的话那在.exe启动的时候会把所有的.dll都加载到进程的地址空间这样的做的话会造成启动的缓慢,而且可能在程序运行过程中可能.dll中的很多函数我们都不需要去访问。可能是在某个条件发生的时候才会访问某个dll中的某个输出函数。如果隐式链接的话所有点函数都已经被加载进了,这样浪费资源(所以的DLL和所有DLL中的函数)。这种情况下我们可以动态加载。在某个条件发生的时候再loadlibary(*.dll)

然后GetProcAddress找到某个函数,用了之后再freeLibary(*.dll)这种显示连接其实是一种动态链接。

有一个地方我们要注意一下:

如果我们为了把.dll做的更加通用,改成下面这样

/*
 dll1.cpp:改变调用约定
 
*/
_stdcall 
int add(int a,int b)
{
    
return a+b;
}

_stdcall 
int subtract(int a,int b)
{
    
return a-b;
}

这样的调用约定的改变并不会改变函数的名字,就是不会发生名字改编,因为我们有dll1.def已经定死了

但是我们在dll1_test中如果还是按原先的方式进行调用就会出错。我们将改之后的dll1.dll替换掉原先dll1_test中的dll,但是调用函数的代码不改变。

那会怎么样,也许我们认为尽管改变了调用约定,但是函数没有发生名字改编,所以应该无需该代码啊。但是实际情况是让我们失望的,必须如下改动

//typedef int (*ADDPTR)(int a,int b);
typedef int (_stdcall *ADDPTR)(int a,int b);

也就是说使用dll的用户要遵照dll的创建者规定的调用约定进行调用,不然就无法正确调用,只是需要我们注意到。

 我们还要注意到一点就是这种显示链接dll库的做法,用dumpbin -imports dll_test.exe命令时是无法看出加载了哪个DLL的信息的。

 

posted on 2009-01-13 15:41  风荷小筑  阅读(623)  评论(0编辑  收藏  举报