【转】DLL编写教程

c++ 动态加载DLL和静态加载DLL以及DLL的编写 1、DLL的编写 新建win32控制台或者win32项目,然后选择DLL 下面是自动生成的文件MyDll.h // 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 MYDLL_EXPORTS // 符号编译的。在使用此 DLL 的 // 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将 /... 阅读详情

DLL编写教程

备注:本来想自己写一篇关于dll编程的文章,苦于不知从何写起,在网上找了一下dll编写教程,还真给我找到了一篇好文章,里面大部分内容都写得很不错,但有些部分我的见解和原文作者的见解有所不同,这里将原文附上,在一些见解不同的地方做了修改,都有标注。如有不对的地方,请各位大虾批评指正,谢谢!

半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?决定最近写一个编程技术系列,其内容是一些通用的编程技术。例如DLL,COM,Socket,多线程等等。这些技术的特点就是使用广泛,但是误解很多;网上教程很多,但是几乎没有什么优质良品。我以近几个月来的编程经验发现,很有必要好好的总结一下这些编程技术了。一来对自己是总结提高,二来可以方便光顾我博客的朋友。

好了,废话少说,言归正传。第一篇就是《DLL编写教程》,为什么起这么土的名字呢?为什么不叫《轻轻松松写DLL》或者《DLL一日通》呢?或者更nb的《深入简出DLL》呢?呵呵,常常上网搜索资料的弟兄自然知道。

本文对通用的DLL技术做了一个总结,并提供了源代码打包下载,下载地址为:

http://www.blogjava.net/Files/wxb_nudt/DLL_SRC.rar

 

DLL的优点

简单的说,dll有以下几个优点:

1) 节省内存。同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。如果使用dll,则只在内存中加载一次,所有使用该dll的进程会共享此块内存(当然,像dll中的全局变量这种东西是会被每个进程复制一份的)。

2) 不需编译的软件系统升级,若一个软件系统使用了dll,则该dll被改变(函数名不变)时,系统升级只需要更换此dll即可,不需要重新编译整个系统。事实上,很多软件都是以这种方式升级的。例如我们经常玩的星际、魔兽等游戏也是这样进行版本升级的。

3) Dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。这一点上DLL还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。要注意:com虽然也是以dll(或exe)的形式存在的,但它的调用方式却不同于普通dll,随后我的博客会给出com的调用方法,请关注。

最简单的dll

开始写dll之前,你需要一个c/c++编译器和链接器,并关闭你的IDE。是的,把你的VC和C++ BUILDER之类的东东都关掉,并打开你以往只用来记电话的记事本程序。不这样做的话,你可能一辈子也不明白dll的真谛。我使用了VC自带的cl编译器和link链接器,它们一般都在vc的bin目录下。(若你没有在安装vc的时候选择注册环境变量,那么就立刻将它们的路径加入path吧)如果你还是因为离开了IDE而害怕到哭泣的话,你可以关闭这个页面并继续去看《VC++技术内幕》之类无聊的书了。

最简单的dll并不比c的helloworld难,只要一个DllMain函数即可,包含objbase.h头文件(支持COM技术的一个头文件)。若你觉得这个头文件名字难记,那么用windows.H也可以。源代码如下:dll_nolib.cpp

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{
    HANDLE g_hModule;
    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
        cout<<"Dll is attached!"<<endl;
        g_hModule = (HINSTANCE)hModule;
        break;
    case DLL_PROCESS_DETACH:
        cout<<"Dll is detached!"<<endl;
        g_hModule=NULL;
        break;
    }
    return true;
}

其中DllMain是每个dll的入口函数,如同c的main函数一样。DllMain带有三个参数,hModule表示本dll的实例句柄(听不懂就不理它,写过windows程序的自然懂),dwReason表示dll当前所处的状态,例如DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载。当然还有表示加载到线程中和从线程中卸载的状态,这里省略。最后一个参数是一个保留参数(目前和dll的一些状态相关,但是很少使用)。

从上面的程序可以看出,当dll被加载到一个进程中时,dll打印"Dll is attached!"语句;当dll从进程中卸载时,打印"Dll is detached!"语句。

编译dll需要以下两条命令:

cl /c dll_nolib.cpp

这条命令会将cpp编译为obj文件,若不使用/c参数则cl还会试图继续将obj链接为exe,但是这里是一个dll,没有main函数,因此会报错。不要紧,继续使用链接命令。

