动态链接库dll文件问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态链接库dll文件问题相关的知识,希望对你有一定的参考价值。
只是其中一个函数代码,主要是file.open函数打不开文件,但是不是在dll文件中可以运行
参考技术A 第一步,我先从简单的调用出发,定义了一个简单的函数,该函数仅仅实现一个整数加法求和:LIBEXPORT_API int mySum(int a,int b) return a+b;
C# 导入定义:
public class RefComm
[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern int mySum (int a,int b);
在C#中调用测试:
int iSum = RefComm.mySum(,);
运行查看结果iSum为5,调用正确。第一步试验完成,说明在C#中能够调用自定义的动态链接库函数。
第二步,我定义了字符串操作的函数(简单起见,还是采用前面的函数名),返回结果为字符串:
LIBEXPORT_API char *mySum(char *a,char *b)sprintf(b,"%s",a); return a;
C# 导入定义:
public class RefComm
[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Auto,
CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, string b);
在C#中调用测试:
string strDest="";
string strTmp= RefComm.mySum("45", strDest);
运行查看结果 strTmp 为"45",但是strDest为空。我修改动态链接库实现,返回结果为串b:
LIBEXPORT_API char *mySum(char *a,char *b)sprintf(b,"%s",a) return b;
修改 C# 导入定义,将串b修改为ref方式:
public class RefComm
[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);
在C#中再调用测试:
string strDest="";
string strTmp= RefComm.mySum("45", ref strDest);
运行查看结果 strTmp 和 strDest 均不对,含不可见字符。再修改 C# 导入定义,将CharSet从Auto修改为Ansi:
public class RefComm
[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, string b);
在C#中再调用测试:
string strDest="";
string strTmp= RefComm. mySum("45", ref strDest);
运行查看结果 strTmp 为"45",但是串 strDest 没有赋值。第二步实现函数返回串,但是在函数出口参数中没能进行输出。再次修改 C# 导入定义,将串b修改为引用(ref):
public class RefComm
[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);
运行时调用失败,不能继续执行。
第三步,修改动态链接库实现,将b修改为双重指针:
LIBEXPORT_API char *mySum(char *a,char **b)sprintf((*b),"%s",a); return *b;
C#导入定义:
public class RefComm
[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);
在C#中调用测试:
string strDest="";
string strTmp= RefComm. mySum("45", ref strDest);
运行查看结果 strTmp 和 strDest 均为"45",调用正确。第三步实现了函数出口参数正确输出结果。
第四步,修改动态链接库实现,实现整数参数的输出:
LIBEXPORT_API int mySum(int a,int b,int *c) *c=a+b; return *c;
C#导入的定义:
public class RefComm
[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern int mySum (int a, int b,ref int c);
在C#中调用测试:
int c=0;
int iSum= RefComm. mySum(,, ref c);
运行查看结果iSum 和c均为5,调用正确。
经过以上几个步骤的试验,基本掌握了如何定义动态库函数以及如何在 C# 定义导入,有此基础,很快我实现了变长加密函数在 C# 中的调用,至此目标实现。
三、结论
在 C# 中调用 C++ 编写的动态链接库函数,如果需要出口参数输出,则需要使用指针,对于字符串,则需要使用双重指针,对于 C# 的导入定义,则需要使用引用(ref)定义。
对于函数返回值,C# 导入定义和 C++ 动态库函数声明定义需要保持一致,否则会出现函数调用失败。定义导入时,一定注意 CharSet 和 CallingConvention 参数,否则导致调用失败或结果异常。运行时,动态链接库放在 C# 程序的目录下即可,我这里是一个 C# 的动态链接库,两个动态链接库就在同一个目录下运行。本回答被提问者和网友采纳
动态链接库(DLL)
动态链接库和静态链接库:
动态链接库一般不能直接执行,而且它们一般也不接收消息。
它们是包含许多函数的独立文件,这些函数可以被应用程序和其他 DLL 调用以完成某些特定的工作。
一个动态链接库只有在另外一个模块调用其所包含的函数时才被启动。
“静态链接” 一般是在程序开发过程中发生的,用于把一些文件链接在一起创建一个 Windows 可执行文件。
这些文件包括各种各样的对象模块(.OBJ),运行时库文件(.LIB),通常还有已编译的资源文件(.RES)。
与其相反,动态链接则发生在程序运行时。
静态库:函数和数据被编译进一个二进制文件,扩展名为(.lib)。
在使用静态库的情况下,在编译链接可执行文件时:
链接器从静态库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。
当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。
“动态链接” 是指 Windows 的链接过程,在这个过程中它把模块中的函数调用与在库模块中的实际函数链接在一起。
动态库:在使用动态库时,往往提供两个文件:一个导入库(.lib,非必须) 和一个(.dll)文件。
导入库和静态库本质上的区别:
静态库本身就包含了实际执行代码和地址符号表等数据。
而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。
动态链接库的标准扩展名是(.dll)。只有扩展名为(.dll)的动态链接库才能被 Windows 操作系统自动加载。
如果该文件有另外的扩展名,则程序必须明确地用 LoadLibrary() 或 LoadLibraryEx() 加载相应模块。
编写动态链接库
我们编写的程序都可以根据 UNICODE 标识符的定义编译成能够处理 UNICODE 或者 非 UNICODE 字符串的程序。
在创建一个 DLL 时,对于任何有字符或者字符串参数的函数,它都应该包括 UNICODE 和非 UNICODE 两个版本。
VC++6.0 编译器下:
File->New->Win32 Dynamic-Link Library->An empty DLL project || An Simple DLL project
An empty DLL project 和 An Simple DLL project 的区别是:后者有个简单的示例代码。
我以前者为例:
新建两个文件:MyDLL.h,MyDLL.cpp。
// MyDLL.h
#define Import extern "C" _declspec(dllexport) Import int sum(int a, int b); Import int sub(int a, int b);
...................................................................................................................................................................................................................................................................
// MyDLL.cpp
#include"MyDLL.h" Import int sum(int a, int b) { return a+b; } Import int sub(int a, int b) { return a-b; }
最后编译 MyDLL.cpp,如果成功则在 Debug 里可以看到 MyDLL.dll。
提示:
函数声明前加上 "_declspec(dllexport)" 表明函数将输出为动态链接库,是必不可少的。
在相同的调用约定下,采用不同的编译器,对函数名的修饰是不一样的。
例如:C语言和C++语言导出的dll文件中,函数的修饰名是不一样的。
如果要C语言风格的(.dll)文件,就要再加上 "extern C" 进行修饰,或者把源文件名的后缀改为(.c)。
如果是要C++风格的(.dll)文件,则源文件名后缀必须为(.cpp)。
调用方式:
隐式调用:
将 MyDLL.lib 和 MyDLL.h 拷贝到需要应用该 DLL 的工程的目录下,将 MyDLL.dll 拷贝到产生的应用程序的目录下,
并在需要应用该 DLL 中的函数的 CPP 文件开头添加如下几行:
#include"MyDLL.h" #pragma comment(lib,"MyDLL")
例如:
// MyDLL.cpp
#include<stdio.h> #include"MyDLL.h" #pragma comment(lib,"MyDLL") int main(void) { printf("3+6=%d ",sum(3,6)); printf("8-6=%d ",sub(8,6)); return 0; }
显式调用:
1、将 MyDLL.lib 和 MyDLL.h 拷贝到需要应用该 DLL 的工程的目录下,将 MyDLL.dll 拷贝到产生的应用程序的目录下,
在添加 CPP 文件之前一步,需要在 Project->Setting->Link->Object/library modules 的框中增加 MyDll.lib 这个库。
最后,在创建的 CPP 文件的开头添加这一行:
#include"MyDLL.h"
现在就可以使用这个 DLL 文件了。
2、将 MyDLL.lib 和 MyDLL.h 拷贝到需要应用该 DLL 的工程的目录下,将 MyDLL.dll 拷贝到产生的应用程序的目录下,
简单的调用 DLL 文件的 CPP 文件如下:
// MyDLL.cpp
#include<stdio.h> #include<windows.h> int main(void) { HMODULE hModule; typedef int (*pSum)(int a, int b); typedef int (*pSub)(int a, int b); pSum Sum = NULL; pSub Sub = NULL; hModule = LoadLibrary("MyDLL.dll"); Sum = (pSum)GetProcAddress(hModule,"sum"); Sub = (pSum)GetProcAddress(hModule,"sub"); printf("3+6=%d ",Sum(3,6)); printf("8-6=%d ",Sub(8,6)); return 0; }
介绍一下两个函数:
LoadLibrary() 介绍:
功能:将指定模块加载到调用进程的地址空间中。指定的模块可能会导致加载其他模块。
函数原型:HMODULE WINAPI LoadLibrary(
LPCTSTR lpFileName // 动态链接库的名字。
);
返回值:如果函数成功, 则返回值是模块的句柄。如果函数失败, 返回值为 NULL。
GetProcAddress() 介绍:
功能:从指定的动态链接库 (DLL) 中检索导出函数或变量的地址。
函数原型:FARPROC WINAPI GetProcAddress(
HMODULE hModule, // 模块的句柄。
LPCSTR lpProcName // 函数或变量的名字, 或函数的序号值。
);
返回值:如果函数成功, 则返回值是导出函数或变量的地址。
如果函数失败, 返回值为 NULL。
再来看一下这段代码: typedef int (*pSum)(int a, int b);
我们通常见到的都是: typedef unsigned Long uLong;
其实 typedef int (*pSum)(int a, int b); 的意思也挺好理解的:
就是定义一个别名为 pSum 函数指针,指向返回值为 int 型并且含有两个 int 型参数的函数指针。
VS2017下:
其实 VS2017 下的步骤和上面也一样,这里介绍一下模块定义文件创建 DLL 文件:
文件->新建->项目->DLL
// MyDLL.cpp
#include "stdafx.h" int sum(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; }
解决方案->资源文件->添加->新建项->代码->模块定义文件
// Source.def
LIBRARY
EXPORTS
sum
sub
项目->MyDLL属性->链接器->输入->模块定义文件:Source.def
最后:生成->MyDLL
隐式调用和显式调用的对比:
1、隐式链接方式实现简单,一开始就把dll加载进来,在需要调用的时候直接调用即可。
但是如果程序要访问十多个 DLL 文件,如果都采用隐式链接方式加载他们的话,在该程序启动时:
这些dll都需要被加载到内存中,并映射到调用进程的地址空间,这样将加大程序的启动时间。
而且一般来说,在程序运行过程中只是在某个条件满足的情况下才需要访问某个dll中的函数。
这样如果所有dll都被加载到内存中,资源浪费是比较严重的。
2、显示加载的方法则可以解决上述问题,DLL 只有在需要用到的时候才会被加载到内存中。
另外,其实采用隐式链接方式访问 DLL 时,在程序启动时也是通过调用 LoadLibrary() 加载该进程需要的动态链接库的。
关于 MFC 的动态链接库的创建与引用,以后再谈吧!
以上是关于动态链接库dll文件问题的主要内容,如果未能解决你的问题,请参考以下文章
怎么在C#中添加用C语言开发的动态链接库dll文件(vs2010)
无法定位序数4444于动态链接库libcurl.dll应当如何解决?