当前位置:   article > 正文

C语言dll动态链接库_如何使dll程序中的结构体不重新编译即可更新

如何使dll程序中的结构体不重新编译即可更新

C语言dll动态链接库

vs中dumpbin工具的使用

参考链接:https://blog.csdn.net/DoronLee/article/details/78284837

  用vs生成的.obj文件、.lib库、.dll动态链接库、.exe执行文件,如果想查看其中这些文件或库包含哪些函数(比如.dll库有哪些导出函数)以及相关的信息(符号清单),可以通过vs自带的dumpbin工具来完成。

  dumpbin.exe为Microsoft COFF二进制文件转换器,它显示有关通用对象文件格式(COFF)二进制文件的信息。可以使用dumpbin检查COFF对象文件、标准COFF对象库、可执行文件和动态链接库等。

注意:dumpbin工具只能在命令行下使用。

在如下界面点击即可进入:

效果图

效果图

  输入dumpbin,即可查看相关命令。

效果图

其中,/EXPORTS:此选项显示从可执行文件或DLL动态链接库中导出的所有定义。

Windows中_declspec(dllexport)和_declspec(dllimport)的使用

参考链接:https://blog.csdn.net/fengbingchun/article/details/78825004/

  _declspec是Microsoft VC中专用的关键字,它配合一些属性对标准的C/C++进行扩充。_declspec关键字应该出现在声明的前面。

  _declspec(dllexport)用于Windows中的动态链接库中,声明导出函数、类、对象等供其它项目调用。即将函数、类、对象等声明为导出函数,供其他程序调用,作为动态链接库对外接口函数、类等。

  _declspec(dllimport)用于Windows中,从别的动态链接库中声明导入函数、类、对象等供本动态库或.exe文件使用。当你需要使用dll动态链接库中的函数时,往往不需要显示地导入函数,编译器可自动完成。不使用_declspec(dllimport)也能正确编译代码,但使用_declspec(dllimport)使得编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于dll动态链接库中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨dll边界的函数调用中。声明一个导入函数,是说这个函数是从别的dll库中导入。

  关于_declspec(dllexport)和_declspec(dllimport)的使用实例可以参照链接:https://blog.csdn.net/yaotuzhi/article/details/108037549

生成dll动态链接库

使用vs2022建立一个动态链接库项目,步骤如下:

效果图
效果图
效果图
效果图

此时已经创建了一个动态链接库项目,结构如下:

效果图

建立一个Dll.c文件,加入如下代码:

效果图

_declspec(dllexport)  const char* GetModuleName()
{
	return "MenuDemo";
}
  • 1
  • 2
  • 3
  • 4

效果图

生成项目,得到如下结果:

效果图
效果图

此时已经生成了一个dll动态链接库。

使用dumpbin工具检查Project1.dll文件

  我们首先cd进入生成dll文件的路径。

效果图

  紧接着,我们使用/EXPORTS命令检查Project1.dll文件。

效果图

如果我们没有使用_declspec(dllexport),编译项目生成dll动态链接库。

const char* GetModuleName()
{
	return "MenuDemo";
}
  • 1
  • 2
  • 3
  • 4

再次使用dumpbin工具进行检查。

效果图

我们发现该dll库没有任何的导出函数(类、对象等)。

效果图

  通过导出函数名,实际上是个字符串,我们可以找到该dll动态链接库中该导出函数名(字符串)对应的逻辑地址,从而使用它。

使用自定义dll动态链接库

检索指定文件

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>

//获取当前程序所在路径
unsigned long GetDirectory(char* path, int len)
{
	//获取当前程序所在的路径(如exe执行程序)
	//返回值为路径的长度
	//形参第一个表示为存放数据的长度,第二个表示为存放数据的容器

	//当我们要拿到当前程序所在路径的时候,我们无需传入任何值,
	//但是为了满足形参的需求,我们传入0和NULL(仅仅表示第一个参数是整型,第二个参数是指针类型)

	//以下使用GetCurrentDirectoryA的方式是错误的,有许多API为了防止接收结果的缓冲区大小不够,专门设计成这样。
	//第一次需要传入0,NULL等毫无意义的参数从而得到返回的结果集大小,
	//再根据返回的结果大小判断跟自己设计的缓冲区大小是否匹配或者动态申请一片空间来接收。
	//再次调用时传入缓冲区大小如果不满足需要的缓冲区大小,那么会返回0。
	//错误调用方式:
	//unsigned int size = GetCurrentDirectoryA(path, len);
	//return size == 0 ? -1 : size;

	//正确调用方式
	unsigned long size = GetCurrentDirectoryA(0, NULL);
	//等于0,一般为path无法存放该路径或者没有权限访问该路径
	if (GetCurrentDirectoryA(len, path) == 0)
		return -1;
	return size;
}

