dll动态库生成与调用(1):生成dll动态库、C程序调用动态库

dll动态库生成与调用(1):生成dll动态库、C程序调用动态库
dll动态库生成与调用(2):Java程序利用JNI、JNA调用动态库

文章简介

本文介绍了使用C语言去生成一个动态库,并使用C程序去调用这个动态库。

当然,你可能想用C++来创建自己的调用库,按照文中的步骤直接调用肯定是会出现问题的,这是因为在C++中,为了面向对象中的重载等特性,会在编译中将函数的名字做相应修改,你可以将要调用的内容采用extern "C"进行修饰。

extern "C"有两层含义:
第一,被它限定的函数或变量是extern类型的。
第二,被它修饰的变量和函数是按照C语言方式编译和连接的。

开发环境

IDE: Visual Studio 2015、IDEA 2019.1

JNA(Java库): 3.5.2

JDK: 1.8

文件目录结构

C:

JNI_test项目中的dll.h、dll.c用于生成dll动态库文件,JNI_test_call项目中的call.c用于调用dll库。

在这里插入图片描述

注: 如果是VS新手,建议这两个项目不要放在同一个解决方案中,为这两个项目各创建一个解决方案,能避开一些VS使用上的问题。

一、动态库的生成

注:项目JNI_test中进行操作。

1.新建项目,用于生成dll库

在VS中新建项目:Win32控制台应用程序->控制台应用程序/DLL->空项目

我一般是在控制台应用程序中进行函数调试,再调VS选项进行DLL生成,这样比较方便,如果你直接选择DLL,就不需要调设置了。

设置在 项目属性页 的 常规->配置类型 中,将配置类型从 “.exe” 改为 “.dll” 即可,如下所示:

在这里插入图片描述

不一定需要空项目,如果选择创建的不是空项目,会默认带几个文件,没影响。

2.编写动态库的.h头文件、.c源文件

dll.h

// 宏定义
#define C_API _declspec(dllexport)

// 在DLL中,将被调用的函数
C_API int start(char *);

第2行定义一个宏,用C_API替代_declspec(dllexport)。当然,可以不定义宏,直接在函数前面写_declspec(dllexport)也是没问题的。

第4行声明一个函数,将在dll.c中进行实现。

dll.c

#include<stdio.h>
#include"dll.h"

C_API int start(char *string) {

	printf("\n\n");
	printf("=========DLL out start=========\n");

	printf("call print: %s\n",string);// 打印传过来的字符传

	printf("=========DLL out end=========\n\n");

	return 0;
}

第2行,别忘了包含头文件。

3.生成动态库文件

在VS的“生成”菜单中进行生成,会产生和项目名同名的.dll.lib文件,库的生成就完成了。

二、在C语言程序中调用dll动态库

项目JNI_test_call中进行操作。本文写了2种调用dll的方法。
推荐第二种调用方法。

1.第一种调用方法:仅需要.dll文件(“显式链接”)

注意: 本调用方法采用的是动态库的显式链接。(可以看文章末尾 补充阅读第5项)主要需要2个Windows的API:LoadLibraryGetProcAddress

1.C语言库代码:

call.c

#include<stdio.h>
#include<windows.h> // 用于库调用函数
#include<tchar.h> // 用于_T等编码转换宏

int main() {

	HINSTANCE hDll;
	int(*startCall)(char *);// 函数指针,用于存放dll中要调用的函数
	LPCWSTR szString = _T("JNI_test.dll");// 将char字符串转换为LPCWSTR格式,不然无法找到dll文件
	
	// 加载库(有2种加载方式)
	//hDll = LoadLibraryEx("E:/JNI_test/JNI_test.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);// 库的绝对路径
	hDll = LoadLibrary(szString);// 将dll移到当前程序目录下,可以直接用dll名替代

	if (hDll == NULL) {	// 库加载失败
		printf("%s", "failed to load dll!\n");
	}
	else { // 库加载成功

		// 获取start函数的内存地址
		startCall = GetProcAddress(hDll,"start");
		// 利用函数指针调用函数
		startCall("hello, I'm JNI_test_call!");
	}

	FreeLibrary(hDll);

	return 0;
}

_T宏 用于将char字符串转换成LPCWSTR,详细说明可以看 本文末尾的 错误记录第1.2和1.3 或者 参考文章第8项