Link /dll dll_nolib.obj

这条命令会生成dll_nolib.dll。

注意,因为编译命令比较简单,所以本文不讨论nmake,有兴趣的可以使用nmake,或者写个bat批处理来编译链接dll。

加载DLL(显式调用)

使用dll大体上有两种方式,显式调用和隐式调用。这里首先介绍显式调用。编写一个客户端程序:dll_nolib_client.cpp

#include <windows.h>

#include <iostream.h>

int main(void)

{
    //加载我们的dll
    HINSTANCE hinst=::LoadLibrary("dll_nolib.dll");
    if (NULL != hinst)
    {
        cout<<"dll loaded!"<<endl;
    }
    return 0;
}

注意,调用dll使用LoadLibrary函数,它的参数就是dll的路径和名称,返回值是dll的句柄。 使用如下命令编译链接客户端:

Cl dll_nolib_client.cpp

并执行dll_nolib_client.exe,得到如下结果:

Dll is attached!

dll loaded!

Dll is detached!

以上结果表明dll已经被客户端加载过。但是这样仅仅能够将dll加载到内存,不能找到dll中的函数。

使用dumpbin命令查看DLL中的函数

Dumpbin命令可以查看一个dll中的输出函数符号名,键入如下命令:

Dumpbin –exports dll_nolib.dll

通过查看,发现dll_nolib.dll并没有输出任何函数。

如何在dll中定义输出函数

总体来说有两种方法,一种是添加一个def定义文件,在此文件中定义dll中要输出的函数;第二种是在源代码中待输出的函数前加上__declspec(dllexport)关键字。

Def文件

首先写一个带有输出函数的dll,源代码如下:dll_def.cpp

#include <objbase.h>

#include <iostream.h>

void FuncInDll (void)

{
    cout<<"FuncInDll is called!"<<endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{
    HANDLE g_hModule;
    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_hModule = (HINSTANCE)hModule;
        break;
    case DLL_PROCESS_DETACH:
        g_hModule=NULL;
        break;
    }
    return TRUE;
}

这个dll的def文件如下:dll_def.def

;

; dll_def module-definition file

;

LIBRARY         dll_def.dll

DESCRIPTION     '(c)2007-2009 Wang Xuebin'

EXPORTS

                FuncInDll @1 PRIVATE

你会发现def的语法很简单,首先是LIBRARY关键字,指定dll的名字;然后一个可选的关键字DESCRIPTION,后面写上版权等信息(不写也可以);最后是EXPORTS关键字,后面写上dll中所有要输出的函数名或变量名,然后接上@以及依次编号的数字(从1到N),最后接上修饰符。

用如下命令编译链接带有def文件的dll:

Cl /c dll_def.cpp

Link /dll dll_def.obj /def:dll_def.def

再调用dumpbin查看生成的dll_def.dll:

Dumpbin –exports dll_def.dll

得到如下结果:

Dump of file dll_def.dll

File Type: DLL

Section contains the following exports for dll_def.dll

           0 characteristics

    46E4EE98 time date stamp Mon Sep 10 15:13:28 2007

        0.00 version

           1 ordinal base

           1 number of functions

           1 number of names

    ordinal hint RVA      name

          1    0 00001000 FuncInDll

Summary

        2000 .data

        1000 .rdata

        1000 .reloc

        6000 .text

观察这一行

          1    0 00001000 FuncInDll

会发现该dll输出了函数FuncInDll。

显式调用DLL中的函数

写一个dll_def.dll的客户端程序:dll_def_client.cpp

#include <windows.h>

#include <iostream.h>

int main(void)

{
    //定义一个函数指针
    typedef void (* DLLWITHLIB )(void);
    //定义一个函数指针变量
    DLLWITHLIB pfFuncInDll = NULL;
    //加载我们的dll
    HINSTANCE hinst=::LoadLibrary("dll_def.dll");
    if (NULL != hinst)
    {
        cout<<"dll loaded!"<<endl;
    }
    //找到dll的FuncInDll函数
    pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll");
    //调用dll里的函数
    if (NULL != pfFuncInDll)
    {
        (*pfFuncInDll)();  
    }
    return 0;
}