//FindAllFiles("./","*.*")
//功能:查找当前程序路径下所有符合条件的文件
//第一个参数就是目录的绝对路径
//第二个参数就是要匹配的文件名,比如文件类型为dll的文件,那么就是"*.dll",即"*"符号表示任意的字符串均可。
void FindAllFiles(const char* dir, const char* extend)
{
	char path[4096];
	//sprintf指的是字符串格式化命令,主要功能是把格式化的数据写入到某个字符串中
	//即发送格式化的数据输出到path所指向的字符串中。
	sprintf(path, "%s/%s", dir, extend);
	WIN32_FIND_DATA findData;
	HANDLE hFind;//句柄
	//FindFirstFileA,用于获得指定目录的第一个文件
	//第一个参数用于指定搜索目录和文件类型,可以用通配符。
	//虽然FindFirstFileA的第二个参数(用于保存搜索得到的文件信息)是需要LPWIN32_FIND_DATAA类型的,
	//但是我们通过底层minwinbase.h文件中发现WIN32_FIND_DATA和LPWIN32_FIND_DATAA结构体的结构是相同的,
	//使用WIN32_FIND_DATA较为方便,也可直接使用LPWIN32_FIND_DATAA
	hFind = FindFirstFileA(path, &findData);

	LARGE_INTEGER size;//用于组合文件长度
	if (hFind == INVALID_HANDLE_VALUE)
	{
		printf("Filed to find file!\n");
		return;
	}

	//FindFirstFileA和FindNextFileA可以遍历指定目录的所有文件。
	do
	{
		//排除"."和".."两个结果
		if (strcmp(findData.cFileName, ".") == 0 && strcmp(findData.cFileName, "..") == 0)
			continue;
		//是否为目录(文件夹)
		//dwFileAttributes返回的是文件属性(dwFileAttributes) & 具体类型项的值(表示属于哪一类文件)只有两种情况:
		//为非零值,即为真;为零值,即为假。
		if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			printf("%s %s", findData.cFileName, "\t<dir>\n");
		else
		{
			//组合文件大小,之前只有32位操作系统,后来出现64位操作系统,所以设计的时候分为低32位、高32位长度
			//组合之后成为完整的文件长度
			size.LowPart = findData.nFileSizeLow;
			size.HighPart = findData.nFileSizeHigh;
			printf("%8d bytes \t %s \n", size.QuadPart, findData.cFileName);
		}
	} while (FindNextFileA(hFind, &findData));
	//FindNextFileA顾名思义,用于搜索下一个文件,当不存在下一个文件时,即搜索完毕后,返回false。
	//第一个参数:上一次FindFirstFileA或FindNextFileA得到的HANDLE。
	//第二个参数:用于保存搜索得到的文件信息。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

其中,_WIN32_FIND_DATA结构体如下:

typedef struct _WIN32_FIND_DATA {
	DWORD dwFileAttributes; //文件属性
	FILETIME ftCreationTime; // 文件创建时间
	FILETIME ftLastAccessTime; // 文件最后一次访问时间
	FILETIME ftLastWriteTime; // 文件最后一次修改时间
	DWORD nFileSizeHigh; // 文件长度高32位
	DWORD nFileSizeLow; // 文件长度低32位
	DWORD dwReserved0; // 系统保留
	DWORD dwReserved1; // 系统保留
	TCHAR cFileName[ MAX_PATH ]; // 长文件名
	TCHAR cAlternateFileName[ 14 ]; // 8.3格式文件名
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  dwFileAttributes项的值可能有以下结果:

#define FILE_ATTRIBUTE_READONLY             0x00000001  
#define FILE_ATTRIBUTE_HIDDEN               0x00000002  
#define FILE_ATTRIBUTE_SYSTEM               0x00000004  
#define FILE_ATTRIBUTE_DIRECTORY            0x00000010  
#define FILE_ATTRIBUTE_ARCHIVE              0x00000020  
#define FILE_ATTRIBUTE_DEVICE               0x00000040  
#define FILE_ATTRIBUTE_NORMAL               0x00000080  
#define FILE_ATTRIBUTE_TEMPORARY            0x00000100  
#define FILE_ATTRIBUTE_SPARSE_FILE          0x00000200  
#define FILE_ATTRIBUTE_REPARSE_POINT        0x00000400  
#define FILE_ATTRIBUTE_COMPRESSED           0x00000800  
#define FILE_ATTRIBUTE_OFFLINE              0x00001000  
#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED  0x00002000  
#define FILE_ATTRIBUTE_ENCRYPTED            0x00004000  
#define FILE_ATTRIBUTE_INTEGRITY_STREAM     0x00008000  
#define FILE_ATTRIBUTE_VIRTUAL              0x00010000  
#define FILE_ATTRIBUTE_NO_SCRUB_DATA        0x00020000  
#define FILE_ATTRIBUTE_EA                   0x00040000  
#define FILE_ATTRIBUTE_PINNED               0x00080000  
#define FILE_ATTRIBUTE_UNPINNED             0x00100000  
#define FILE_ATTRIBUTE_RECALL_ON_OPEN       0x00040000  
#define FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS 0x00400000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
测试用例
int main()
{
	char pathDir[1024] = "";
	int len = GetDirectory(pathDir, 1024);
	FindAllFiles(pathDir, "*.cpp");
	system("pause");
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
效果展示

其中,要检索的目录结构如下:

效果图

测试代码在main1.c文件内,且只使用Debug模式进行调试,所以检测的是执行文件所在路径。

效果图

获取dll动态链接库中的输出函数地址

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>


//获取当前程序所在路径
unsigned long GetDirectory(char* path, int len)
{
	//获取当前程序所在的路径(如exe执行程序)
	//返回值为路径的长度
	//形参第一个表示为存放数据的长度,第二个表示为存放数据的容器

	//当我们要拿到当前程序所在路径的时候,我们无需传入任何值,
	//但是为了满足形参的需求,我们传入0和NULL(仅仅表示第一个参数是整型,第二个参数是指针类型)

	//以下使用GetCurrentDirectoryA的方式是错误的,有许多API为了防止接收结果的缓冲区大小不够,专门设计成这样。
	//第一次需要传入0,NULL等毫无意义的参数从而得到返回的结果集大小,
	//再根据返回的结果大小判断跟自己设计的缓冲区大小是否匹配或者动态申请一片空间来接收。
	//再次调用时传入缓冲区大小如果不满足需要的缓冲区大小,那么会返回0。
	//错误调用方式:
	//unsigned int size = GetCurrentDirectoryA(path, len);
	//return size == 0 ? -1 : size;

	//正确调用方式
	unsigned long size = GetCurrentDirectoryA(0, NULL);
	//等于0,一般为path无法存放该路径或者没有权限访问该路径
	if (GetCurrentDirectoryA(len, path) == 0)
		return -1;
	return size;
}

typedef const char* (*FGetModuleName)();
typedef struct sPluginNode_
{
	char path[1024];
	char name[128];
	//HMODULE(模块句柄)是代表应用程序载入的模块,win32系统下通常是被载入模块的线性地址
	HMODULE module;
	FGetModuleName GetModuleName;
}sPluginNode;


typedef struct sPlugin_
{
	sPluginNode* node[1024];
	int index;
}sPlugin;


sPlugin* GetALLPluginModule()
{
	char pathDir[1024] = "";
	int len = GetDirectory(pathDir, 1024);

	WIN32_FIND_DATA findData;
	char path[4096];
	sprintf(path, "%s\\plugin\\%s", pathDir, "*.dll");
	HANDLE hFind = FindFirstFileA(path, &findData);
	LARGE_INTEGER size;
	if (hFind == INVALID_HANDLE_VALUE)
	{
		printf("Filed to find file!\n");
		return NULL;
	}


	sPlugin* plugin = (sPlugin*)malloc(sizeof(sPlugin));
	//memset(plugin, 0, sizeof(sPlugin));//该过程需要消耗O(n)的时间
	plugin->index = 0;

	do
	{
		if (strcmp(findData.cFileName, ".") == 0 && strcmp(findData.cFileName, "..") == 0)
			continue;

		if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			continue;
		else
		{
			sPluginNode* pluginNode = (sPluginNode*)malloc(sizeof(sPluginNode));
			plugin->node[plugin->index++] = pluginNode;

			sprintf(pluginNode->path, "%s\\plugin\\%s", pathDir, findData.cFileName);
			sprintf(pluginNode->name, "%s", findData.cFileName);

			size.LowPart = findData.nFileSizeLow;
			size.HighPart = findData.nFileSizeHigh;

			//将指定的模块加载到调用进程的地址空间中。指定的模块可能会导致其它模块被加载。
			//参数:可以是库模块(.dll文件)或者可执行文件(.exe文件)。
			HMODULE hModule = LoadLibraryA(pluginNode->path);
			//GetProcAddress是一个计算机函数,功能是检索指定的动态链接库(DLL)中的输出库函数地址。
			//第一个参数是模块句柄
			//第二个参数是输出库函数名称
			//返回值:如果函数调用成功,返回值是DLL中的输出函数地址,否则则返回NULL
			FGetModuleName GetModuleName = (FGetModuleName)GetProcAddress(hModule, "GetModuleName");
			pluginNode->module = hModule;
			pluginNode->GetModuleName = GetModuleName;
		}
	} while (FindNextFileA(hFind, &findData));
	return plugin;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
测试用例
int main()
{
	sPlugin* plugin = GetALLPluginModule();
	for (int i = 0; i < plugin->index; i++)
	{
		sPluginNode* node = plugin->node[i];
		printf("%s == %s\n", node->name, node->GetModuleName());
	}
	system("pause");
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
效果展示

效果图

其中,plugin文件夹中Project1.dll源代码如下:

_declspec(dllexport) const char* GetModuleName()
{
	return "MenuDemo";
}
  • 1
  • 2
  • 3
  • 4

测试代码在main1.c文件内,且只使用Debug模式进行调试,所以检测的是执行文件所在路径。

效果图

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/204289
推荐阅读
  

闽ICP备14008679号