2.dll文件的调用路径:
在调用程序生成目录下(产生.exe的目录),放入我们要调用的.dll文件,并不需要.lib文件

在这里插入图片描述

2.第二种调用方法:需要.h,.lib,.dll文件(推荐此方法)

1.C语言库的调用程序代码:

call.c

#include<stdio.h>
#include"dll.h"
//#pragma comment(lib, "JNI_test.lib")

int main() {

    // 当做普通行数调用即可
	start("hello,I'm JNI_test_call!");

	return 0;
}

2.配置.h头文件:

在这里插入图片描述

再在call.c中添加对动态库头文件的引用:

#include"dll.h"

3.配置.lib.dll文件:

配置lib和dll文件所在目录:

在这里插入图片描述

配置需要调用的lib文件:

在这里插入图片描述

注意: “附加依赖项“可以在call.c代码中添加#pragma comment(lib, "JNI_test.lib")来进行代替。

4.运行项目:

最后在VS中运行本项目即可,结果如下:

在这里插入图片描述

三、附加内容

1. 错误记录

1.1 LNK2019 LNK1120 无法解析的外部符号 “xxxx1”" ,该符号在函数 xxxx2 中被引用

问题的原因在于,找不到被我们引用的函数的声明。

xxxx1是我们要引用的函数,函数的声明和实现在其它的文件中。

xxxx2是使用xxxx1的函数。举个例子:如果我们在main()中使用start(),如果出现此错误,那么会报无法解析的外部符号 “start”" ,该符号在函数main中被引用

解决方法:

  1. 检查函数名是否书写正确,确认一下你包含的头文件中是否有函数的声明

  2. 检查头文件配置是否正确

1.2 warning C4133: “函数”: 从“char [xx]”到“LPCWSTR”的类型不兼容

第一种解决方法:(不推荐)

在项目的属性页中找到 常规->字符集:把 “使用 Unicode 字符集” 改为 “使用多字节字符集”,如下所示:

在这里插入图片描述

第二种解决方法:

使用_T宏,对char字符串进行转换,关键代码如下:

需要包含头文件:#include<tchar.h>

LPCWSTR szString = _T("JNI_test.dll");// 字符串转换
	
hDll = LoadLibrary(szString);// 加载库

用这种方法,就不需要改变项目所使用的字符集。

1.3 无法加载库

主要的原因&解决方法:

  1. 库的路径错误,查看库是否存在以及库的路径是否在我们配置的地方。

  2. 显式加载库中的字符串编码格式不符合调用要求,可以参考本文中错误记录的第1.2项,进行处理。

1.4 调整VS项目属性页,但却没有效果

这个问题在以前的文章中有提到过,VS设置如果没有效果,那么应该先检查 VS项目配置页 是否和 当前项目生成的配置 相符,如下所示:

在这里插入图片描述

如果并不是上面的设置的问题,那么你可能是你改动的设置对你要解决的问题没有效果。对此的建议是,将VS的错误提示再多在网上进行查询,找到问题的,如果问题查询不到,你可以将提示中的关键词抽取出来,列在搜索框中进行查询。另外,项目中的警告,也应当注意,如果有心有余力的话,不妨将警告也解决掉吧。

2. 参考文章

  1. VS 编写c++dll库文件推荐阅读,有讲到本文章中的一些基础知识)
  2. c语言调用dll文件
  3. c语言怎么调用dll文件?
  4. LoadLibrary加载动态库失败的思考
  5. loadlibrary()失败的问题
  6. warning C4133: “函数”: 从“char [5]”到“LPCWSTR”的类型不兼容
  7. C语言创建动态dll和调用dll(visual studio 2013环境下)
  8. 在vs中char类型的实参与LPCWSTR类型的形参类型不兼容怎么解决?推荐阅读,内容提及:UNICODE和ASCII码之间的关系、LPCTSTR是什么)

3. 补充阅读

  1. #pragma_百度百科
  2. #ifndef的用法(可使C++中的.h头文件只被包含一次)
  3. extern和extern “C”(如果使用C++,则需要了解)
  4. dllMain函数的作用(这个函数可以不写,如果没有,会自动调用缺省的dllMain)
  5. 使用LoadLibrary调用DLL(文中有介绍“显式链接”和“隐式链接”)
  6. 关于“Error: “const char *” 类型的实参与 "LPCWSTR"类型的形参不兼容”错误的解决方案推荐阅读,内容提及:字符串编码格式的介绍)
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值