添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

使用 DEF 文件从 DLL 导出

0.3 确定要使用的导出方法

闭眼使用.def文件,因为__declspec(dllexport)生成的名字前后加了很多编译相关的字符,而且不同版本编译器可能会有区别。即使extern "C"后,仍然会根据调用方式和参数不同会有区别。而.def不存在这些问题;而且.def可以为每个函数名定义序号。如果需要删除某个导出的函数,.def文件只需要删除那一行即可,不需要修改代码文件;而__declspec(dllexport)需要在代码文件上修改。
最好__declspec(dllexport)和.def文件不要同时使用,用一种即可,虽然大部分时候同时用也不会有太大问题。
参考:
确定要使用的导出方法

1. extern "C"的作用

extern "C"的作用是声明以c语言的格式编译当前代码:

  • c语言没有函数重载
  • 编译后的函数名若有参数以"xxx@数字"结尾,“数字"为所有参数占用的内存大小(4位对齐);若无参数则结尾不含”@数字"
  • 编译后的开头字符与调用约定__cdecl(无开头字符)、__stdcall(以‘_’开头)、__fastcall(以‘@’开头)有关

上代码,两个函数,分别以c和c++格式编译,看看效果是什么:

//ApiExport.h
// extern "C" 与 默认c++ 方式的区别
extern "C" __declspec(dllexport) void func1_c();
__declspec(dllexport) void func1_cpp();
//ApiExport.cpp
void func1_c(){}
void func1_cpp(){}

很简单的两个函数,没有输入也没有输出。编译,查看生产的dll导出的函数名称:
在这里插入图片描述
可以看到声明extern "C"编译的函数名和我们定义的函数名是一致的,而以c++方式编译的函数名加了前缀和后缀。
再分析一下编译后的“func1_c”,既没有前缀又没有后缀,说明调用约定是__cdecl(默认调用约定)。

2. __cdecl、 __stdcall、__fastcall

调用约定,约定的什么?
在表面看来,约定了编译后的函数名称;实际约定的是由调用者还是被调用者清理堆栈上的参数

有两派:
__cdecl :调用者清理
__stdcall :被调用者清理
__fastcall比较特殊,没有被标准化,各个编译器实现不一样。
但是,但是!!!在Windows x64环境下编译代码时,只有一种调用约定,也就是说,32位下的各种约定在64位下统一成一种了。(
X86调用约定 - 维基百科

再深入就涉及到汇编了,到此为止;下面讲一下编译后的函数名称的区别。
还是上图上代码:
这里只写出声明代码,实际是没有定义的函数是不会编译的。

//ApiExport.h
#ifdef DLLAPI
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif
#ifndef DLLAPI_EXTERN_C
#define DLLAPI_EXTERN_C extern "C" DLLAPI
#endif
// 以c++方式编译
DLLAPI_EXTERN_C void __stdcall fun_extern_c_stdcall();
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall0();
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall1(int param);
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall2(int param, char param2);
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall3(int param, char param2,long param3);
DLLAPI_EXTERN_C int __cdecl fun_extern_c_cdecl(int param);
DLLAPI_EXTERN_C int __fastcall fun_extern_c_fastcall(int param);
// 以c方式编译
DLLAPI void __stdcall fun_cpp_stdcall();
DLLAPI int __stdcall fun_cpp_stdcall0();
DLLAPI int __stdcall fun_cpp_stdcall1(int param);
DLLAPI int __stdcall fun_cpp_stdcall2(int param, char param2);
DLLAPI int __stdcall fun_cpp_stdcall3(int param, char param2, long param3);
DLLAPI int __cdecl fun_cpp_cdecl(int param);
DLLAPI int __fastcall fun_cpp_fastcall(int param);

2.1 C++编译时函数名修饰约定规则:

__stdcall调用约定:
  1)、以"?“标识函数名的开始,后跟函数名;
  2)、函数名后面以”@@YG"标识参数表的开始,后跟参数表;
  3)、参数表以代号表示:
   X–void ,
   D–char,
   E–unsigned char,
   F–short,
   H–int,
   I–unsigned int,
   J–long,
   K–unsigned long,
   M–float,
   N–double,
   _N–bool,
   PA(32位)/PEA(64位)–表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;
  4)、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
  5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。
  其格式为"?functionname@@YG***@Z"或"?functionname@@YG*XZ",例如:

  	int __stdcall func_cpp_stacall3(int param,char param2,long param3)
  	编译后函数名:“?func_cpp_stacall3@@YGHHDJ@Z”
  	“?”  以c++方式编译
  	“@@” 后面为参数定义
  	“YG” 调用约定为__stdcall
  	“H” 返回值为 int 型
  	“H” 第一个参数为 int 型
  	“D” 第二个参数为 char 型
  	“J” 第三个参数为 long 型
  	“@Z” 有参函数名后缀
		void __stdcall func_cpp_stdcall()
		编译后函数名:“?func_cpp_stdcall@@YGXXZ”
		“?” 以c++方式编译
		“@@” 后接参数定义
		“X” 返回值为 void 
		“XZ” 无参函数后缀

