本文讲讲用VS2008制作易语言支持库(动态库和静态库)的具体方法和步骤。
这是我学习制作支持库时的学习笔记,贴出来分享下,也方便我以后查询。
时间仓促水平有限,不到之处还请各位高手们批评指正,以便完善。
本文欢迎转载,但请注明作者和出处。谢谢!
易语言支持库制作顺序是先制作动态库(dll) 再制作静态库(lib)。
I. 动态库的制作
1. 打开VS2008, 新建项目MFC DLL, 名称mylib, 完成
2. 项目属性,配置里选择所有配置 (设置debug和release模式的配置项)
项目属性 -> 常规 -> 字符集 设置为多字节字符集
项目属性 -> C++选项卡 -> 附加包含目录 填入sdk的Elib目录,如我的Elib目录是 D:\E51\sdk\cpp\ELib
3. 编写代码
1. 打开mylib.cpp文件
2. 文件头处加入
3.文件结尾处加入自己的代码
1) 定义LIB_INFO
这段代码是定义模块信息的,只在动态模块中使用,所以加入宏__E_STATIC_LIB来定义。
也就是说,如果是静态库那么这段代码就不会被编译。
静态库编译的时候只需在项目设置的C++ -> 预处理器 中加入 __E_STATIC_LIB 即可。
注意: GetNewInf 是易语言动态库 (dll) 唯一必须导出的函数。所以需要在.def文件(mylib.def)里加入以下
接下来我们看看LIB_INFO 是如何定义的。具体含义可以参阅开发文档。这里根据库的不同,需要修改的地方有
1- LIB_ 这样的变量是我们另外定义的,可以定义在mylib.h中如下:
2- DataTypes 是库中定义的数据类型表,类型的数量可以自动由sizeof(DataTypes)/sizeof(DataTypes[0]) 计算。
Commands 函数命令信息表,ExecuteCommand 命令指针表,Consts 常量表 都是一样的。这些都是库中希望定义的成员。
如果不需要就在数量上写0,变量处替换成NULL即可。
3- mylib_ProcessNotifyLib 是库中需要定义的消息指针。必须定义在宏的外面以便静态和动态库都能调用
本函数的作用是接收来自易语言IDE或者运行时环境的通知。并且在编译的时候起到在静态库和动态库之间通讯的目的。
接下来我们就可以定义库中需要使用的 函数,类型,常量 了。
这些都定义在 mylib_ProcessNotifyLib 函数的前面,且都定义在宏中,如下:
#ifndef __E_STATIC_LIB
...
#endif
因为这些只需包含在动态模块里,不需要静态中定义。 函数的具体实现过程需要定义在宏的外面,具体方式会在下文中讲到。
2) 定义常量 Consts
常量等级有: LVL_SIMPLE 1 // 初级
LVL_SECONDARY 2 // 中级
LVL_HIGH 3 // 高级
参数类型有: CT_NUM 1 // sample: 3.1415926
CT_BOOL 2 // sample: 1
CT_TEXT 3 // sample: "abc"
文本内容是CT_TEXT用,数值内容是CT_NUM和CT_BOOL用。
3) 定义数据类型 DataTypes
用作提供支持库自定义数据类型,包含窗口单元。一个简单的例子:
关于LIB_DATA_TYPE_INFO的解释 以及 窗口型数据的定义 都可参见开发文档中 "m_pDataType成员说明"和"数据类型说明"。
4) 定义函数,ExecuteCommand,Commands
函数书写规范是
函数的实现都需要定义在宏的外面以便静态和动态库都能使用,但ExecuteCommand,Commands则只需定义在宏的里面供动态库使用。
pRetData 输出数据指针。当对应CMD_INFO中m_dtRetType为_SDT_NULL(即定义无返回值)时,pRetData无效;
iArgCount 函数参数个数
pArgInf 函数参数指针
注:
a. 函数名前的前缀 mylib_ 是以后静态库中使用的。要求必须是动态库项目名称mylib的全小写形式 mylib_。
b. 只需对库中需要调出的函数加前缀处理,其他函数不需要这样书写。
c. 如果程序内部需要调用这个函数,只需使用函数名即可,无须前缀。
d. 参数类型如果是 AS_RECEIVE_VAR或AS_RECEIVE_VAR_ARRAY或AS_RECEIVE_VAR_OR_ARRAY 则参数必须是变量。
// AS_DEFAULT_VALUE_IS_EMPTY 参数可空且默认值为空
// AS_HAS_DEFAULT_VALUE 参数有默认值 且不可空
// AS_RECEIVE_VAR 参数必须是非数组变量
// AS_RECEIVE_VAR_OR_ARRAY 参数是数组或非数组变量
// AS_RECEIVE_ARRAY_DATA 参数是数组数据
// AS_RECEIVE_ALL_TYPE_DATA 参数是数组或非数组数据
以上参数除了前两个互斥,其他都可以相加使用,以便具有多个性质。
例如 AS_DEFAULT_VALUE_IS_EMPTY + AS_RECEIVE_VAR 表示参数可空,并且只能是变量。
e. 所属类别是在库中函数归类的子目录,这些子目录的名字在 LIB_INFO的LIB_TYPE_STR 字串定义。-1是数据类型,1第一类,2第二类...
sdk中关于数据类型的定义如下:
#define _SDT_NULL 0 // 空白数据类型
#define _SDT_ALL MAKELONG (MAKEWORD (0, 0), 0x8000) // 通用型
/* 仅用于支持库命令定义其参数或返回值的数据类型,当用于定义库命令参数时,
_SDT_ALL可以匹配所有数据类型(数组类型必须符合要求)。*/
#define SDT_BYTE MAKELONG (MAKEWORD (1, 1), 0x8000) // 字节
#define SDT_SHORT MAKELONG (MAKEWORD (1, 2), 0x8000) // 短整数
#define SDT_INT MAKELONG (MAKEWORD (1, 3), 0x8000) // 整数
#define SDT_INT64 MAKELONG (MAKEWORD (1, 4), 0x8000) // 长整数
#define SDT_FLOAT MAKELONG (MAKEWORD (1, 5), 0x8000) // 小数
#define SDT_DOUBLE MAKELONG (MAKEWORD (1, 6), 0x8000) // 双精度小数
#define SDT_BOOL MAKELONG (MAKEWORD (2, 0), 0x8000) // 逻辑
#define SDT_DATE_TIME MAKELONG (MAKEWORD (3, 0), 0x8000) // 日期时间
#define SDT_TEXT MAKELONG (MAKEWORD (4, 0), 0x8000) // 文本
#define SDT_BIN MAKELONG (MAKEWORD (5, 0), 0x8000) // 字节集
#define SDT_SUB_PTR MAKELONG (MAKEWORD (6, 0), 0x8000) // 记录用户易语言子程序的代码地址
这些数据类型在函数定义参数时调用。
动态库制作完成后,编译会生成mylib.dll文件。把.dll改成.fne 拷贝到易语言的lib文件目录下即可使用。或者也可以修改项目属性,把编译输出文件后缀从.dll修改成.fne,并输出在易语言的lib目录中。 这样编译完后即可直接在易语言里调用。
II. 静态库的制作
如果你已经按照上面的方法书写代码并制作好了动态库,接着就可以方便的再此基础上制作静态库了。方法有两种
第一种是生成一个静态库项目编译
第二种是在原来的动态库项目上修改成静态库项目
方法一)生成静态库项目
具体步骤如下:
1. 在项目解决方案上鼠标右键 -> 添加 -〉新建项目 名称输入 mylib_static (就是动态库项目名称加后缀_static)。
VS的向导里设置程序类型 为"静态库"。去掉预编译头的钩。点击完成。
2. 选择动态库项目文件列表中的所有文件,ctrl+C 复制 在新的mylib_static项目 中ctrl+V拷贝过来。
3. 配置mylib_static 项目属性, 以release模式为例,打开release配置。修改配置如下:
+ 常规 -> 字符集 设置为多字节字符集
+ 常规 -> 全程序优化 设成 无全程序优化 (这里很重要否则会影响 resym.exe 的工作)
+ C++选项卡 -> 附加包含目录 填入D:\E51\sdk\cpp\ELib
+ C++ -> 预处理器 -> 预处理定义 添加宏 __E_STATIC_LIB
+ 生成事件 -> 生成后事件 -> 命令行 填入 D:\E51\sdk\tools\resym.exe all infile=$(TargetPath) outfile=$(TargetPath)
resym.exe 是用来修改lib库连接符号的,以解决连接时符号冲突的问题。程序位置设成你自己sdk所在位置。
设置后选择release编译即可。 编译完后会生成静态库mylib_static.lib文件。
方法二)修改动态库配置为静态库配置
具体步骤如下:
打开动态库项目mylib的项目管理,点击配置管理器,mylib的配置处下拉菜单选择 "新建", 添入名称 release_static, "从此处复制设置"选择release
创建release_static后开始修改配置如下:
+ 常规 -> 配置类型 设置为 静态库(.lib)
+ 常规 -> 全程序优化 设成 无全程序优化
+ C++ -> 预处理器 -> 预处理定义 删除宏 _USRDLL 添加宏 __E_STATIC_LIB
+ C++ -> 预编译头 -> 选择不使用预编译头
+ 管理员 -> 常规 -> 输出文件 改为 $(OutDir)\$(ProjectName)_static.lib
+ 生成事件 -> 生成后事件 -> 命令行 填入你 D:\E51\sdk\tools\resym.exe all infile=$(TargetPath) outfile=$(TargetPath)
设置修改完成后,选择release_static项目编译
静态库的使用需要动态库支持。我们需要把动态库的fne放在易语言的lib目录下 和 静态库的lib放在static_lib目录下。这样才能静态编译。编译出来的结果将可以脱离任何非系统支持库独立运行。
关于运行时库的配置:
个人比较喜欢把release模式设置成"在静态库中使用 MFC"并且使用"多线程(/MT)"。
VS向导生成的MFC项目(exe,dll,lib)一般都默认设置为 "在共享 DLL 中使用 MFC" 并使用 "多线程 DLL (/MD)"
这样编译出来的COFF文件尺寸小,但程序运行时必须依赖MFC的dll库。
把设置修改成"在静态库中使用 MFC"并且使用"多线程(/MT)" 就可以脱离MFC的dll库运行。这样即使在没有安装Visual C++ Runtime library的机器上也能正常运行。这种模式也就是VC++的"静态编译"。编译后的文件尺寸会稍大些。技术上和易语言的静态编译是一样的。
具体设置方法
+ 常规 -> MFC的使用 选择 "在静态库中使用 MFC",如果库中没有用到MFC的类函数和类资源 那么也可以选择"使用标准 Windows 库"
+ C++ -> 代码生成 -> 运行时库 选择 "多线程(/MT)"
很多VC软件开发者就是因为这里没有设置好,结果程序在其他机器上一运行就跳出缺少运行时库的错误提示。所以本人建议最好养成习惯,使用静态编译模式配置项目。
下面给出一个简单的支持库的例子:
我们希望库里面有一个函数,且不需要自定义数据类型和常量。
函数中文名称是 "两数相加",英文名称是"myadd", 希望调用格式是。
〈小数型〉 两数相加 (小数型 参数1,小数型 参数2)
该函数在库中所属函数分类是"基本运算"
源代码如下:
+ mylib.cpp文件内容
+ mylib.h的文件里加入
常见问题:
Q: 编译静态库没问题,但是易语言调用静态编译时出现
error LNK2001: unresolved external symbol ___security_cookie
error LNK2001: unresolved external symbol @__security_check_cookie@4
A: 关掉“缓冲区安全检测”重新编译静态库。 具体方法:
静态库编译项目设置里,C++ -> 代码生成 -> 缓冲区安全检测 设置为 否 重新编译静态库即可。
Q: 如果你的静态库在静态编译的时候需要其他静态库的支持 怎么办?
A: 例如: 你的静态库A.lib里使用了其他静态库B.lib和C.lib的函数。
那么你需要修改A源码里的函数mylib_ProcessNotifyLib的
else if(nMsg == NL_GET_DEPENDENT_LIBS)
return (INT)"B.lib\0""C.lib\0""\0";
// 返回静态库所依赖的其它静态库文件名列表(格式为\0分隔的文本,结尾两个\0)
相应的编译动态库时,需要把 B.lib;C.lib 加入到项目属性的
连接器 -> 输入 -> 附加依赖项 里。也就是使用静态库编译dll和exe的方法。
Q: 如何调试你的动态库?
A: VC上调试你的动态库和调试dll是一样的。具体步骤是:
1. debug模式编译出动态库fne/fnr文件,放到lib目录下
2. 易语言写个测试程序,里面调用这个动态库某个需要调试的函数
3. 动态编译这个测试程序,加入是test.exe
4. VC中在需要调试的调试函数开头处设断点,然后F5运行调试
5. 选择test.exe文件,执行。一旦测试程序运行到这个函数,就会断下来,我们就可以调试了。
只要动态库运行正常了,静态库因为用的是同一套代码,所以也没问题。
Q: 待续...
这是我学习制作支持库时的学习笔记,贴出来分享下,也方便我以后查询。
时间仓促水平有限,不到之处还请各位高手们批评指正,以便完善。
本文欢迎转载,但请注明作者和出处。谢谢!
易语言支持库制作顺序是先制作动态库(dll) 再制作静态库(lib)。
I. 动态库的制作
1. 打开VS2008, 新建项目MFC DLL, 名称mylib, 完成
2. 项目属性,配置里选择所有配置 (设置debug和release模式的配置项)
项目属性 -> 常规 -> 字符集 设置为多字节字符集
项目属性 -> C++选项卡 -> 附加包含目录 填入sdk的Elib目录,如我的Elib目录是 D:\E51\sdk\cpp\ELib
3. 编写代码
1. 打开mylib.cpp文件
2. 文件头处加入
复制代码
- #include <lib2.h>
- #include <lang.h>
- #include <fnshare.h>
- #include <fnshare.cpp>
3.文件结尾处加入自己的代码
1) 定义LIB_INFO
这段代码是定义模块信息的,只在动态模块中使用,所以加入宏__E_STATIC_LIB来定义。
也就是说,如果是静态库那么这段代码就不会被编译。
静态库编译的时候只需在项目设置的C++ -> 预处理器 中加入 __E_STATIC_LIB 即可。
复制代码
- #ifndef __E_STATIC_LIB
- static LIB_INFO LibInfo =
- {
- /* { 库格式号, GUID串号, 主版本号, 次版本号, 构建版本号, 系统主版本号, 系统次版本号, 核心库主版本号, 核心库次版本号,
- 支持库名, 支持库语言, 支持库描述, 支持库状态,
- 作者姓名, 邮政编码, 通信地址, 电话号码, 传真号码, 电子邮箱, 主页地址, 其它信息,
- 类型数量, 类型指针, 类别数量, 命令类别, 命令总数, 命令指针, 命令入口,
- 附加功能, 功能描述, 消息指针, 超级模板, 模板描述,
- 常量数量, 常量指针, 外部文件} */
- LIB_FORMAT_VER, _T(LIB_GUID_STR),
- LIB_MajorVersion, LIB_MinorVersion, LIB_BuildNumber,
- LIB_SysMajorVer, LIB_SysMinorVer, LIB_KrnlLibMajorVer, LIB_KrnlLibMinorVer,
- _T(LIB_NAME_STR), __GBK_LANG_VER, _WT(LIB_DESCRIPTION_STR), _LIB_OS(__OS_WIN),
- _WT(LIB_Author), _WT(LIB_ZipCode), _WT(LIB_Address), _WT(LIB_Phone), _WT(LIB_Fax), _WT(LIB_Email), _WT(LIB_HomePage), _WT(LIB_Other),
- sizeof(DataTypes)/sizeof(DataTypes[0]), DataTypes, LIB_TYPE_COUNT, _WT(LIB_TYPE_STR), sizeof(Commands)/sizeof(Commands[0]), Commands, ExecuteCommand,
- NULL, NULL, mylib_ProcessNotifyLib, NULL, NULL,
- sizeof(Consts)/sizeof(Consts[0]), Consts, NULL
- };
- PLIB_INFO WINAPI GetNewInf()
- {
- return (&LibInfo);
- };
- #endif
注意: GetNewInf 是易语言动态库 (dll) 唯一必须导出的函数。所以需要在.def文件(mylib.def)里加入以下
复制代码
- EXPORTS
- ; 此处可以是显式导出
- GetNewInf
接下来我们看看LIB_INFO 是如何定义的。具体含义可以参阅开发文档。这里根据库的不同,需要修改的地方有
1- LIB_ 这样的变量是我们另外定义的,可以定义在mylib.h中如下:
复制代码
- #ifndef __E_STATIC_LIB
- #define LIB_GUID_STR "0000000000000000000000000000" /*GUID串: {00000000-0000-0000-0000-00000000}, 必须使用guidgen.exe生成*/
- #define LIB_MajorVersion 1 /*库主版本号*/
- #define LIB_MinorVersion 1 /*库次版本号*/
- #define LIB_BuildNumber 20110318 /*构建版本号*/
- #define LIB_SysMajorVer 3 /*系统主版本号*/
- #define LIB_SysMinorVer 0 /*系统次版本号*/
- #define LIB_KrnlLibMajorVer 3 /*核心库主版本号*/
- #define LIB_KrnlLibMinorVer 0 /*核心库次版本号*/
- #define LIB_NAME_STR "支持库名称" /*支持库名*/
- #define LIB_DESCRIPTION_STR "支持库功能描述" /*功能描述*/
- #define LIB_Author "作者名称" /*作者名称*/
- #define LIB_ZipCode "邮政编码" /*邮政编码*/
- #define LIB_Address "通信地址" /*通信地址*/
- #define LIB_Phone "电话号码" /*电话号码*/
- #define LIB_Fax "传真号码" /*传真号码*/
- #define LIB_Email "电子邮箱" /*电子邮箱*/
- #define LIB_HomePage "主页地址" /*主页地址*/
- #define LIB_Other "其它信息" /*其它信息*/
- #define LIB_TYPE_COUNT 1 /*命令分类数量*/
- #define LIB_TYPE_STR "0000基本命令\0""\0" /*命令分类*/
- #endif
2- DataTypes 是库中定义的数据类型表,类型的数量可以自动由sizeof(DataTypes)/sizeof(DataTypes[0]) 计算。
Commands 函数命令信息表,ExecuteCommand 命令指针表,Consts 常量表 都是一样的。这些都是库中希望定义的成员。
如果不需要就在数量上写0,变量处替换成NULL即可。
3- mylib_ProcessNotifyLib 是库中需要定义的消息指针。必须定义在宏的外面以便静态和动态库都能调用
复制代码
- EXTERN_C INT WINAPI mylib_ProcessNotifyLib(INT nMsg, DWORD dwParam1, DWORD dwParam2)
- {
- #ifndef __E_STATIC_LIB
- if(nMsg == NL_GET_CMD_FUNC_NAMES) // 返回所有命令实现函数的的函数名称数组(char*[]), 支持静态编译的动态库必须处理
- return (INT)CommandNames;
- else if(nMsg == NL_GET_NOTIFY_LIB_FUNC_NAME) // 返回处理系统通知的函数名称(PFN_NOTIFY_LIB函数名称), 支持静态编译的动态库必须处理
- return (INT)"mylib_ProcessNotifyLib";
- else if(nMsg == NL_GET_DEPENDENT_LIBS) return (INT)NULL;
- // 返回静态库所依赖的其它静态库文件名列表(格式为\0分隔的文本,结尾两个\0), 支持静态编译的动态库必须处理
- // kernel32.lib user32.lib gdi32.lib 等常用的系统库不需要放在此列表中
- // 返回NULL或NR_ERR表示不指定依赖文件
- #endif
- return ProcessNotifyLib(nMsg, dwParam1, dwParam2);
- };
本函数的作用是接收来自易语言IDE或者运行时环境的通知。并且在编译的时候起到在静态库和动态库之间通讯的目的。
接下来我们就可以定义库中需要使用的 函数,类型,常量 了。
这些都定义在 mylib_ProcessNotifyLib 函数的前面,且都定义在宏中,如下:
#ifndef __E_STATIC_LIB
...
#endif
因为这些只需包含在动态模块里,不需要静态中定义。 函数的具体实现过程需要定义在宏的外面,具体方式会在下文中讲到。
2) 定义常量 Consts
复制代码
- #ifndef __E_STATIC_LIB
- LIB_CONST_INFO Consts[] =
- {
- /* { 中文名称, 英文名称, 常量布局, 常量等级(LVL_), 参数类型(CT_), 文本内容, 数值内容 } */
- { _WT("常量_ZERO"), _WT("ZERO"), NULL, LVL_SIMPLE, CT_NUM, NULL, 0 },//数值常量
- { _WT("常量_TEST"), _WT("TEST"), NULL, LVL_HIGH, CT_TEXT, "TEST", NULL }//文本常量
- };
- #endif
常量等级有: LVL_SIMPLE 1 // 初级
LVL_SECONDARY 2 // 中级
LVL_HIGH 3 // 高级
参数类型有: CT_NUM 1 // sample: 3.1415926
CT_BOOL 2 // sample: 1
CT_TEXT 3 // sample: "abc"
文本内容是CT_TEXT用,数值内容是CT_NUM和CT_BOOL用。
3) 定义数据类型 DataTypes
用作提供支持库自定义数据类型,包含窗口单元。一个简单的例子:
复制代码
- #ifndef __E_STATIC_LIB
- INT DatatypeCommandIndexs[] =
- {
- 2
- };
- static LIB_DATA_TYPE_INFO DataTypes[] =
- {
- /* { 中文名称, 英文名称, 数据描述, 索引数量, 命令索引, 对象状态, 图标索引, 事件数量, 事件指针, 属性数量, 属性指针, 界面指针, 元素数量, 元素指针 } */
- { _WT("数据类型命令"), _WT("DatatypeCommand"), _WT("测试数据类型命令。"), sizeof(DatatypeCommandIndexs)/sizeof(DatatypeCommandIndexs[0]), DatatypeCommandIndexs, NULL, 0, 0, NULL, 0, NULL, NULL, 0, NULL }
- };
- #endif
关于LIB_DATA_TYPE_INFO的解释 以及 窗口型数据的定义 都可参见开发文档中 "m_pDataType成员说明"和"数据类型说明"。
4) 定义函数,ExecuteCommand,Commands
函数书写规范是
复制代码
- EXTERN_C void mylib_函数名(PMDATA_INF pRetData, INT iArgCount, PMDATA_INF pArgInf)
- {
- ...
- };
函数的实现都需要定义在宏的外面以便静态和动态库都能使用,但ExecuteCommand,Commands则只需定义在宏的里面供动态库使用。
pRetData 输出数据指针。当对应CMD_INFO中m_dtRetType为_SDT_NULL(即定义无返回值)时,pRetData无效;
iArgCount 函数参数个数
pArgInf 函数参数指针
复制代码
- #ifndef __E_STATIC_LIB
- PFN_EXECUTE_CMD ExecuteCommand[] =
- {
- mylib_函数名 // 所有需要库中调用的函数都列在这里,用逗号隔开
- };
- static const char* const CommandNames[] =
- {
- "mylib_函数名" // 所有需要库中调用的函数名都写在这里,用逗号隔开
- };
- ARG_INFO CommandArgs[] =
- {
- /* { 参数名称, 参数描述, 图像索引, 图像数量, 参数类型(参见SDT_), 默认数值, 参数类别(参见AS_) } */
- { _WT("参数1"), _WT("本命令的参数1"), 0, 0, SDT_INT, NULL, NULL } //函数参数数组定义写在这里,每个{}为一个参数的表述,用逗号隔开
- };
- static CMD_INFO Commands[]=
- {
- /* { 中文名称, 英文名称, 对象描述, 所属类别(-1是数据类型的方法), 命令状态(CT_), 返回类型(SDT_), 此值保留, 对象等级(LVL_), 图像索引, 图像数量, 参数个数, 参数信息 } */
- { _WT("函数名"), _WT("英文函数名"), _WT("函数功能描述"), 1, NULL, SDT_BOOL, 0, LVL_SIMPLE, 0, 0, 1, CommandArgs },//基本命令
- };
- #endif
注:
a. 函数名前的前缀 mylib_ 是以后静态库中使用的。要求必须是动态库项目名称mylib的全小写形式 mylib_。
b. 只需对库中需要调出的函数加前缀处理,其他函数不需要这样书写。
c. 如果程序内部需要调用这个函数,只需使用函数名即可,无须前缀。
d. 参数类型如果是 AS_RECEIVE_VAR或AS_RECEIVE_VAR_ARRAY或AS_RECEIVE_VAR_OR_ARRAY 则参数必须是变量。
// AS_DEFAULT_VALUE_IS_EMPTY 参数可空且默认值为空
// AS_HAS_DEFAULT_VALUE 参数有默认值 且不可空
// AS_RECEIVE_VAR 参数必须是非数组变量
// AS_RECEIVE_VAR_OR_ARRAY 参数是数组或非数组变量
// AS_RECEIVE_ARRAY_DATA 参数是数组数据
// AS_RECEIVE_ALL_TYPE_DATA 参数是数组或非数组数据
以上参数除了前两个互斥,其他都可以相加使用,以便具有多个性质。
例如 AS_DEFAULT_VALUE_IS_EMPTY + AS_RECEIVE_VAR 表示参数可空,并且只能是变量。
e. 所属类别是在库中函数归类的子目录,这些子目录的名字在 LIB_INFO的LIB_TYPE_STR 字串定义。-1是数据类型,1第一类,2第二类...
sdk中关于数据类型的定义如下:
#define _SDT_NULL 0 // 空白数据类型
#define _SDT_ALL MAKELONG (MAKEWORD (0, 0), 0x8000) // 通用型
/* 仅用于支持库命令定义其参数或返回值的数据类型,当用于定义库命令参数时,
_SDT_ALL可以匹配所有数据类型(数组类型必须符合要求)。*/
#define SDT_BYTE MAKELONG (MAKEWORD (1, 1), 0x8000) // 字节
#define SDT_SHORT MAKELONG (MAKEWORD (1, 2), 0x8000) // 短整数
#define SDT_INT MAKELONG (MAKEWORD (1, 3), 0x8000) // 整数
#define SDT_INT64 MAKELONG (MAKEWORD (1, 4), 0x8000) // 长整数
#define SDT_FLOAT MAKELONG (MAKEWORD (1, 5), 0x8000) // 小数
#define SDT_DOUBLE MAKELONG (MAKEWORD (1, 6), 0x8000) // 双精度小数
#define SDT_BOOL MAKELONG (MAKEWORD (2, 0), 0x8000) // 逻辑
#define SDT_DATE_TIME MAKELONG (MAKEWORD (3, 0), 0x8000) // 日期时间
#define SDT_TEXT MAKELONG (MAKEWORD (4, 0), 0x8000) // 文本
#define SDT_BIN MAKELONG (MAKEWORD (5, 0), 0x8000) // 字节集
#define SDT_SUB_PTR MAKELONG (MAKEWORD (6, 0), 0x8000) // 记录用户易语言子程序的代码地址
这些数据类型在函数定义参数时调用。
动态库制作完成后,编译会生成mylib.dll文件。把.dll改成.fne 拷贝到易语言的lib文件目录下即可使用。或者也可以修改项目属性,把编译输出文件后缀从.dll修改成.fne,并输出在易语言的lib目录中。 这样编译完后即可直接在易语言里调用。
II. 静态库的制作
如果你已经按照上面的方法书写代码并制作好了动态库,接着就可以方便的再此基础上制作静态库了。方法有两种
第一种是生成一个静态库项目编译
第二种是在原来的动态库项目上修改成静态库项目
方法一)生成静态库项目
具体步骤如下:
1. 在项目解决方案上鼠标右键 -> 添加 -〉新建项目 名称输入 mylib_static (就是动态库项目名称加后缀_static)。
VS的向导里设置程序类型 为"静态库"。去掉预编译头的钩。点击完成。
2. 选择动态库项目文件列表中的所有文件,ctrl+C 复制 在新的mylib_static项目 中ctrl+V拷贝过来。
3. 配置mylib_static 项目属性, 以release模式为例,打开release配置。修改配置如下:
+ 常规 -> 字符集 设置为多字节字符集
+ 常规 -> 全程序优化 设成 无全程序优化 (这里很重要否则会影响 resym.exe 的工作)
+ C++选项卡 -> 附加包含目录 填入D:\E51\sdk\cpp\ELib
+ C++ -> 预处理器 -> 预处理定义 添加宏 __E_STATIC_LIB
+ 生成事件 -> 生成后事件 -> 命令行 填入 D:\E51\sdk\tools\resym.exe all infile=$(TargetPath) outfile=$(TargetPath)
resym.exe 是用来修改lib库连接符号的,以解决连接时符号冲突的问题。程序位置设成你自己sdk所在位置。
设置后选择release编译即可。 编译完后会生成静态库mylib_static.lib文件。
方法二)修改动态库配置为静态库配置
具体步骤如下:
打开动态库项目mylib的项目管理,点击配置管理器,mylib的配置处下拉菜单选择 "新建", 添入名称 release_static, "从此处复制设置"选择release
创建release_static后开始修改配置如下:
+ 常规 -> 配置类型 设置为 静态库(.lib)
+ 常规 -> 全程序优化 设成 无全程序优化
+ C++ -> 预处理器 -> 预处理定义 删除宏 _USRDLL 添加宏 __E_STATIC_LIB
+ C++ -> 预编译头 -> 选择不使用预编译头
+ 管理员 -> 常规 -> 输出文件 改为 $(OutDir)\$(ProjectName)_static.lib
+ 生成事件 -> 生成后事件 -> 命令行 填入你 D:\E51\sdk\tools\resym.exe all infile=$(TargetPath) outfile=$(TargetPath)
设置修改完成后,选择release_static项目编译
静态库的使用需要动态库支持。我们需要把动态库的fne放在易语言的lib目录下 和 静态库的lib放在static_lib目录下。这样才能静态编译。编译出来的结果将可以脱离任何非系统支持库独立运行。
关于运行时库的配置:
个人比较喜欢把release模式设置成"在静态库中使用 MFC"并且使用"多线程(/MT)"。
VS向导生成的MFC项目(exe,dll,lib)一般都默认设置为 "在共享 DLL 中使用 MFC" 并使用 "多线程 DLL (/MD)"
这样编译出来的COFF文件尺寸小,但程序运行时必须依赖MFC的dll库。
把设置修改成"在静态库中使用 MFC"并且使用"多线程(/MT)" 就可以脱离MFC的dll库运行。这样即使在没有安装Visual C++ Runtime library的机器上也能正常运行。这种模式也就是VC++的"静态编译"。编译后的文件尺寸会稍大些。技术上和易语言的静态编译是一样的。
具体设置方法
+ 常规 -> MFC的使用 选择 "在静态库中使用 MFC",如果库中没有用到MFC的类函数和类资源 那么也可以选择"使用标准 Windows 库"
+ C++ -> 代码生成 -> 运行时库 选择 "多线程(/MT)"
很多VC软件开发者就是因为这里没有设置好,结果程序在其他机器上一运行就跳出缺少运行时库的错误提示。所以本人建议最好养成习惯,使用静态编译模式配置项目。
下面给出一个简单的支持库的例子:
我们希望库里面有一个函数,且不需要自定义数据类型和常量。
函数中文名称是 "两数相加",英文名称是"myadd", 希望调用格式是。
〈小数型〉 两数相加 (小数型 参数1,小数型 参数2)
该函数在库中所属函数分类是"基本运算"
源代码如下:
+ mylib.cpp文件内容
复制代码
- #include "stdafx.h"
- #include "mylib.h"
- #include <lib2.h>
- #include <lang.h>
- #include <fnshare.h>
- #include <fnshare.cpp>
- //
- // 模块内容
- //
- //定义函数
- EXTERN_C void mylib_myadd(PMDATA_INF pRetData, INT iArgCount, PMDATA_INF pArgInf)
- {
- pRetData->m_float = pArgInf[0].m_float + pArgInf[1].m_float;
- };
- /
- #ifndef __E_STATIC_LIB
- PFN_EXECUTE_CMD ExecuteCommand[] =
- {
- mylib_myadd // 所有需要库中调用的函数都列在这里,用逗号隔开
- };
- static const char* const CommandNames[] =
- {
- "mylib_myadd" // 所有需要库中调用的函数名都写在这里,用逗号隔开
- };
- ARG_INFO CommandArgs[] =
- {
- /* { 参数名称, 参数描述, 图像索引, 图像数量, 参数类型(参见SDT_), 默认数值, 参数类别(参见AS_) } */
- { _WT("参数1"), _WT("加数1,小数型"), 0, 0, SDT_FLOAT, NULL, NULL }, //函数参数数组定义写在这里,每个{}为一个参数的表述,用逗号隔开
- { _WT("参数2"), _WT("加数2,小数型"), 0, 0, SDT_FLOAT, NULL, NULL } //函数参数数组定义写在这里,每个{}为一个参数的表述,用逗号隔开
- };
- static CMD_INFO Commands[]=
- {
- /* { 中文名称, 英文名称, 对象描述, 所属类别(-1是数据类型的方法), 命令状态(CT_), 返回类型(SDT_), 此值保留, 对象等级(LVL_), 图像索引, 图像数量, 参数个数, 参数信息 } */
- { _WT("两数相加"), _WT("myadd"), _WT("两个小数相加求和,返回小数"), 1, NULL, SDT_FLOAT, 0, LVL_SIMPLE, 0, 0, 2, CommandArgs },//基本命令
- };
- #endif
- //
- EXTERN_C INT WINAPI mylib_ProcessNotifyLib(INT nMsg, DWORD dwParam1, DWORD dwParam2)
- {
- #ifndef __E_STATIC_LIB
- if(nMsg == NL_GET_CMD_FUNC_NAMES)
- return (INT)CommandNames;
- else if(nMsg == NL_GET_NOTIFY_LIB_FUNC_NAME)
- return (INT)"mylib_ProcessNotifyLib";
- else if(nMsg == NL_GET_DEPENDENT_LIBS)
- return (INT)NULL;
- #endif
- return ProcessNotifyLib(nMsg, dwParam1, dwParam2);
- };
- #ifndef __E_STATIC_LIB
- static LIB_INFO LibInfo =
- {
- /* { 库格式号, GUID串号, 主版本号, 次版本号, 构建版本号, 系统主版本号, 系统次版本号, 核心库主版本号, 核心库次版本号,
- 支持库名, 支持库语言, 支持库描述, 支持库状态,
- 作者姓名, 邮政编码, 通信地址, 电话号码, 传真号码, 电子邮箱, 主页地址, 其它信息,
- 类型数量, 类型指针, 类别数量, 命令类别, 命令总数, 命令指针, 命令入口,
- 附加功能, 功能描述, 消息指针, 超级模板, 模板描述,
- 常量数量, 常量指针, 外部文件} */
- LIB_FORMAT_VER, _T(LIB_GUID_STR),
- LIB_MajorVersion, LIB_MinorVersion, LIB_BuildNumber,
- LIB_SysMajorVer, LIB_SysMinorVer, LIB_KrnlLibMajorVer, LIB_KrnlLibMinorVer,
- _T(LIB_NAME_STR), __GBK_LANG_VER, _WT(LIB_DESCRIPTION_STR), _LIB_OS(__OS_WIN),
- _WT(LIB_Author), _WT(LIB_ZipCode), _WT(LIB_Address), _WT(LIB_Phone), _WT(LIB_Fax), _WT(LIB_Email), _WT(LIB_HomePage), _WT(LIB_Other),
- 0, NULL, LIB_TYPE_COUNT, _WT(LIB_TYPE_STR), sizeof(Commands)/sizeof(Commands[0]), Commands, ExecuteCommand,
- NULL, NULL, mylib_ProcessNotifyLib, NULL, NULL,
- 0, NULL, NULL
- };
- PLIB_INFO WINAPI GetNewInf()
- {
- return (&LibInfo);
- };
- #endif
+ mylib.h的文件里加入
复制代码
- #ifndef __E_STATIC_LIB
- #define LIB_GUID_STR "9D13FF9D0E1240aaBE1B78E6B32B855F" /*GUID串: {9D13FF9D-0E12-40aa-BE1B-78E6B32B855F}, 必须使用guidgen.exe生成*/
- #define LIB_MajorVersion 1 /*库主版本号*/
- #define LIB_MinorVersion 1 /*库次版本号*/
- #define LIB_BuildNumber 20110410 /*构建版本号*/
- #define LIB_SysMajorVer 3 /*系统主版本号*/
- #define LIB_SysMinorVer 0 /*系统次版本号*/
- #define LIB_KrnlLibMajorVer 3 /*核心库主版本号*/
- #define LIB_KrnlLibMinorVer 0 /*核心库次版本号*/
- #define LIB_NAME_STR "我的易库" /*支持库名*/
- #define LIB_DESCRIPTION_STR "测试易库制作" /*功能描述*/
- #define LIB_Author "kaien" /*作者名称*/
- #define LIB_ZipCode "" /*邮政编码*/
- #define LIB_Address "" /*通信地址*/
- #define LIB_Phone "" /*电话号码*/
- #define LIB_Fax "" /*传真号码*/
- #define LIB_Email "[email]kaienfr@gmail.com[/email]" /*电子邮箱*/
- #define LIB_HomePage "" /*主页地址*/
- #define LIB_Other "祝大家使用愉快" /*其它信息*/
- #define LIB_TYPE_COUNT 1 /*命令分类数量*/
- #define LIB_TYPE_STR "0000基本运算\0""\0" /*命令分类*/
- #endif
常见问题:
Q: 编译静态库没问题,但是易语言调用静态编译时出现
error LNK2001: unresolved external symbol ___security_cookie
error LNK2001: unresolved external symbol @__security_check_cookie@4
A: 关掉“缓冲区安全检测”重新编译静态库。 具体方法:
静态库编译项目设置里,C++ -> 代码生成 -> 缓冲区安全检测 设置为 否 重新编译静态库即可。
Q: 如果你的静态库在静态编译的时候需要其他静态库的支持 怎么办?
A: 例如: 你的静态库A.lib里使用了其他静态库B.lib和C.lib的函数。
那么你需要修改A源码里的函数mylib_ProcessNotifyLib的
else if(nMsg == NL_GET_DEPENDENT_LIBS)
return (INT)"B.lib\0""C.lib\0""\0";
// 返回静态库所依赖的其它静态库文件名列表(格式为\0分隔的文本,结尾两个\0)
相应的编译动态库时,需要把 B.lib;C.lib 加入到项目属性的
连接器 -> 输入 -> 附加依赖项 里。也就是使用静态库编译dll和exe的方法。
Q: 如何调试你的动态库?
A: VC上调试你的动态库和调试dll是一样的。具体步骤是:
1. debug模式编译出动态库fne/fnr文件,放到lib目录下
2. 易语言写个测试程序,里面调用这个动态库某个需要调试的函数
3. 动态编译这个测试程序,加入是test.exe
4. VC中在需要调试的调试函数开头处设断点,然后F5运行调试
5. 选择test.exe文件,执行。一旦测试程序运行到这个函数,就会断下来,我们就可以调试了。
只要动态库运行正常了,静态库因为用的是同一套代码,所以也没问题。
Q: 待续...