有两个地方值得注意,第一是函数指针的定义和使用,不懂的随便找本c++书看看;第二是GetProcAddress的使用,这个API是用来查找dll中的函数地址的,第一个参数是DLL的句柄,即LoadLibrary返回的句柄,第二个参数是dll中的函数名称,即dumpbin中输出的函数名(注意,这里的函数名称指的是编译后的函数名,不一定等于dll源代码中的函数名)。

编译链接这个客户端程序,并执行会得到:

dll loaded!

FuncInDll is called!

这表明客户端成功调用了dll中的函数FuncInDll。

__declspec(dllexport)

为每个dll写def显得很繁杂,目前def使用已经比较少了,更多的是使用__declspec(dllexport)在源代码中定义dll的输出函数。

Dll写法同上,去掉def文件,并在每个要输出的函数前面加上声明__declspec(dllexport),例如:

__declspec(dllexport) void FuncInDll (void)

这里提供一个dll源程序dll_withlib.cpp,然后编译链接。链接时不需要指定/DEF:参数,直接加/DLL参数即可,

Cl /c dll_withlib.cpp

Link /dll dll_withlib.obj

然后使用dumpbin命令查看,得到:

1    0 00001000 ?FuncInDll@@YAXXZ

可知编译后的函数名为?FuncInDll@@YAXXZ,而并不是FuncInDll,这是因为c++编译器基于函数重载的考虑,会更改函数名,这样使用显式调用的时候,也必须使用这个更改后的函数名,这显然给客户带来麻烦。为了避免这种现象,可以使用extern “C”指令来命令c++编译器以c编译器的方式来命名该函数。修改后的函数声明为:

extern "C" __declspec(dllexport) void FuncInDll (void)

dumpbin命令结果:

1    0 00001000 FuncInDll

这样,显式调用时只需查找函数名为FuncInDll的函数即可成功。

extern “C”

     使用extern “C”关键字实际上相当于一个编译器的开关,它可以将c++语言的函数编译为c语言的函数名称。即保持编译后的函数符号名等于源代码中的函数名称。

     虽然使用 extern C 的方法可以解决名字改编的问题,但它有两个缺陷,一个是它不能用来导出一个类的成员函数,只能导出全局函数,使全局函数不发生名字改编。另一个是如果我们导出的函数的调用约定发生改变的话,即使我们的函数用 extern C 作声明,函数名仍会发生改编。

     调用约定,一般没有指定下,用 C 编译器一般默认就是 C 调用约定(_cdcel),常用的调用约定有以下几种:

     _stdcall:也称 pascal 语言调用约定,在MS C++ 系列中的C/C++编译器,常用宏PASCAL宏来声明这个调用约定。

     _cecel        _fastcal       _thiscall      _naked call

     由于调用约定不是本章讨论的主要内容,有兴趣的可以在网上找一些资料看看。

隐式调用DLL

显式调用显得非常复杂,每次都要LoadLibrary,并且每个函数都必须使用GetProcAddress来得到函数指针,这对于大量使用dll函数的客户是一种困扰。而隐式调用能够像使用c函数库一样使用dll中的函数,非常方便快捷。

下面是一个隐式调用的例子:dll包含两个文件dll_withlibAndH.cpp和dll_withlibAndH.h。

代码如下:dll_withlibAndH.h

extern "C" __declspec(dllexport) void FuncInDll (void);

dll_withlibAndH.cpp

#include <objbase.h>

#include <iostream.h>

#include "dll_withLibAndH.h"//看到没有,这就是我们增加的头文件

extern "C" __declspec(dllexport) void FuncInDll (void)

{
    cout<<"FuncInDll is called!"<<endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{
    HANDLE g_hModule;
    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_hModule = (HINSTANCE)hModule;
        break;
    case DLL_PROCESS_DETACH:
        g_hModule=NULL;
        break;
    }
    return TRUE;
}

编译链接命令:

Cl /c dll_withlibAndH.cpp

Link /dll dll_withlibAndH.obj

在进行隐式调用的时候需要在客户端引入头文件,并在链接时指明dll对应的lib文件(dll只要有函数输出,则链接的时候会产生一个与dll同名的lib文件)位置和名称。然后如同调用api函数库中的函数一样调用dll中的函数,不需要显式的LoadLibrary和GetProcAddress。使用最为方便。客户端代码如下:dll_withlibAndH_client.cpp

