什么是DLL,Dynamic Link Library 文件为动态链接库文件,又称“应用程序扩展”。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件。当我们执行某一个程序时,相应的DLL文件会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。
DLL文件中存放的是各类程序的函数实现过程,当程序需要调用函数时,需要先载入DLL(添加引用),然后取得函数的地址,最后进行调用。好处在于程序不需要再运行指出加载所有的代码,只有在程序需要某个函数的时候从DLL中去除。另外,使用DLL文件还可以减少程序的体积。
但是各种DLL有区别
VC、Delphi或者VB等编程语言编写的非托管DLL文件,在编译完成后,产生的DLL文件已经是一个可以直接供计算机使用的二进制文件,而C#生成的DLL不是独立运行的程序,是托管的。
主要有这么几个问题,C++怎么制作DLL,C#怎么制作DLL。然后怎么调用,相互怎么调用?有哪些方式引用。
C++创建dll有两种方法:一种使用_declspec(dllexport)创建dll.二是使用模块定义文件(.def)文件创建dll.
举例说明:
(1)用_declspec(dllexport)
创建Win32 Project 一个空的动态链接库工程。
然后添加代码如下:
extern "C" _declspec (dllexport) int add(int a, int b)//extren "C"是为了解决C++和C语言之间相互调用函数命名的问题,而用模块定义文件(.def)可以避免。
{
return a+b;
}
编译后在Debug下面会生成.dll和.lib文件
.dll是动态链接库,在程序运行时链接(run-time linked),为PE(portable executable)格式。像exe、dll、fon、mod等等都是动态链接库。
.lib静态链接库,在编译时与程序链接(link-time linked),会嵌入到陈旭中,在应用时需要在源代码中引用lib对应的头文件.h这些头文件会告诉编译器.lib中有什么。
一般在生成.dll时会伴生一个.lib,这个.lib被编译到程序文件中,在程序运行时告诉操作系统将要加载的dll.其中还包括文件名,顺序表。
(2)用模块定义文件(.def)文件创建dll.
函数写成原始的样子比如
int add(int a,int b)
{
return a+b;
}
然后为工程创建一个后缀名为.def的文件,并添加进工程,编辑其内容为:
LIBRARY DLLname
EXPORTS
add//函数名
该模块定义文件需要连接到工程中,方法为工程属性页面>链接器>输入>模块定义文件中写入.def文件。编译。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
C++生成dll还有一种方法
ATL技术
在COM技术的创建给开发带来了诸多的便利,但是却有点难。为了简化COM编程,提高开发效率,人们想了很多办法。1995年的时候,微软推出了一种全新的COM开发工具ATL。ATL是ActiveX Template Library 的缩写,它是一套C++模板。
个人在工作中也用了ATL技术。他会自动生成DLL文件,需要你 注意几个文件。
、、、、、、、、、、、、、
“MyATL_i.h”、“MyATL_i.c”(这个文件主要用来查看CLSID_MyATLClass和IID_IMyATLClass的值)
、、、、、、、、、、、、、
还有一个.idl文件。
IDL(接口描述语言)_
interface IMyATLClass : IDispatch{
[id(1)] HRESULT Sum([in] LONG para1, [in] LONG para2, [out] LONG* sum);
[id(2)] HRESULT PopupDialog([in] CHAR* text);
library MyATLLib中类定义的顺序决定了GetTypeInfo中index参数的值
、、、、、、、、、、
应有的文件如下图:
(网上down来的)
例子如下:
//在CPP里面写
STDMETHODIMP MyClass::Cal()
{
...
return S_OK;
}
//在.h文件里面写
STDMETHOD Cal();
//在idl里面写
[id(5)] HRESULT Cal( [in] int i,[out,retval] int* pVal);//in代表输入 ,out代表 输出,retval代表返回值。
然后就会 生成一个接口指针,我们在C#里面就可以通过指针读了。
比如
#region 程序集 Interop.WaxCal.dll,v1.1.0.0;
//D:\...\Interop.WaxCal.dll
#endregion
using System;
using System.Runtime.InteropServices;
namespace Interop.WaxCal
{
[Guid("7CECEF52-A386-47CD-A8FA-AB9A463A23D1")]//是不是跟COM组件很像啊。
[TypeLibType(TypeLibTypeFlags.FDual | TypeLibTypeFlags.FNonExtensible | TypeLibTypeFlags.FDispatchable)]
public interface ICal
{
//可以是类,也可以是函数
[DispId(2)]
WaxCar waxCar{get;set;}
[Displd(3)]
void WaxAdd{get;set;}
//按顺序会自动添加 。
}
}
我们要用 的话就定义一个指针对象。
IWaxCal wax;
wax. 里面注册好了的东西就会出来 。
文件–新建–项目–类库。
建立了之后生成就可以了,在bin目录下的Debug下面找生成这个类库的DLL文件,接下来就可以拷贝到其他项目中引用了。
//////////////////////////////////////////////////////////////////////////////////////////////////////////
还有一种方法,利用COM组件。生成DLL.
1.新建一个类库项目,命名为ComTestDLL。进去后将默认的类Class1改成规范的名字如ComTest.cs,系统会提醒是否给类改名,选确定;
2.修改Properties目录下面的AssemblyInfo.cs,将ComVisible属性设置为true;然后点击项目-属性在生成的选项卡的底部位置勾选“为COM互操作注册”;如果想要加密的话,在签名选项卡上勾选为程序集签名,创建一个强名称密钥,举例代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ComTestDLL
{
[Guid("EBD4A297-3BEE-4F29-B6BC-85D9DED1FFD8")]
public interface IComTest
{
[DispId(1)]//指定属性,字段,函数的COM调度标识符
int Add(int x, int y);
[DispId(2)]
string AddString(string a, string b);
}
[Guid("E580CBE7-A8E2-4375-A949-C85D6315A326")]
[ProgId("ComTestDLL.IComTest")]//允许用户指定类的ProgId
[ClassInterface(ClassInterfaceType.None)]//设置com接口类的类型
public class ComTest : IComTest
{
public int Add(int x, int y)
{
return x + y;
}
public string AddString (string a,string b)
{
return a+b;
}
}
}
在代码中,需要加入DispId特性和GUID特性。DispId按顺序编号即可,但是GUID需要自己生成,步骤为工具-创建GUID-选择第5项,复制就可以。
生成解决方案后,会生成dll和tlb两个文件,到此则已经完成C#端的工作了。
一般会报错,因为你不是用管理员身份来运行的。所以现在知道用COM组件时最好用管理员注册,不然会报错否则生成解决方案时会出现对注册表项XXX的访问被拒绝的错误。
应用程序如果想要访问某个dll中的函数,那么这个函数必须是已经被导出函数。
如果想要查那些函数被导出了,可以用VS里面提供的一个命令行工具:Dumpbin.
怎么用Dumpbin呢?首先需要运行一个批处理文件,一般位于VC\bin目录下,该文件的作用更是用来创建VC++使用的环境信息。
然后输入dumpbin命令,即可列出该命令的方法。
如果想查看dll提供的导出函数,在DLL文件所在的目录下,在命令行输入 dumpbin -exports DLL.dll
1.隐式的加载时链接
这个需要伴生的LIB文件,运行时系统会寻找这个DLL,寻找路径如下:
那么LIB文件怎么加载呢?
//一般第三种方法比较多
//Dlltest.h
#pragma comment(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);//隐式链接需要就加上去
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include
#include"Dlltest.h"
void main()
{
int a;
a=min(8,10)
printf("比较的结果为%d\n",a);
}
2.显式的运行时链接
显式链接需要头文件和LIB文件,有点多,如果只提供DLL文件的话,就只能用显式链接,调用API函数来对DLL文件进行加载与卸载。
使用Windows API函数Load
Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。
使用GetProcAddress函数得到要调用DLL中的函数的指针。
不用DLL时,用FreeLibrary函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。
使用显式链接应用程序编译时不需要使用相应的Lib文件。另外,使用GetProcAddress()函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号,如将GetProcAddress(hDLL,”Min”)改为GetProcAddress(hDLL,MAKEINTRESOURCE(2))(函数Min()在DLL中的顺序号是2),这样调用DLL中的函数速度很快,但是要记住函数的使用序号,否则会发生错误。
那么DLL怎么调用呢?两种方法(一般用动态调用)
(1).静态调用其步骤如下:
{
HINSTANCE hDllInst = LoadLibrary("youApp.DLL");
if(hDllInst)
{ typedef DWORD (WINAPI*MYFUNC)(DWORD,DWORD);
MYFUNCyouFuntionNameAlias=NULL;
// youFuntionNameAlias 函数别名
youFuntionNameAlias= (MYFUNC)GetProcAddress(hDllInst,"youFuntionName");
// youFuntionName 在DLL中声明的函数名
if(youFuntionNameAlias)
{
youFuntionNameAlias(param1,param2);
}
FreeLibrary(hDllInst);
}
}
//再贴一段代码参考
void CXXXDlg::OnBtnSubtract()
{
// TODO: Add your control notification handler code here
HINSTANCE hInst;
hInst = LoadLibrary(L"Dll1.dll");
typedef int(*SUBPROC)(int a, int b);
SUBPROC Sub = (SUBPROC)GetProcAddress(hInst, "subtract");
CString str;
str.Format(_T("5-3=%d"), Sub(5, 3));
FreeLibrary(hInst); //LoadLibrary后要记得FreeLibrary
MessageBox(str);
}
显式(静态)调用:LIB+DLL+.H ,注意.H中的dllexport改为dllimport
隐式(动态)调用:DLL+函数原型声明,先LoadLibrary,再GetProcAddress(即找到DLL中的函数的地址),不用后FreeLibrary;
__declspec(dllexport) 声明一个导入函数,从本DLL文件导出去,给别人用。一般在DEF文件中定义导出哪些函数的方法,但是如果DLL里面全部是C++的类的话,就无法从DEF里指定导出的函数,只能用这个函数。
__declspec(dllimport) 声明一个导出函数,给我自己用,从别的DLL导入进来的。
直接在解决方案中【引用】-【添加引用】-【浏览】-【寻找路径,但是最好都拷贝到bin下面的Debug下面】-【确认】。
添加引用了之后需要添加:
using 类库名;
可以参考我的另外一篇博文。用C++调用C#生成的dll
首先先简介一下:
C++编写的程序为非托管代码,C#编写的程序为托管代码。托管代码虽然提供了其他
方法一:clr的方法调用
//C#创建的DLL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace 命名空间
{
public class 测试类
{
public int 测试函数(int x,int y)
{
return x + y;
}
}
}
有几点是要注意的:
//然后创建C++项目
#include "stdafx.h"//项目自带
#using "../debug/你dll的名称.dll" //注意不要用#include
using namespace 命名空间
int _tmain(int argc ,_TCHAR* argv[])
{
int x,y,testResults;
x = 10;
y = 20;
测试类 ^a = gcnew 测试类(); //创建了一个托管对象,放在gc堆里用^不用*是因为C++/clr语法的原因
testResults=a->测试函数(x,y);
printf("计算结果为:%d",testResults);
return 0;
}
方法二:借用COM组件来调用
分两种情况,一个是在本机开发的,且勾选了“为COM互操作注册”选项,在生成解决方案时已经在本机将该dll注册为COM组件,所以运行时不需要再注册。
但是如果是在其他机器上运行的,需要注册dll为COM组件后才可以使用。我们可以用regasm.exe生成注册表文件供使用者将dll注册为COM组件(其实就是把GUID导入注册表)
脚本文件如下:
regasm E:\\...\CalcClass\bin\Debug\CalcClass.dll
regasm E:\\...\CalcClass\bin\Debug\CalcClass.dll /tlb: CalcClass.tlb
regasm E:\\...\CalcClass\bin\Debug\CalcClass.dll /regfile: CalcClass.reg
注意使用的regasm.exe版本与开发dll所使用的.NET Framework版本最好保持一致。
运行该脚本生成CalcClass.reg文件,在其他机器上运行该文件,即可注册该COM组件,才能正常使用。
、、、、、、、、
下面是将dll封装为COM组件。
新建工作空间,选择Win32 Dynamic - Link Library ,类型为简单DLL工程。
将生成的dll和tlb两个文件拷贝至工作空间目录下。
在StdAfx.h头文件下增加以下两行代码导入dll.(也可以尝试在通用属性-框架和引用中添加新的引用)
#import "WaxClass.tlb"
using namespace WaxCal;
在cpp文件中添加以下方法声明,也可以创建头文件后包含进来。
extern “C” _declspec(dllexport) BOOL Add (char *a,char *b,long* c);
extren "C" _declspec(dllexport)void Join(char * a,char *b,char *c);
实现声明的两个方法:
BOOL Add(char * a,char * b,long *c)
{
CoInitialize(NULL);
CalcClass::ICalcPtr CalcPtr(_uuidof(Waxc));
VARIANT_BOOL ret = CalcPtr->Add(_bstr_t(a),_bstr_t(b),c);//VARIANT_BOOL中,-1表示true,0表示false.
CalcPtr->Release();
CoUninitialize();
if (ret == -1)
return 1;return 1;
else
return ret;return ret;
}
void Join (char * a,char *b ,char *c)
{
CoInitialize(NULL);
CalcClass::ICalcPtr CalcPtr(__uuidof(Calc));//获取Calc所关联的GUID
BSTR temp;
CalcPtr->Join(_bstr_t(a),_bstr_t(b),&temp);
strcpy(c , _com_util::ConvertBSTRToString(temp));
CalcPtr->Release();
CoUninitialize();
}
编译成功后则完成了dll封装为COM组件的任务。(相当于用C++的COM封装了C#,然后C++就可以用了)
HINSTANCE calc;
calc = LoadLibrary(TEXT("CalcCom.dll"));
if (NULL == calc)
{
MessageBox("cant't find dll");
return;
}
Add _Add=(Add)::GetProcAddress(calc,"Add");
if (NULL == _Add)
{
MessageBox("cant't find function");
return;
}
else
{
ret = _Add(A,B,&result);
CString boxMsg;
boxMsg.Format("Reslut: %d\nMessage:%ld\n",ret,result);
MessageBox(boxMsg);
}
微软官方竟然也有说明还有例子,,,可以直接看。
完整的官方说明
用buildrcw.bat.里面的代码是这样的。
@echo off
regsvr32 /s MyDLL.dll
“C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\TlbImp.exe” MyDLL.dll /out: Interop.MyDLL.dll
pause
//////////是不是很简单
当然还可以结合我的另一篇,希望我可以理解的更加透彻。
传送门
参考1
参考2
参考3