__cdecl调用约定:
  规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。
  
__fastcall调用约定:
  规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。

2.2 C编译时函数名修饰约定规则:

**__stdcall调用约定:**
	以“_”开头,以“@数字”结尾,数字为参数占用的内存字节数。
**__cdecl调用约定:**
	不改变函数名称。
**__fastcall调用约定:**
	以“@”开头,以“@数字”结尾,数字为参数占用的内存字节数。

假设函数为:int func(int arg1,char arg2,long arg3);
以下列方式编译后的函数名为:

cdeclstdcallfastcall
以C方式编译func_func@12@func@12
以C++方式编译?func@@YAHHDJ@Z?func@@YGHHDJ@Z?func@@YIHHDJ@Z

为什么是@12?int占4byte,char占1byte,long占4byte,4+1+4=9?只想说一句:内存4位对齐!。注意:指针在32位程序中是4字节,64位程序是8字节

3. 用.def文件定义导出函数

从上一节可以看出,只有以C方式编译且调用约定为“__cdecl”(extern “C” __declspec(dllimport) int function)时函数名不变,其他时候函数名都会变,在大部分时候都没有影响(c/c++提供.lib文件即可、c#需要声明调用约定的特性),但是在delphi调用c/c++ dll时不能直接使用定义的函数名,只能使用编译后的函数名。为了统一编译后的函数名称,只能使用模块定义文件(def文件)定义函数名称,使用模块定义文件可以不用__declspec(dllimport)声明函数,最好都统一在.def文件中声明,方便管理。

看一下.def文件格式:

LIBRARY dllExportTest
EXPORTS
fun_cpp_stdcall @1
  • 文件第一条LIBRARY语句不是必须的,但若有LIBRARY语句则后面必须是生成的dll的文件名;
  • EXPORTS语句之后每一行列出一个已定义的函数名称(未定义的名称也可以,但没什么用)
  • 函数名称后接“@函数序号”,函数序号取值范围为 1 - N(N为导出函数的总数),且不能重复

3.1 在Visual Studio中使用.def文件:

  • 为项目添加模块定义文件
  • 在文件中添加导出函数
  • 设置到项目环境(添加模块定义文件时会自动设置)
1. extern "C"的作用extern "
DLL导出函数,__declspec(dllexport),.def模块定义文件,extern “C”区别一,__declspec(dllexport)二,.DEF模块定义文件三,__declspec(dllexport)和def文件的区别1,显式调用2,隐式调用四,结论       我们在编写动态库时,经常会用到类似extern “C”, __...
为何DLL 先看看静态库与DLL的不同之处 可执行文件的生成(Link期):前者很慢(因为要将库中的所有符号定义Link到EXE文件中),而后者很快(因为后者被Link的引入库文件无符号定义) 可执行文件的大小:前者很大,后者很小(加上DLL的大小就和前者差不多了) 可执行文件的运行速度:前者快(直接在EXE模块的内存中查找符号),后者慢(需要在DLL模块的内存中查找,在另一个模块的内存中查找自然较慢) 可共享性:前者不可共享,也就是说如果两个EXE使用了同一个静态库,那么实际在内存中存在此库的两份拷贝,而后者是可共享的。 可升级性:前者不可升级(因为静态库符号已经编入EXE中,要升级则EXE也需要重新编译),后者可以升级(只要接口不变,DLL即可被升级为不同的实现) 综合以上,选择静态库还是DLL 1. 静态库适于稳定的代码,而动态库则适于经常更改代码(当然接口要保持不变),当DLL更改(仅实现部分)后,用户不需要重编工程,只需要使用新的Dll即可。 2. 由于静态库很吃可执行文件的生成(Link期)时间,所以如果对可执行文件的Link时间比较敏感,那么就用DLL。 使用DLL 在介绍如何创建DLL之前,让我们先了解它是如何被使用的。 1. 显式调用(也叫动态调用) 显 示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存,再用GetProcAddress()在 内存中获取引入函数地址,然后你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的 AfxLoadLibrary释放DLL。 下面是个显示调用的例子,假定你已经有一个Test.dll,并且DLL中有个函数名为Test,其声明式是void(); #include < iostream > using namespace std; typedef void(*TEST )(); int main( char argc, char* argv[] ) { const char* dllName = "Test.dll"; const char* funcName = "Test"; HMODULE hDLL = LoadLibrary( dllName ); if ( hDLL != NULL ) { TEST func = TEST( GetProcAddress( hDLL, funcName ) ); if ( func != NULL ) { func(); else { cout << "Unable to find function \'" << funcName << "\' !" << endl; FreeLibrary( hDLL ); else { cout << "Unable to load DLL \'" << dllName << "\' !" << endl; return 0; 1. 显示调用使用GetProcAddress,所以只能加载函数,无法加载变量和类。 2. 此外GetProcAddress是直接在.dll文件中寻找同名函数,如果DLL中的Test函数是个C++函数,那么由于在.dll文件中的实际文件名会被修饰(具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看),所以直接找Test是找不到的,这时必须把函数名修改为正确的被修饰后的名称,下面是一种可能(此函数DLL中的调用约定为__cdecl): const char* funcName = "?Test@@YAXXZ"; 2. 隐式调用(也叫静态调用) 隐式调用必须提供DLL的头文件和引入库(可以看作轻量级的静态库(没有符号定义,但是说明了符号处于哪个DLL中))。 有了头文件和引入库,DLL的使用就跟普通静态库的使用没啥区别,只除了DLL要和EXE一起发布。 显示调用与隐式调用的优缺点 显示调用使用复杂,但能更加有效地使用内存,因为DLL是在EXE运行时(run time)加载,必须由用户使用LoadLibrary和FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写的DLL函数。 静态调用使用简单,但不能控制DLL加载时机,EXE加载到内存同时自动加载DLL到内存,EXE退出时DLL也被卸载。 创建DLL 下面我们着重讲解如何在VC下创建DLL 首先要建立一个Win32的DLL工程。再把普通静态库的所有文件转移到DLL工程中,然后: 为所有的类声明加上__declspec(dllexport)关键字,这有这样编译器才能自动为你产生引入库文件(否则你需要自己写.def文件来产生,因为不常用所以在此不再阐述其细节) 对于DLL的用户来讲,类声明就需要用另外一个关键字__declspec(dllimport)(此关键字对于类和函数而言并非必须,但对于变量而言则是必须的)。所以通常我们会定义一个宏来包装之,比如 #ifdef MYDLL_EXPORTS # define MYDLL_API __declspec(dllexport) #else # define MYDLL_API __declspec(dllimport) #endif 这样我们就能写出如下的类 class MYDLL_API MyClass { 当然在创建DLL的工程里需要包含preprocessor(预处理指示器)MYDLL_EXPORTS,而在用户工程里面则不应该包含MYDLL_EXPORTS。 其实上面说的VC早就帮我们做好了。如果你创建DLL工程叫做Test,那么此工程自动包含TEST_EXPORTS。如果你在创建工程的时候选择了Exprot Symbols, 那么VC还会自动帮你创建一个示例文件Test.h,此文件定义出 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif 你自定义的文件都应该包含此文件以使用TEST_API。(如果没有选择Exprot Symbols,那么就得自己动手写出这段宏了) 示例文件中还包括了一个类,变量,以及全局函数的写法 class TEST_API CTest { public: CTest(void); // TODO: add your methods here. extern TEST_API int nTest; TEST_API int fnTest(void); 通过上面的示例我们也可以看出全局(或者名字空间)变量和函数的声明方法 1. DLL的入口函数DllMain并非必须,如果没有它,编译器会帮你自动生成一个不做任何事的DllMain。 2. 如果是可以定义在头文件里面的东西:包括宏,常量,内联函数(包括成员内联函数)以及模板。那么在DLL中的定义中可以不必包含TEST_API,和普通的定义没有区别。 如果一个类完全由内联函数和纯虚函数构成,那么也不需要TEST_API这样的关键字。一个不带成员函数的struct也一样。 3. 所有未经__declspec(dllexport)导出的类都只能作为DLL内部类使用。当然外部仍然可以使用其内联成员函数(前提是该成员函数不应该调用任何未经导出函数或者类成员函数) 发布DLL 1. 程序库的作者应该将三件套:头文件,引入库文件和DLL一同发布给用户,其中头文件和引入库是专为静态调用的用户准备,也就是C/C++用户。(此外有些 DLL内部使用的头文件,如果没有被接口头文件直接#include,那么该头文件就不必发布,这和静态库是一样的)。 2. DLL的用户只需将DLL和可执行程序一同发布即可。 C++程序使用C语言DLL(静态库规则一样) C不存在class, 所以由C创建DLL必然也不会出现class;对于全局变量的使用C和C++没有什么不同,所以我们把焦点集中在全局函数上(此外全局变量的规则一样)。 我们知道C++程序要使用C语言函数就必须在函数声明前加上extern "C"关键字,这对于静态库和DLL没有什么不同。 但是这个关键字不能直接出现在头文件函数声明中,否则DLL无法通过编译, 原因很简单,C语言并没有extern "C"这个关键字。 1. 一种作法是把C向C++迁移的责任交给DLL创建者。定义出一个宏,以使DLL(C工程)中不出现extern "C"或者只是extern,而在用户工程(C++工程)中保持原样。幸运的是windows早已为我们设计好一切,这个宏就是EXTERN_C(存在于 winnt.h中) : #ifdef __cplusplus #define EXTERN_C extern "C" #else #define EXTERN_C extern #endif 注意上面必须是extern而不是空。因为虽然C的函数声明不是必须添加extern,但是变量声明则必须添加extern。 有了EXTERN_C,在头文件中这样定义函数: EXTERN_C TEST_API int fnTest(void); 2. 另外一种做法是把把C向C++迁移的责任交给用户,用户在包含DLL头文件的时候以extern "C"来include: extern "C" { #include "mydll.h" #include "mydllcore.h" 可以把上述的extern "C"段放在新的头文件中和DLL一起发布,这样C++用户只需要包含这个头文件就可以了。比如Lua库就自带了一个etc/lua.hpp文件。通常此文件会由DLL作者提供,所以此时迁移的责任仍在DLL创建者。 注意用户不要试图以extern "C"来重新声明函数,因为重复声明是允许的,但是必须保证和头文件中的声明相同。 3. 这种做法的一个变形就是直接在C头文件中以extern "C"将所有函数和变量声明包含之,这样就无需像上面那样多提供一个额外的头文件了。通常像这样(mydll头文件): #include 头文件段 #ifdef __cplusplus extern "C" { #endif 函数和变量声明 #ifdef __cplusplus #endif 创建标准的DLL,使其可被其他语言使用 通常我们会希望DLL能够被其他语言使用,因而我们的DLL往往不会提供类定义,而只提供函数定义。(因为许多编程语言是不支持类的)。 此时函数的调用约定必须是__stdcall(在vc下默认是__cdecl,所以你不得不手工添置),因为大部分语言不支持__cdecl,但支持__stdcall(比如VBScript,Delphi等)。 此外我们希望导出DLL中的函数名能够更易被识别(用户使用才会更方便),也就是说DLL应该编译出无修饰的C函数名。 所以我们可能会 1. 如果你只用C语言,那么必然以C文件创建DLL(自动编出C符号名),考虑到潜在的C++用户(此类用户多以静态调用方式使用DLL,因而需要看到其函数声明),我们还需要使用EXTERN_C关键字(详见上面的讨论)。 2. 如果你使用C++,那么必然以C++文件创建DLL,这时你应该直接以extern "C"修饰。 所以要创建能够为任意编程语言使用之DLL,我们应该 1. 只创建函数 2. 声明__stdcall调用约定(或者WINAPI,CALLBACK,前提是你必须包含windows头文件) 3. 以CPP文件 + extern "C" 或者 C文件 + EXTERN_C 4. 当然还需要DLL_API的声明(如果光有dllexport,那么DLL也只能被显示调用)。 更完美一点 不应该使用dllexport和extern "C"而应该使用.def文件。虽然经过上面的4个步骤,我们的DLL里面的C函数名已经相当简洁了,但仍不是最完美的:比如一个声明为function的函数,实际在DLL中的名字确可能是function@#。而使用.def文件,我们可以让DLL中的函数名和声明的函数名保持一致。 关于.def的详细用法可参考: 微软DLL专题 使用 DEF 文件从 DLL 导出DLL与静态库之间切换 前面我曾经提到对于使用DLL的用户__declspec(dllimport)关键字可有可无,当然前提是DLL中不包括变量定义。 所以要把库既能够做成DLL,也能够做成静态库,那么就应该作出类似下面这样的定义: 1. DLL不包括变量的定义 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API #endif 然 后只要把工程的配置属性(Configuration Type)简单地在Static Library (.lib)或者Dynamic Library (.dll)切换即可(VC会自动地为DLL添加预处理器TEST_EXPORTS,为静态库取消TEST_EXPORTS)。 2. DLL包含变量定义,也是标准的做法 #ifdef TEST_BUILD_AS_DLL #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif #else #define TEST_API #endif 如果要将库做成DLL,那么需要DLL创建者添加预处理器TEST_BUILD_AS_DLL和TEST_EXPORTS,后者通常由编译器自动添加;如果做成静态库则不需要添加任何预处理器。 用户则可以通过添加或取消TEST_BUILD_AS_DLL来使用动态库或静态库。 对于DLL的用户,TEST_BUILD_AS_DLL这个名称似乎起得不好。下面的做法或许会更合理些: #if defined(TEST_API) #error "macro alreday defined!" #endif #if defined(TEST_BUILD_DLL) && defined(TEST_USE_DLL) #error "macro conflict!" #endif #if defined(TEST_BUILD_DLL) // build dll # define TEST_API __declspec(dllexport) #elif defined(TEST_USE_DLL) // use dll # define TEST_API __declspec(dllimport) #else // build or use library, no preprocessor needs # define TEST_API #endif 微软DLL专题 Windows API编程之动态链接库(DLL
通用的DLL劫持技术,不需要手动导出DLL的相同功能接口,非常容易! 创建一个与被劫持的DLL具有相同名称DLL(例如,target.dll),并将被劫持的DLL重命名为其他名称(例如,target.dll.1),然后调用SuperDllHijack函数来完成hajick工作。 2020-4-4 修复了在x64中获取peb的错误。感谢 , , 。 您可以在example代码中查看更多详细信息。 VOID DllHijack1(HMODULE hMod) TCHAR tszDllPath[MAX_PATH] = { 0 }; GetModuleFileName(hMod, tszDllPath, MAX_PATH); PathRemoveFileSpec(tszDllPath); PathAppend(tszDllPat 导出函数: 在DLL中有一张导出表,其中有一系列函数,这些函数叫做导出函数。这些函数可供外部程序调用,即这些函数都是该DLL的入口点(类似main函数)。不在导出表中的函数,为该DLL私有的函数,外部程序不能调用它们。 1、没有__declspec(dllexport),将生成的测试lib库添加到项目中,直接调用,会报错: LNK2019 无法解析的外部符号 "vo... 一、以C语言接口的方式导出 这种方法就是在函数前面加上 extern “C” __declspec(dllexport) 加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。 #include stdafx.h #include<iostre 379 如何捕获AR和Shift+Alt组合键? 380 如何捕获Ctrl和Ctrl+Shift组合键? 381 如何捕获Ctrl、Ctrl+Alt和Ctrl+Alt+Shifl组合键? 第20章 声音和视频 382 如何调节系统音量? 383 如何设置背景音乐? 384 如何播放AVI动画文件? 385 如何播放VCD视频文件? 386 如何播放WAV简单声音文件? 387 如何播放系统默认声音文件? 388 如何使用MCI播放WAV声音文件? 389 如何使用MCI播放MIDI声音文件? 第21章 图形和图像 390 如何通过读取位图资源显示位图? 391 如何通过读取位图文件显示位图? 392 如何通过装入位图文件显示位图? 393 如何缩放显示位图? 394 如何截取当前屏幕? 395 如何任意裁剪图片? 396 如何利用掩码位图制作透明图片? 397 如何实现图形的拉伸显示效果? 398 如何通过位图文件直接得到位图大小? 399 如何获取屏幕上某点的颜色? 400 如何设置屏幕上某点的颜色? 401 如何读取与显示JPG等格式图像文件? 402 如何转换图像文件大小? 403 如何转换图像文件格式? 404 如何将彩色图像转换成黑白图像? 405 如何实现图像的底片化效果? 406 如何实现图像的雾化效果? 407 如何实现图像的锐化效果? 408 如何实现图像的柔化效果? 409 如何实现图像的马赛克效果? 410 如何实现图像的百叶窗效果? 411 如何复制图像? 412 如何剪切图像? 413 如何粘贴图像? 414 如何实现画线拉伸效果? 415 如何绘制渐变色图形? 416 如何绘制渐变色文字? 第22章 网络 417 如何初始化Socket? 418 如何创建Socket? 419 如何处理网络监听Socket? 420 如何处理C/S互连? 421 如何处理C/S数据发送? 422 如何处理C/S数据接收? 423 如何实现无连接的通信? 424 如何实现有连接的通信? 425 如何用有连接方式实现网络会议? 426 如何获取网卡地址? 427 如何扫描端口状态? 428 如何进行连续的Ping? 429 如何获取主机名和IP地址? 430 如何搜索局域网内的计算机? 431 如何创建拨号网络? 432 如何检查电子邮件数量? 433 如何发送和接收电子邮件? 434 如何连接FTP服务器? 435 如何获取FTP服务器的文件列表? 436 如何向FTP服务器上传文件? 437 如何从FTP服务器下载文件? 438 如何查询HTTP站点? 439 如何查询FTP站点? 440 如何查询Gopher站点? 第23章 数据库 441 如何使用ODBC连接数据源? 442 如何使用ODBC实现应用程序与数据库记录的交换? 443 如何使用ODBC浏览数据库记录? 444 如何使用ODBC增加数据库记录? 445 如何使用ODBC删除数据库记录? 446 如何使用ODBC修改数据库记录? 447 如何使用ODBC排序数据库记录? 448 如何使用ODBC查询数据库记录? 449 如何使用SQL语句查询排序数据厍记录? 450 如何使用ODBC创建EXCEL文件? 451 如何使用ODBC读取EXCEL文件信息? 452 如何获取系统已经安装的ODBC驱动程序? 453 如何安装Visual C++.NET中的MS Server服务管理器桌面引擎? 454 如何在Visual C++.NET中创建 MS SQL Server数据库? 455 如何使用.NET类库访问数据库? 456 如何使用DAO新建数据库? 457 如何使用DAO打开数据库? 458 如何使用DAO关闭数据库? 459 如何使用DAO新建数据库表? 460 如何使用DAO打开数据库表? 461 如何使用DAO删除数据库表? 462 如何使用DAO浏览数据库表字段? 463 如何使用DAO增加数据库表字段? 464 如何使用DAO删除数据库表字段? 465 如何使用DAO新建数据库表查询? 466 如何使用DAO浏览数据库表查询? 467 如何使用DAO删除数据库表查询? 468 如何使用DAO自定义记录集类? 469 如何使用DAO浏览数据库记录? 470 如何使用DAO增加数据库记录? 471 如何使用DAO删除数据库记录? 472 如何使用DAO修改数据库记录? 473 如何使用DAO查询数据库记录? 474 如何使用DAO排序数据库记录? 475 如何使用DAO处理数据库异常? 476 如何判断数据集是否允许更新? 477 如何实现ADO对象与数据源的连接? 478 如何导入ADO动态链接库? 479 如何使用ADO对象浏览数据库记录? 480 如何使用ADO对象增加数据库记录? 481 如何使用ADO对象删除数据库记录? 482 如何使用ADO对象修改数据库记录? 483 如何使用ADO对象排序数据库记录? 484 如何使用ADO对象查询数据库记录? 485 如何使用ADO处理数据库异常? 486 如何存取数据库图像字段? 487 如何创建数据库操作事务? 488 如何在程序中注册数据源? 489 如何创建ODBC数据源? 490 如何使用SQL模糊查询语句? 491 如何使用SQL语句检索时间段? 第24章 开发工具 492 如何设置条件断点? 493 如何设置堆栈大小? 494 如何产生全局惟一标识符? 495 如何删除项目文件中的类? 496 如何打开和编辑二进制文件? 497 如何检测代码括号是否匹配? 498 如何查看一个宏的原始定义? 499 如何添加.1ib文件到当前项目? 500 如何调整对话框模板上的控件的Tab键顺序?
创建动态链接库(DLL)可以实现代码的模块化,提高代码的重用性和可维护性,能够简化开发过程。本文将介绍如何为 C 语言编写 DLL 文件。 首先,在 Visual Studio 中创建一个新的 DLL 项目。选择“动态链接库”作为项目类型,应选择“Win32 项目”,并从“Win32 项目向导”中选择“DLL”。 接下来,在“Create a New Project”的“Application Settings”那一步中,选择“Dynamic-link Library(DLL)”。 然后,点击“next”,选择一个“Empty project”,并将项目命名为“mydll”。 然后选择 DLL 的输出路径,这就是我们要将 DLL 放置的位置。 在项目中添加一个新的源文件,将其命名为“mydll.c”。 在“mydll.c”文件中,编写你的 C 语言代码,可以定义任何数量的公共和私有函数和变量。将要导出函数声明为公共函数,使用“__declspec(dllexport)”标记。 当你完成编写代码后,需要将其编译DLL 文件。在 Visual Studio 中,选择“生成”菜单,然后选择“生成解决方案”,这将自动构建所有的项目,包括 DLL 文件。 DLL 文件有两部分,一部分是导出表,它包含了 DLL 中所有公共函数名称和入口地址;另一部分是 DLL 的代码和数据。导出表告诉其他程序如何正确使用 DLL 文件中的函数。 在使用 DLL 文件时,需要将该 DLL 文件包含在你的应用程序路径中,并在应用程序中使用“LoadLibrary”和“GetProcAddress”函数加载 DLL 并获取 DLL 中的函数入口地址。 总之,创建 DLL 文件可以使 C 语言代码更加模块化,提高软件开发效率。这个过程需要一些技巧和知识,但是只需遵循上述步骤,你就能轻松地创建自己的 DLL 文件。