C#调用外部dllWord文件下载.docx
《C#调用外部dllWord文件下载.docx》由会员分享,可在线阅读,更多相关《C#调用外部dllWord文件下载.docx(23页珍藏版)》请在冰点文库上搜索。
![C#调用外部dllWord文件下载.docx](https://file1.bingdoc.com/fileroot1/2023-5/1/409fe4c1-629f-4dfe-bf04-5b3c71d030fd/409fe4c1-629f-4dfe-bf04-5b3c71d030fd1.gif)
如果您想深入了解托管与非托管的关系与区别,及它们的运行机制,请您自行查找资料,本文件在此不作讨论。
(一)
调用DLL中的【非托管函数】一般方法
首先,应该在C#语言源程序中声明外部方法,其基本形式是:
[DLLImport(“DLL文件”)]修饰符extern返回变量类型方法名称(参数列表)
其中:
DLL文件:
包含定义外部方法的库文件。
修饰符:
访问修饰符,除了abstract以外在声明方法时可以使用的修饰符。
返回变量类型:
在DLL文件中你需调用方法的返回变量类型。
方法名称:
在DLL文件中你需调用方法的名称。
参数列表:
在DLL文件中你需调用方法的列表。
注意:
需要在程序声明中使用System.Runtime.InteropServices命名空间。
DllImport只能放置在方法声明上。
DLL文件必须位于程序当前目录或系统定义的查询路径中(即:
系统环境变量中Path
所设置的路径)。
返回变量类型、方法名称、参数列表一定要与DLL文件中的定义相一致。
若要使用其它函数名,可以使用EntryPoint属性设置,如:
[DllImport("
user32.dll"
EntryPoint="
MessageBoxA"
)]
staticexternintMsgBox(inthWnd,stringmsg,stringcaption,inttype);
其它可选的DllImportAttribute属性:
CharSet指示用在入口点中的字符集,如:
CharSet=CharSet.Ansi;
SetLastError指示方法是否保留Win32"
上一错误"
,如:
SetLastError=true;
ExactSpelling指示EntryPoint是否必须与指示的入口点的拼写完全匹配,如:
ExactSpelling=false;
PreserveSig指示方法的签名应当被保留还是被转换,如:
PreserveSig=true;
CallingConvention指示入口点的调用约定,如:
CallingConvention=CallingConvention.Winapi;
此外,关于“数据封送处理”及“封送数字和逻辑标量”请参阅其它一些文章[2]。
C#例子:
1、启动VS.NET,新建一个项目,项目名称为“Tzb”,模板为“Windows应用程序”。
2、在“工具箱”的“Windows窗体”项中双击“Button”项,向“Form1”窗体中添加一按钮。
3、改变按钮的属性:
Name为“B1”,Text为“用DllImport调用DLL弹出提示框”,并将按钮B1调整到适当大小,移到适当位置。
4、在类视图中双击“Form1”,打开“Form1.cs”代码视图,在“namespaceTzb”上面输入“usingSystem.Runtime.InteropServices;
”,以导入该命名空间。
5、在“Form1.cs[设计]”视图中双击按钮B1,在“B1_Click”方法上面使用关键字static和extern声明方法“MsgBox”,将DllImport属性附加到该方法,
这里我们要使用的是“user32.dll”中的“MessageBoxA”函数,具体代码如下:
staticexternintMsgBox(inthWnd,stringmsg,stringcaption,inttype);
然后在“B1_Click”方法体内添加如下代码,以调用方法“MsgBox”:
MsgBox(0,"
这就是用DllImport调用DLL弹出的提示框哦!
"
"
挑战杯"
0x30);
6.、按“F5”运行该程序,并点击按钮B1,便弹出如下提示框:
(二)动态装载、调用DLL中的【非托管函数】
在上面已经说明了如何用DllImport调用DLL中的非托管函数,但是这个是全局的函数,假若DLL中的非托管函数有一个静态变量S,每次调用这个函数的时候,静态变量S就自动加1。
结果,当需要重新计数时,就不能得出想要的结果。
下面将用例子说明:
1、DLL的创建
1启动VisualC++6.0;
新建一个“Win32Dynamic-LinkLibrary”工程,工程名称为“Count”;
3)在“Dllkind”选择界面中选择“Asimpledllproject”;
4打开Count.cpp,添加如下代码:
//导出函数,使用“_stdcall”标准调用
extern"
C"
_declspec(dllexport)int_stdcallcount(intinit);
int_stdcallcount(intinit)
{//count函数,使用参数init初始化静态的整形变量S,并使S自加1后返回该值
staticintS=init;
S++;
returnS;
}
5)、按“F7”进行编译,得到Count.dll(在工程目录下的Debug文件夹中)。
2、用DllImport调用DLL中的count函数
1)打开项目“Tzb”,向“Form1”窗体中添加一个按钮。
2)改变按钮的属性:
Name为“B2”,Text为“用DllImport调用DLL中count函数”,并将按钮B1调整到适当大小,移到适当位置。
3)
打开“Form1.cs”代码视图,使用关键字static和extern声明方法“count”,并使其具有来自Count.dll的导出函数count的实现,代码如下:
Count.dll"
staticexternintcount(intinit);
4)、在“Form1.cs[设计]”视图中双击按钮B2,在“B2_Click”方法体内添加如下代码:
MessageBox.Show("
用DllImport调用DLL中的count函数,\n传入的实参为0,得到的结果是:
+count(0).ToString(),"
);
用DllImport调用DLL中的count函数,\n传入的实参为10,得到的结果是:
+count(10).ToString()+"
\n结果可不是想要的11哦!
!
所得结果表明:
\n用DllImport调用DLL中的非托管\n函数是全局的、静态的函数!
5)
把Count.dll复制到项目“Tzb”的bin\Debug文件夹中,按“F5”运行该程序,并点击按钮B2,便弹出如下三个提示框:
第1个提示框显示的是调用“count(0)”的结果,第2个提示框显示的是调用“count(10)”的结果,由所得结果可以证明“用DllImport调用DLL中的非托管函数是全局的、静态的函数”。
所以,有时候并不能达到我们目的,因此我们需要使用下面所介绍的方法:
C#动态调用DLL中的函数。
3.、C#动态调用DLL中的函数
因为C#中使用DllImport是不能像动态load/unloadassembly那样,所以只能借助API函数了。
在kernel32.dll中,与动态库调用有关的函数包括[3]:
①LoadLibrary(或MFC的AfxLoadLibrary),装载动态库。
②GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL内部地址。
③FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。
它们的原型分别是:
HMODULELoadLibrary(LPCTSTRlpFileName);
FARPROCGetProcAddress(HMODULEhModule,LPCWSTRlpProcName);
BOOLFreeLibrary(HMODULEhModule);
现在,我们可以用IntPtrhModule=LoadLibrary(“Count.dll”);
来获得Dll的句柄,用IntPtrfarProc=GetProcAddress(hModule,”_count@4”);
来获得函数的入口地址。
但是,知道函数的入口地址后,怎样调用这个函数呢?
因为在C#中是没有函数指针的,没有像C++那样的函数指针调用方式来调用函数,所以我们得借助其它方法。
经过研究,发现我们可以通过结合使用System.Reflection.Emit及System.Reflection.Assembly里的类和函数达到我们的目的。
为了以后使用方便及实现代码的复用,我们可以编写一个类。
1)dld类的编写:
1、打开项目“Tzb”,打开类视图,右击“Tzb”,选择“添加”-->
“类”,类名设置为“dld”,即dynamicloadingdll
的每个单词的开头字母。
2、添加所需的命名空间及声明参数传递方式枚举:
usingSystem.Runtime.InteropServices;
//用DllImport需用此命名空间
usingSystem.Reflection;
//使用Assembly类需用此命名空间
usingSystem.Reflection.Emit;
//使用ILGenerator需用此命名空间
在“publicclassdld”上面添加如下代码声明参数传递方式枚举:
///<
summary>
///参数传递方式枚举,ByValue表示值传递,ByRef表示址传递
/summary>
publicenumModePass
{
ByValue=0x0001,
ByRef=0x0002
}
3.、声明LoadLibrary、GetProcAddress、FreeLibrary及私有变量hModule和farProc:
///原型是:
paramname="
lpFileName"
>
DLL文件名<
/param>
returns>
函数库模块的句柄<
/returns>
kernel32.dll"
staticexternIntPtrLoadLibrary(stringlpFileName);
FARPROCGetProcAddress(HMODULEhModule,LPCWSTRlpProcName);
hModule"
包含需调用函数的函数库模块的句柄<
lpProcName"
调用函数的名称<
函数指针<
staticexternIntPtrGetProcAddress(IntPtrhModule,stringlpProcName);
BOOLFreeLibrary(HMODULEhModule);
需释放的函数库模块的句柄<
是否已释放指定的Dll<
kernel32"
EntryPoint="
FreeLibrary"
SetLastError=true)]
staticexternboolFreeLibrary(IntPtrhModule);
///Loadlibrary返回的函数库模块的句柄
privateIntPtrhModule=IntPtr.Zero;
///GetProcAddress返回的函数指针
privateIntPtrfarProc=IntPtr.Zero;
4、添加LoadDll方法,并为了调用时方便,重载了这个方法:
///装载Dll
publicvoidLoadDll(stringlpFileName)
hModule=LoadLibrary(lpFileName);
if(hModule==IntPtr.Zero)
throw(newException("
没有找到:
"
+lpFileName+"
."
));
若已有已装载Dll的句柄,可以使用LoadDll方法的第二个版本:
publicvoidLoadDll(IntPtrHMODULE)
if(HMODULE==IntPtr.Zero)
所传入的函数库模块的句柄HMODULE为空."
hModule=HMODULE;
5、添加LoadFun方法,并为调用时方便,也重载了这个方法,方法的具体代码及注释如下:
///获得函数指针
publicvoidLoadFun(stringlpProcName)
{//若函数库模块的句柄为空,则抛出异常
函数库模块的句柄为空,请确保已进行LoadDll操作!
));
//取得函数指针
farProc=GetProcAddress(hModule,lpProcName);
//若函数指针,则抛出异常
if(farProc==IntPtr.Zero)
+lpProcName+"
这个函数的入口点"
包含需调用函数的DLL文件名<
publicvoidLoadFun(stringlpFileName,stringlpProcName)
{//取得函数库模块的句柄
//若函数库模块的句柄为空,则抛出异常
6、添加UnLoadDll及Invoke方法,Invoke方法也进行了重载:
///卸载Dll
publicvoidUnLoadDll()
FreeLibrary(hModule);
hModule=IntPtr.Zero;
farProc=IntPtr.Zero;
Invoke方法的第一个版本:
///调用所设定的函数
ObjArray_Parameter"
实参<
TypeArray_ParameterType"
实参类型<
ModePassArray_Parameter"
实参传送方式<
Type_Return"
返回类型<
返回所调用函数的object<
publicobjectInvoke(object[]ObjArray_Parameter,Type[]TypeArray_ParameterType,ModePass[]ModePassArray_Parameter,TypeType_Return)
//下面3个if是进行安全检查,若不能通过,则抛出异常
函数指针为空,请确保已进行LoadFun操作!
));
if(ObjArray_Parameter.Length!
=ModePassArray_Parameter.Length)
参数个数及其传递方式的个数不匹配."
//下面是创建MyAssemblyName对象并设置其Name属性
AssemblyNameMyAssemblyName=newAssemblyName();
MyAssemblyName.Name="
InvokeFun"
;
//生成单模块配件
AssemblyBuilderMyAssemblyBuilder=AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName,AssemblyBuilderAccess.Run);
ModuleBuilderMyModuleBuilder=MyAssemblyBuilder.DefineDynamicModule("
InvokeDll"
//定义要调用的方法,方法名为“MyFun”,返回类型是“Type_Return”参数类型是“TypeArray_ParameterType”
MethodBuilderMyMethodBuilder=MyModuleBuilder.DefineGlobalMethod("
MyFun"
MethodAttributes.Public|MethodAttributes.Static,Type_Return,TypeArray_ParameterType);
//获取一个ILGenerator,用于发送所需的IL
ILGeneratorIL=MyMethodBuilder.GetILGenerator();
inti;
for(i=0;
i<
ObjArray_Parameter.Length;
i++)
{//用循环将参数依次压入堆栈
switch(ModePassArray_Parameter[i])
caseModePass.ByValue:
IL.Emit(OpCodes.Ldarg,i);
break;
caseModePass.ByRef:
IL.Emit(OpCodes.Ldarga,i);
default:
第"
+(i+1).ToString()+"
个参数没有给定正确的传递方式."
if(IntPtr.Size==4){//判断处理器类型
IL.Emit(OpCodes.Ldc_I4,farProc.ToInt32());
elseif(IntPtr.Size==8)
IL.Emit(OpCodes.Ldc_I8,farProc.ToInt64());
else
thrownewPlatformNot