#include "dll_withLibAndH.h"

//注意路径,加载 dll的另一种方法是 Project | setting | link 设置里

#pragma comment(lib,"dll_withLibAndH.lib")

int main(void)

{
    FuncInDll();//只要这样我们就可以调用dll里的函数了
    return 0;
}

__declspec(dllexport)和__declspec(dllimport)配对使用

上面一种隐式调用的方法很不错,但是在调用DLL中的对象和重载函数时会出现问题。因为使用extern “C”修饰了输出函数,因此重载函数肯定是会出问题的,因为它们都将被编译为同一个输出符号串(c语言是不支持重载的)。

事实上不使用extern “C”是可行的,这时函数会被编译为c++符号串,例如(?FuncInDll@@YAXH@Z、 ?FuncInDll@@YAXXZ),当客户端也是c++时,也能正确的隐式调用。

这时要考虑一个情况:若DLL1.CPP是源,DLL2.CPP使用了DLL1中的函数,但同时DLL2也是一个DLL,也要输出一些函数供Client.CPP使用。那么在DLL2中如何声明所有的函数,其中包含了从DLL1中引入的函数,还包括自己要输出的函数。这个时候就需要同时使用__declspec(dllexport)和__declspec(dllimport)了。前者用来修饰本dll中的输出函数,后者用来修饰从其它dll中引入的函数。

所有的源代码包括DLL1.H,DLL1.CPP,DLL2.H,DLL2.CPP,Client.cpp。源代码可以在下载的包中找到。你可以编译链接并运行试试。

值得关注的是DLL1和DLL2中都使用的一个编码方法,见DLL2.H

#ifdef DLL_DLL2_EXPORTS

#define DLL_DLL2_API __declspec(dllexport)

#else

#define DLL_DLL2_API __declspec(dllimport)

#endif

DLL_DLL2_API void FuncInDll2(void);

DLL_DLL2_API void FuncInDll2(int);

在头文件中以这种方式定义宏DLL_DLL2_EXPORTS和DLL_DLL2_API,可以确保DLL端的函数用__declspec(dllexport)修饰,而客户端的函数用__declspec(dllimport)修饰。当然,记得在编译dll时加上参数/D “DLL_DLL2_EXPORTS”,或者干脆就在dll的cpp文件第一行加上#define DLL_DLL2_EXPORTS。

VC自动生成的代码也是这样的!事实证明,我是抄袭它的,hoho!

DLL中的全局变量和对象

解决了重载函数的问题,那么dll中的全局变量和对象都不是问题了,只是有一点语法需要注意。如源代码所示:dll_object.h

#ifdef DLL_OBJECT_EXPORTS

#define DLL_OBJECT_API __declspec(dllexport)

#else

#define DLL_OBJECT_API __declspec(dllimport)

#endif

DLL_OBJECT_API void FuncInDll(void);

extern DLL_OBJECT_API int g_nDll;

class DLL_OBJECT_API CDll_Object {
public:
    CDll_Object(void);
    show(void);
    // TODO: add your methods here.
};

Cpp文件dll_object.cpp如下:

#define DLL_OBJECT_EXPORTS

#include <objbase.h>

#include <iostream.h>

#include "dll_object.h"

DLL_OBJECT_API void FuncInDll(void)

{
    cout<<"FuncInDll is called!"<<endl;
}

DLL_OBJECT_API int g_nDll = 9;

CDll_Object::CDll_Object()

{
    cout<<"ctor of CDll_Object"<<endl;
}

CDll_Object::show()

{
    cout<<"function show in class CDll_Object"<<endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{
    HANDLE g_hModule;
    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_hModule = (HINSTANCE)hModule;
        break;
    case DLL_PROCESS_DETACH:
        g_hModule=NULL;
        break;
    }
    return TRUE;
}

编译链接完后Dumpbin一下,可以看到输出了5个符号:

1    0 00001040 ??0CDll_Object@@QAE@XZ

2    1 00001000 ??4CDll_Object@@QAEAAV0@ABV0@@Z

3    2 00001020 ?FuncInDll@@YAXXZ

4    3 00008040 ?g_nDll@@3HA

5    4 00001069 ?show@CDll_Object@@QAEHXZ

它们分别代表类CDll_Object,类的构造函数,FuncInDll函数,全局变量g_nDll和类的成员函数show。下面是客户端代码:dll_object_client.cpp

