因为除了接口本身的开发外还要进行原SCADA系统与新版规约通讯所使用dll的修改和调试,本篇整理一下这部分的相关知识。
九、动态链接库dll编写与使用(链接、导入与导出)
dll项目在编译时注意在配置类型中选择动态库(.dll),总体上MSVC写dll的时候有两种导出方式:模块定义文件和__declspec(dllexport)导出函数,调用的时候也有两种链接方式:隐式载入时链接(implicit load-time linking)和显式运行时链接(explicit run-time linking)。
1. 模块定义文件
先说模块定义文件即*.def文件导出方式,主要包含一条LIBRARY语句——用于标识该模块定义文件属于某dll项目,一条EXPORTS语句——用于指定导出部分,例如:
其中EXPORTS语句格式为:
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
需要注意的是entryname/internalname一项在导出具有修饰名的函数或类时需要使用修饰名,修饰名可以从生成文件夹(debug或release)中的.map文件得到或者在dll所在目录使用
dumpbin -exports *.dll
命令查看。MS关于修饰名的说明:[修饰名]
C 函数的修饰形式取决于其声明中使用的调用约定。 这也是在将 C++ 代码声明为具有 extern "C" 链接时使用的修饰格式。 默认调用约定是 __cdecl。 请注意,在 64 位环境中,不修饰函数。
2. __declspec(dllexport)方式导出
在导出项的声明前加上__declspec(dllexport)修饰符,同样只适用于无修饰名和仅C编译调用(可以去除修饰名的前下划线)的情况,对于C++函数注意使用extern "C"(不能用于类及类成员函数)。
extern "C" __declspec( dllexport ) void foo();
3. dll的隐式加载
先链接编译好的引入库文件(.lib),方式有两种,一是在编译器里配置,二是用预编译指令来链接,注意路径,最好是直接把dll和lib文件直接拷到运行目录;链接后用_declspec(dllimport)修饰符来声明外部函数,如果需要C方式编译且导出时未做处理,也可以在前面加上extern "C"
。个人理解这种方法适用于要多次用到不同导出项的时候,比较方便,但浪费内存,如果一开始链接的库较多会导致启动缓慢。
// 预编译指令方式链接
#pragma comment(lib,"foo.lib")
// ...
_declspec(dllimport) void bar();
4. dll的显式加载
在用到导出函数之前用LoadLibrary函数加载动态库,用GetProcAddress函数获取导出函数地址,通常使用函数指针来保存这个地址以供调用,使用完FreeLibrary卸载动态库。这种方式适合于没有多次调用多个dll函数的情况,比较节省内存空间。
typedef void(*BAR)(); // 定义一个函数指针类型
HINSTANCE hDLL; // 保存该句柄用于后续的加载和释放
hDLL=LoadLibrary("foo.dll");// 加载动态链接库
BAR foobar=(FOO)GetProcAddress(hDLL,"bar/*1*/"); // 获取导出函数bar的地址
// 也可以使用之前模块定义文件中bar的编号"@1"来导出
foobar(); // 调用函数
FreeLibrary(hDLL);// 释放动态链接库
5. 第三方库常用方式:头文件处理
由于导出与调用使用的函数名(修饰名)和调用方式需要一致,一般来说第三方库的开发者常采用在供使用者使用的头文件中事先写好导出项调用语句的方式,这样使用者就不必关心中间的这些转换而只需隐式加载动/静态链接库并包含头文件即可直接调用原本的导出项了。下面摘出ACL库中的方式用作示例说明,有了上面的了解就可以看懂这些宏定义的具体意义了:
// 注意对于作为动态链接库时的处理
#ifdef ACL_CPP_LIB
# ifndef ACL_CPP_API
# define ACL_CPP_API
# endif
#elif defined(ACL_CPP_DLL) || defined(_WINDLL)
# if defined(ACL_CPP_EXPORTS) || defined(acl_cpp_EXPORTS)
# ifndef ACL_CPP_API
# define ACL_CPP_API __declspec(dllexport)
# endif
# elif !defined(ACL_CPP_API)
# define ACL_CPP_API __declspec(dllimport)
# endif
#elif !defined(ACL_CPP_API)
# define ACL_CPP_API
#endif
// ...
ACL_CPP_API void acl_cpp_init(void);// 有没有想起有一个叫WINAPI的宏定义?
// ...
当然,要写一个考虑到方方面面的第三方库不仅仅只有这些处理,只是以我目前的能力暂且了解到这里了。
十、 DLL调试
在以上部分做好之后,调试dll程序时只需把dll项目设为启动项目,并在项目的配置属性->调试->命令
中选择调用dll的应用程序即可,其他地方与常用调试方式并无区别。