#include "dll_object.h"

#include <iostream.h>

//注意路径,加载 dll的另一种方法是 Project | setting | link 设置里

#pragma comment(lib,"dll_object.lib")

int main(void)

{
    cout<<"call dll"<<endl;
    cout<<"call function in dll"<<endl;
    FuncInDll();//只要这样我们就可以调用dll里的函数了
    cout<<"global var in dll g_nDll ="<<g_nDll<<endl;
    cout<<"call member function of class CDll_Object in dll"<<endl;
    CDll_Object obj;
    obj.show();
    return 0;
}

运行这个客户端可以看到:

call dll

call function in dll

FuncInDll is called!

global var in dll g_nDll =9

call member function of class CDll_Object in dll

ctor of CDll_Object

function show in class CDll_Object

可知,在客户端成功的访问了dll中的全局变量,并创建了dll中定义的C++对象,还调用了该对象的成员函数。

中间的小结

牢记一点,说到底,DLL是对应C语言的动态链接技术,在输出C函数和变量时显得方便快捷;而在输出C++类、函数时需要通过各种手段,而且也并没有完美的解决方案,除非客户端也是c++。

记住,只有COM是对应C++语言的技术。

下面开始对各各问题一一小结。

显式调用和隐式调用

何时使用显式调用?何时使用隐式调用?我认为,只有一个时候使用显式调用是合理的,就是当客户端不是C/C++的时候。这时是无法隐式调用的。例如用VB调用C++写的dll。(VB我不会,所以没有例子)

我认为:显示调用更能节省内存,而且效率更高。所以在大型项目中应该多用显示调用的方法,下面从网上摘来一些更详细的说明:

静态调用方式所需的代码较动态调用方式所需的少,但存在着一些不足,一是如果要加载的DLL不存在或者DLL中没有要引入的例程,这时候程序就自动终止运行;二是 DLL一旦加载就一直驻留在应用程序的地址空间,即使DLL已不再需要了。动态调用方式就可解决以上问题,它在需要用到DLL的时候才通过 LoadLibrary函数引入,用完后通过FreeLibrary函数从内存中卸载,而且通过调GetProcAddress函数可以指定不同的例程。最重要的是,如果指定的DLL出错,至多是API调用失败,不会导致程序终止。以下将通过具体的实例说明说明这调用方式的使用方法。 动态连接库最大的特点就是能节省磁盘空间.当多个进程共享同一个DLL的时候,内存中只有一个DLL的代码.通过映射来使各个进程得以调用.

调用DLL,首先需要将DLL文件映像到用户进程的地址空间中,然后才能进行函数调用,这个函数和进程内部一般函数的调用方法相同。Windows提供了两种将DLL映像到进程地址空间的方法:隐式调用(通过lib和头文件)和显式调用(只通过提供的dll文件)。下面对这两种方式在vc中如何调用做详细的说明:

1、隐式调用(也叫隐式链接)

这种方法需要DLL工程经编译产生的LIB文件,此文件中包含了DLL允许应用程序调用的所有函数的列表,当链接器发现应用程序调用了LIB文件列出的某个函数,就会在应用程序的可执行文件的文件映像中加入一些信息,这些信息指出了包含这个函数的DLL文件的名字。当这个应用程序运行时,也就是它的可执行文件被操作系统产生映像文件时,系统会查看这个映像文件中关于DLL的信息,然后将这个DLL文件映像到进程的地址空间。

系统通过DLL文件的名称,试图加载这个文件到进程地址空间时,它寻找DLL 文件的路径按照先后顺序如下:

· 程序运行时的目录,即可执行文件所在的目录;

· 当前程序工作目录

· 系统目录:对于Windows95/98来说,可以调用GetSystemDirectory函数来得到,对于WindowsNT/2000来说,指的是32位Windows的系统目录,也可以调用GetSystemDirectory函数来得到,得到的值为SYSTEM32

· Windows目录

· 列在PATH环境变量中的所有目录

VC中加载DLL的LIB文件的方法有以下三种:

①LIB文件直接加入到工程文件列表中

在VC中打开 File View 一页,选中工程名,单击鼠标右键,然后选中 Add Files to Project 菜单,在弹出的文件对话框中选中要加入DLL的LIB文件即可。

②设置工程的 Project Settings 来加载DLL的LIB文件

打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件。

③通过程序代码的方式

加入预编译指令#pragma comment (lib,”*.lib”),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。

当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明。

2、显式调用(也叫显示加载)

隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,其能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下:

①使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。

②使用GetProcAddress函数得到要调用DLL中的函数的指针。

③不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。

Def和__declspec(dllexport)

其实def的功能相当于extern “C” __declspec(dllexport),所以它也仅能处理C函数,而不能处理重载函数。而__declspec(dllexport)和__declspec(dllimport)配合使用能够适应任何情况,因此__declspec(dllexport)是更为先进的方法。所以,目前普遍的看法是不使用def文件,我也同意这个看法。

从其它语言调用DLL

从其它编程语言中调用DLL,有两个最大的问题,第一个就是函数符号的问题,前面已经多次提过了。这里有个两难选择,若使用extern “C”,则函数名称保持不变,调用较方便,但是不支持函数重载等一系列c++功能;若不使用extern “C”,则调用前要查看编译后的符号,非常不方便。

第二个问题就是函数调用压栈顺序的问题,即__cdecl和__stdcall的问题。__cdecl是常规的C/C++调用约定,这种调用约定下,函数调用后栈的清理工作是由调用者完成的。__stdcall是标准的调用约定,即这些函数将在返回到调用者之前将参数从栈中删除。

这两个问题DLL都不能很好的解决,只能说凑合着用。但是在COM中,都得到了完美的解决。所以,要在Windows平台实现语言无关性,还是只有使用COM中间件。

总而言之,除非客户端也使用C++,否则dll是不便于支持函数重载、类等c++特性的。DLL对c函数的支持很好,我想这也是为什么windows的函数库使用C加dll实现的理由之一。

在VC中编写DLL

在VC中创建、编译、链接dll是非常方便的,点击file → New → Project → Win32 Dynamic-Link Library,输入dll名称dll_InVC然后点击确定。然后选择A DLL that export some symbols,点击Finish。即可得到一个完整的DLL。

仔细观察其源代码,是不是有很多地方似曾相识啊,哈哈!

最后

贴上女儿近照一张,小家伙长得太快了!

clip_image002

注: 原文出处 http://www.blogjava.net/wxb_nudt/archive/2007/09/11/144371.html

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/Linruin/archive/2009/10/14/4666796.aspx

如何编辑和编程 DLL 文件 在开始编辑和编程 DLL 文件之前,你需要了解 DLL 文件的结构和功能。完成 DLL 代码的编写后,使用开发工具的编译器编译项目,并生成 DLL 文件。你需要了解 DLL 文件的结构和功能,选择合适的开发工具和环境,创建 DLL 项目,编写 DLL 代码,编译和生成 DLL 文件,然后在其他程序中使用 DLL 文件。其他程序可以通过加载 DLL 文件,并调用其中的函数来使用 DLL 提供的功能。DLL 文件提供了一种方便的方式来共享代码和函数库,从而提高代码的重用性和软件开发的效率。 阅读详情

相关推荐

Qt DLL编写与应用实践教程

一旦接口设计原则被确定,下一步就是定义类和函数。在Qt环境下,你可以使用C++编写DLL的接口代码。考虑一个简单的例子,假设我们需要定义一个处理文本的类public:// 构造函数// 析构函数// 处理文本的函数// ... 可能还有其他函数和方法 ...导入符号前,需要在主程序或另一个DLL中使用相应的宏或编译器指令声明引用。这些声明应该匹配导出时使用的名称和签名。例如,在使用宏导出插件的情况下,导入时通常需要在客户端代码中包含相应的头文件,并声明与导出类兼容的接口。

weixin_35364187的博客 872

VS工具DUMPBIN查看依赖的LIB,DLL(DLL文件查看入口方法)

有时候我们想查看一个exe引用了哪些动态库,或者我们想看某个动态库包含哪些接口函数,这个时候可以使用dumpbin.exe工具。开始->全部程序->Visual Studio 2017->VS 2017开发人员命令行提示符。dumpbin /dependents xxx.exe # 简化版,到加载的dll。dumpbin -imports xxx.exe # 可以看到dll及dll函数。1)查看它的输入信息,可以看到加载的dll及dll函数。2、dumpbin工具的使用。

weixin_40029679的博客 3969

LabVIEW与VisionMaster融合开发:从封装避坑到高效集成的实战指南

本文为LabVIEW开发者提供了一套与海康VisionMaster融合开发的实战指南。针对直接调用SDK时遇到的程序集加载失败和面向对象编程冲突等核心障碍,文章详细介绍了通过C#进行“浅封装”的解决方案,包括封装显示控件和核心功能类,并给出了在LabVIEW中高效集成、调用及资源管理的最佳实践,旨在帮助开发者稳定、高效地集成机器视觉功能。

weixin_29210727的博客 155

VS自带工具:dumpbin的使用

用VS2010生成的.obj文件、.lib库、.dll库、.exe执行文件,如果想查看其中这些文件或库包含了哪些函数以及相关的信息(符号清单),可以通过VS2010自带的dumpbin工具来完成。 1.输入Dumpbin -imports calldll.exe查看它的输入信息,可以看到它加载了***.dll 2.输入dumpbin –exports dlltest.dll,列出导

Maybe 3404

CANoe实战:LIN Slave一致性测试全流程解析

本文详细解析了使用CANoe进行LIN Slave一致性测试的全流程,包括测试概述、环境搭建、模块集成、测试用例详解及报告分析。通过实际案例分享,帮助工程师掌握LIN 2.0/2.1/2.2、SAE J2602和ISO 17987等标准的自动化测试方法,提升汽车电子从节点的可靠性和兼容性验证效率。

weixin_42665725的博客 533

卡方检验的隐藏逻辑:为什么正态分布的平方和如此重要?

本文深入探讨了卡方检验的核心逻辑,揭示了正态分布平方和在统计检验中的重要性。通过分析卡方分布与伽马分布的关系,以及实际应用案例,帮助读者理解卡方检验的原理、局限及高级应用场景,为数据分析提供有力工具。

3a9bq4r8t2y的博客 208

DLL的生成、编写、调用总结

一. DLL的创建 二. DLL内容编写 三. DLL调用 四.dll编写时宏的解释

ZBMS_的博客 1万+

4. PCIe 接口时序

在上一篇中,大致介绍IPcore接口。总的来说接口如下图数据的接收和发送通过transmitTLPinterface和ReceiveTLPinterface。收发数据的时序也只是描述这几个接口的关系。从手册上截取PCIex1的接口时序。

jjinl的专栏 6123

[转]DLL编写教程

DLL编写教程半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?决定最近写一个编程技术系列,其内容是一些通用的编程技术。例如DLL,COM,Socket,多线程等等。这些技术的特点就是使用广泛,但是误解很多;网上教程很多,但是几乎没有什么优质良品。我以近几个月来的编程经验发现,很有必要好好的总结一下这些编程技术了。一来对自己是总结提高,二来可以方便光顾我博客的朋友。好了,废话

roofwei的专栏 564

CMOS图像传感器——工作原理

CMOS图像传感器的曝光与读出

沧海一升的博客 9460

【YOLOv8科研级优化】集成YOLOv10的SCDown采样模块,让你的模型下采样更高效更精准

打开确保模型解析器能识别SCDown模块。通过将YOLOv10的SCDown模块集成到YOLOv8中,我们实现了一个“细节保留+高效下采样”的目标检测模型。这个改进不仅在工程上能提升小目标、细粒度场景的检测效果,更在科研上提供了“分阶段下采样”的创新思路——这种思路可以延伸到其他卷积网络(如ResNet、MobileNet)的下采样层改进中,帮你在论文中打造独特的研究点。

m0_71589036的博客 137

(转)DLL编写教程

半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?决定最近写一个编程技术系列,其内容是一些通用的编程技术。例如DLL,COM,Socket,多线程等等。这些技术的特点就是使用广泛,但是误解很多;网上教程很多,但是几乎没有什么优质良品。我以近几个月来的编程经验发现,很有必要好好的总结一下这些编程技术了。一来对自己是总结提高,二来可以方便光顾我博客的朋友。 好了,废话少说,言归正传

poloHex的专栏 617

【零基础入门unity游戏开发——unity通用篇12】导入导出unity资源包、设置编程工具、创建脚本(组件)规则、MonoBehaviour基类介绍、脚本模板(基于unity6开发介绍)

导入导出unity资源包、设置编程工具、创建脚本(组件)规则、MonoBehaviour基类介绍、脚本模板(基于unity6开发介绍)

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 760

DLL编写教程(转)

转自:http://www.blogjava.net/wxb_nudt/archive/2007/09/11/144371.html<br /> <br /><br />DLL编写教程<br />半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?决定最近写一个编程技术系列,其内容是一些通用的编程技术。例如DLL,COM,Socket,多线程等等。这些技术的特点就是使用广泛,但是误解很多;网上教程很多,但是几乎没有什么优质良品。我以近几个月来的编程经验发现,很有必要好好的总结一下这些编程技术了。

sophiejohnson的专栏 390

linux那些事之numa balance

numa balance numa balance是页迁移技术最重要也是最开始使用应用的技术,主要是为解决由于调度器进程在两个node迁移造成访问内存性能问题。 numba balance通过页迁移技术,将不在运行节点的内存迁移到所运行的节点上。对应numa balance要注意几点: 防止进程在两个节点之前来回切换造成频繁进行页迁移(ping pang现场)从而影响性能。numa balance本身就是为了提高性能,不能因为造成频繁页迁移而对性能进行一个反向优化。 多个线程分布在不同线程 访.

weixin_42730667的博客 3908

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Unity TTS文字转语音 朗读 基于SpeechLib

本教程适用于不借助第三方语音平台,仅用Windows平台组件 进行文字转语音,基于SpeechLib 什么是TTS?TTS是Text To Speech的缩写,即“从文本到语音”,是人机对话的一部分,让机器能够说话。 教程需要用到两个dll文件,我已近整合好了下载地址←点我 1.新建Unity项目,新建空文件夹 Plugins 把下载到的dll文件放进去 2.编...

Liam's blog 5581

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

DLL教程--如何编写、调用DLL(附图说明)

该lib文件不同于<https://blog.youkuaiyun.com/weixin_43758311/article/details/133955852>中的静态库文件,此lib文件只是dll文件中导出函数的声明和定位信息,并不包含函数的实现(而第一节中的静态库文件,包含了函数的实现),因此此lib文件只是在调用对应dll库的工程编译时使用,不需要随exe发布。创建一个新的 DLL 项目时,通常会自动生成一些预编译头文件和其他文件,这些文件的作用是帮助加快编译速度和提高代码组织结构。,但是它不是预编译头文件。

m0_55621747的博客 1万+

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

DLL编写教程

第二是GetProcAddress的使用,这个API是用来查找dll中的函数地址的,第一个参数是DLL的句柄,即LoadLibrary返回的句柄,第二个参数是dll中的函数名称,即dumpbin中输出的函数名(注意,这里的函数名称指的是编译后的函数名,不一定等于dll源代码中的函数名)。FuncInDll@@YAXXZ,而并不是FuncInDll,这是因为c++编译器基于函数重载的考虑,会更改函数名,这样使用显式调用的时候,也必须使用这个更改后的函数名,这显然给客户带来麻烦。好了,废话少说,言归正传。

m0_63176080的博客 1919

(转载)DLL编写教程

半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?决定最近写一个编程技术系列,其内容是一些通用的编程技术。例如DLL,COM,Socket,多线程等等。这些技术的特点就是使用广泛,但是误解很多;网上教程很多,但是几乎没有什么优质良品。我以近几个月来的编程经验发现,很有必要好好的总结一下这些编程技术了。一来对自己是总结提高,二来可以方便光顾我博客的朋友。 好了,废话少说,言归正传

u011153111的专栏 548

C++编写DLL

在写C++程序时,时常需要将一个class写成DLL,供客户端程序调用。这样的DLL可以导出整个class,也可以导出这个class的某个方法。 DLL编写 一、导出整个class 方法很简单,只需要在类的头文件中class和类名之间加上_declspec(dllexport),同时在另外一份提供给客户端调用程序使用的类的头文件中class和类名之间加上_declspec(dllimport)。为了能让客户端程序和DLL程序公用该类的一份头文件,通常在类的头文件中使用宏和预编译指令来处理。如下DLLTest

skysys的研究小屋 2662
上一篇: 【转】全局变量、局部变量、静态全局变量、静态局部变量的区别
下一篇: 【转】快速掌握一个语言最常用的50%
Linruin
博客等级 码龄19年 18粉丝 36原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值