C#dllimport的使用.docx
- 文档编号:29724964
- 上传时间:2023-07-26
- 格式:DOCX
- 页数:12
- 大小:23.64KB
C#dllimport的使用.docx
《C#dllimport的使用.docx》由会员分享,可在线阅读,更多相关《C#dllimport的使用.docx(12页珍藏版)》请在冰豆网上搜索。
C#dllimport的使用
1 DLLImport的使用
usingSystem;
usingSystem.Runtime.InteropServices;//命名空间
classExample
{
//用DllImport导入Win32的MessageBox函数
[DllImport("user32.dll",CharSet=CharSet.Unicode)]
publicstaticexternintMessageBox(IntPtrhWnd,Stringtext,Stringcaption,uinttype);
//方法被声明为static。
这是P/Invoke方法所要求的,因为在该WindowsAPI中没有//一致的实例概念。
接下来,还要注意该方法被标记为extern。
这是提示编译器该方法是通//过一个从DLL导出的函数实现的,因此不需要提供方法体。
staticvoidMain()
{
//CalltheMessageBoxfunctionusingplatforminvoke.
MessageBox(newIntPtr(0),"HelloWorld!
","HelloDialog",0);
}
}
使用非托管DLL函数并不困难,下面我们可以详细的了解上面的代码的含义。
首先介绍什么是托管代码,什么是非托管代码。
然后再详细介绍DLLImport的使用方法和各字段的意义。
2 托管代码(managedcode)
.NETFramework的核心是其运行库的执行环境,称为公共语言运行库(CLR)或.NET运行库。
通常将在CLR的控制下运行的代码称为托管代码(managedcode)。
运行库环境(而不是直接由操作系统)执行的代码。
托管代码应用程序可以获得公共语言运行库服务,例如自动垃圾回收、运行库类型检查和安全支持等。
这些服务帮助提供独立于平台和语言的、统一的托管代码应用程序行为。
托管代码是可以使用20多种支持Microsoft.NETFramework的高级语言编写的代码,它们包括:
C#,J#,MicrosoftVisualBasic.NET,MicrosoftJScript.NET,以及C++。
所有的语言共享统一的类库集合,并能被编码成为中间语言(IL)。
运行库编译器(runtime-awareompiler)在托管执行环境下编译中间语言(IL)使之成为本地可执行的代码,并使用数组边界和索引检查,异常处理,垃圾回收等手段确保类型的安全。
在托管执行环境中使用托管代码及其编译,可以避免许多典型的导致安全黑洞和不稳定程序的编程错误。
同样,许多不可靠的设计也自动的被增强了安全性,例如类型安全检查,内存管理和释放无效对象。
程序员可以花更多的精力关注程序的应用逻辑设计并可以减少代码的编写量。
这就意味着更短的开发时间和更健壮的程序。
简单点说,托管代码是microsoft的中间语言,他主要的作用是在.NETFRAMEWORK的CLR执行代码前去编译源代码,也就是说托管代码充当着翻译的作用,源代码在运行时分为两个阶段:
1.源代码编译为托管代码;(所以源代码可以有很多种,如VB,C#,J#)
2.托管代码编译为microsoft系统的.net平台专用文件(如类库、可执行文件等)。
2.1 非托管代码(unmanagedcode)
在公共语言运行库环境的外部,由操作系统直接执行的代码。
非托管代码必须提供自己的垃圾回收、类型检查、安全支持等服务;它与托管代码不同,后者从公共语言运行库中获得这些服务。
.net中托管代码的含义
2.2 什么是托管?
托管是什么意思?
托管代码就是基于.net元数据格式的代码,运行于.net平台之上,所有的与操作系统的交换有.net来完成,就像是把这些功能委托给.net,所以称之为托管代码。
非托管代码则反之。
举个例子l
V还可以使用mfc,atl来编写程序,他们基于MFC或者ATL,而不是.NET,所以是非托管代码,如果基于.net比如C#,VB.net则是托管代码
非托管代码是指.NET解释不了的
简单的说,托管代码的话,.net可以自动释放资料,非托管代码需要手动释放资料.
什么是托管C++
托管是.NET的一个专门概念,它倡导一种新的编程理念,因此我们完全可以把“托管”视为“.NET”。
由托管概念所引发的C++应用程序包括托管代码、托管数据和托管类三个组成部分。
托管代码
.Net环境提供了许多核心的运行(RUNTIME)服务,比如异常处理和安全策略。
为了能使用这些服务,必须要给运行环境提供一些信息代码(元数据),这种代码就是托管代码。
所有的C#、VB.NET、JScript.NET默认时都是托管的,但VisualC++默认时不是托管的,必须在编译器中使用命令行选项(/CLR)才能产生托管代码。
托管数据
与托管代码密切相关的是托管数据。
托管数据是由公共语言运行的垃圾回收器进行分配和释放的数据。
默认情况下,C#、VisualBasic和JScript.NET数据是托管数据。
不过,通过使用特殊的关键字,C#数据可以被标记为非托管数据。
VisualC++数据在默认情况下是非托管数据,即使在使用/CLR开关时也不是托管的。
托管类
尽管VisualC++数据在默认情况下是非托管数据,但是在使用C++的托管扩展时,可以使用“__gc”关键字将类标记为托管类。
就像该名称所显示的那样,它表示类实例的内存由垃圾回收器管理。
另外,一个托管类也完全可以成为.NET框架的成员,由此可以带来的好处是,它可以与其他语言编写的类正确地进行相互操作,如托管的C++类可以从VisualBasic类继承等。
但同时也有一些限制,如托管类只能从一个基类继承等。
2.3 托管代码如何调用非托管代码(csharp如何调用c++代码)?
两种常用的做法:
下载:
1.COMinterop
具体操作:
a.用atl写com服务程序
b.使用Tlbimp将atl写的com程序转换成COMDLL
用如下命令:
tlbimp你写的com.dll
tlbimp是.NETFrameworkSDK中附带的类型库导入程序。
用这个命令即是把生成一个非托管comdll的托管包装。
c.托管客户端非常简单
直接new一下,然后调用对应的方法即可。
2.P/Invoke
a.在托管客户端增加一条DllImport语句和一个方法的调用。
介绍一个P/Invoke网站,
这个网站主要是一个wiki,允许开发者发现,编辑,增加PInvoke的签名,用户自定义类型和从托管代码(指c#和VB.net开发语言)访问win32和其他非托管api的信息。
世界各地的.Net开发者可以很容易分享自己有价值的东西给社区,
2.4 托管代码和非托管代码效率的对比
参加原文来自
更详细的信息
3 DllImportAttribute的字段
在对托管代码进行P/Invoke调用时,DllImportAttribute类型扮演着重要的角色。
DllImportAttribute的主要作用是给CLR指示哪个DLL导出您想要调用的函数。
相关DLL的名称被作为一个构造函数参数传递给DllImportAttribute。
下表列出了所有与平台调用相关的特性字段。
对于每个字段,下表都将包含其默认值,并且会提供一个链接,用于获取有关如何使用这些字段定义非托管DLL函数的信息。
字段
说明
BestFitMapping
启用或禁用最佳匹配映射。
CallingConvention
指定用于传递方法参数的调用约定。
默认值为WinAPI,该值对应于基于32位Intel的平台的__stdcall。
CharSet
控制名称重整以及将字符串参数封送到函数中的方式。
默认值为CharSet.Ansi。
EntryPoint
指定要调用的DLL入口点。
ExactSpelling
控制是否应修改入口点以对应于字符集。
对于不同的编程语言,默认值将有所不同。
PreserveSig
控制托管方法签名是否应转换成返回HRESULT并且返回值有一个附加的[out,retval]参数的非托管签名。
默认值为true(不应转换签名)。
SetLastError
允许调用方使用Marshal.GetLastWin32ErrorAPI函数来确定执行该方法时是否发生了错误。
在VisualBasic中,默认值为true;在C#和C++中,默认值为false。
ThrowOnUnmappableChar
控件引发的异常,将无法映射的Unicode字符转换成一个ANSI"?
"字符。
除了指出宿主DLL外,DllImportAttribute还包含了一些可选属性,其中四个特别有趣:
EntryPoint、CharSet、SetLastError和CallingConvention。
/3877783_84071078.shtml
3.1 entrypoint
入口点用于标识函数在DLL中的位置。
在托管对象中,目标函数的原名或序号入口点将标识跨越交互操作边界的函数。
此外,您可以将入口点映射到一个不同的名称,这实际上是将函数重命名。
以下列出了重命名DLL函数的可能原因:
· 避免使用区分大小写的API函数名
· 符合现行的命名标准
· 提供采用不同数据类型的函数(通过声明同一DLL函数的多个版本)
· 简化对包含ANSI和Unicode版本的API的使用
您可以使用DllImportAttribute.EntryPoint字段按名称或序号指定DLL函数。
如果函数在方法定义中的名称与入口点在DLL的名称相同,则不必用EntryPoint字段来显式地标识函数。
否则,使用以下属性形式之一来指示名称或序号:
l [DllImport("dllname",EntryPoint="Functionname")]
l [DllImport("dllname",EntryPoint="#123")]
l 指定入口点名称时,您可以提供一个字符串来指示包含入口点的DLL的名称,或者也可以按序号来标识入口点。
序号以#符号为前缀,如#1。
如果省略此字段,则公共语言运行库将使用以DllImportAttribute标记的.NET方法的名称。
下面的示例演示如何使用EntryPoint字段将代码中的MessageBoxA替换为MsgBox
usingSystem.Runtime.InteropServices;
publicclassWin32{
[DllImport("user32.dll",EntryPoint="MessageBoxA")]
publicstaticexternintMsgBox(inthWnd,Stringtext,Stringcaption,uinttype);
}
3.2 CharSet
以下来自
DllImportAttribute.CharSet字段控制字符串封送处理并确定平台调用在DLL中查找函数名的方式。
本主题将介绍这两种行为。
对于采用字符串参数的函数,有些API将导出它们的两个版本:
窄版本(ANSI)和宽版本(Unicode)。
例如,Win32API包含MessageBox函数的以下入口点名称:
· MessageBoxA
提供单字节字符ANSI格式,其特征是在入口点名称后附加一个“A”。
对MessageBoxA的调用始终会以ANSI格式封送字符串,它常见于Windows95和Windows98平台。
· MessageBoxW
提供双字节字符Unicode格式,其特征是在入口点名称后附加一个“W”。
对MessageBoxW的调用始终会以Unicode格式封送字符串,它常见于WindowsNT、Windows2000和WindowsXP平台。
CharSet字段接受以下值:
CharSet.Ansi(默认值)
· 字符串封送处理
平台调用将字符串从托管格式(Unicode)封送为ANSI格式。
· 名称匹配
在DllImportAttribute.ExactSpelling字段为true(它是VisualBasic2005中的默认值)时,平台调用将只搜索您指定的名称。
例如,如果指定MessageBox,则平台调用将搜索MessageBox,如果它找不到完全相同的拼写则失败。
当ExactSpelling字段为false(它是C++和C#中的默认值)时,平台调用将首先搜索未处理的别名(MessageBox),如果找不到未处理的别名,则将搜索已处理的名称(MessageBoxA)。
请注意,ANSI名称匹配行为与Unicode名称匹配行为不同。
CharSet.Unicode
· 字符串封送处理
平台调用会将字符串从托管格式(Unicode)复制为Unicode格式。
· 名称匹配
当ExactSpelling字段为true(它是VisualBasic2005中的默认值)时,平台调用将只搜索您指定的名称。
例如,如果指定MessageBox,则平台调用将搜索MessageBox,如果它找不到完全相同的拼写则失败。
当ExactSpelling字段为false(它是C++和C#中的默认值)时,平台调用将首先搜索已处理的名称(MessageBoxW),如果找不到已处理的名称,则将搜索未处理的别名(MessageBox)。
请注意,Unicode名称匹配行为与ANSI名称匹配行为不同。
CharSet.Auto
· 平台调用在运行时根据目标平台在ANSI和Unicode格式之间进行选择。
下面的示例演示用于指定字符集的MessageBox函数的三个托管定义。
在第一个定义中,通过省略,使CharSet字段默认为ANSI字符集。
[DllImport("user32.dll")]
publicstaticexternintMessageBoxA(inthWnd,Stringtext,Stringcaption,uinttype);
[DllImport("user32.dll",CharSet=CharSet.Unicode)]
publicstaticexternintMessageBoxW(inthWnd,Stringtext,Stringcaption,uinttype);
[DllImport("user32.dll",CharSet=CharSet.Auto)]
publicstaticexternintMessageBox(inthWnd,Stringtext,Stringcaption,uinttype);
CharSet.Ansi和CharSet.Unicode的名称匹配规则大不相同。
对于Ansi来说,如果将EntryPoint设置为“MyMethod”且它存在的话,则返回“MyMethod”。
如果DLL中没有“MyMethod”,但存在“MyMethodA”,则返回“MyMethodA”。
对于Unicode来说则正好相反。
如果将EntryPoint设置为“MyMethod”且它存在的话,则返回“MyMethodW”。
如果DLL中不存在“MyMethodW”,但存在“MyMethod”,则返回“MyMethod”。
如果使用的是Auto,则匹配规则与平台有关(在WindowsNT上为Unicode,在Windows98上为Ansi)。
如果ExactSpelling设置为true,则只有当DLL中存在“MyMethod”时才返回“MyMethod”。
如果DLL函数不以任何方式处理文本,则可以忽略DllImportAttribute的CharSet属性。
然而,当Char或String数据是等式的一部分时,应该将CharSet属性设置为CharSet.Auto。
这样可以使CLR根据宿主OS使用适当的字符集。
如果没有显式地设置CharSet属性,则其默认值为CharSet.Ansi。
这个默认值是有缺点的,因为对于在Windows2000、WindowsXP和WindowsNT®上进行的interop调用,它会消极地影响文本参数封送处理的性能。
应该显式地选择CharSet.Ansi或CharSet.Unicode的CharSet值而不是使用CharSet.Auto的唯一情况是:
您显式地指定了一个导出函数,而该函数特定于这两种Win32OS中的某一种。
ReadDirectoryChangesWAPI函数就是这样的一个例子,它只存在于基于WindowsNT的操作系统中,并且只支持Unicode;在这种情况下,您应该显式地使用CharSet.Unicode。
有时,WindowsAPI是否有字符集关系并不明显。
一种决不会有错的确认方法是在PlatformSDK中检查该函数的C语言头文件。
(如果您无法肯定要看哪个头文件,则可以查看PlatformSDK文档中列出的每个API函数的头文件。
)如果您发现该API函数确实定义为一个映射到以A或W结尾的函数名的宏,则字符集与您尝试调用的函数有关系。
WindowsAPI函数的一个例子是在WinUser.h中声明的GetMessageAPI,您也许会惊讶地发现它有A和W两种版本。
3.3 SetLastError
SetLastError错误处理非常重要,但在编程时经常被遗忘。
当您进行P/Invoke调用时,也会面临其他的挑战—处理托管代码中WindowsAPI错误处理和异常之间的区别。
我可以给您一点建议。
如果您正在使用P/Invoke调用WindowsAPI函数,而对于该函数,您使用GetLastError来查找扩展的错误信息,则应该在外部方法的DllImportAttribute中将SetLastError属性设置为true。
这适用于大多数外部方法。
这会导致CLR在每次调用外部方法之后缓存由API函数设置的错误。
然后,在包装方法中,可以通过调用类库的System.Runtime.InteropServices.Marshal类型中定义的Marshal.GetLastWin32Error方法来获取缓存的错误值。
我的建议是检查这些期望来自API函数的错误值,并为这些值引发一个可感知的异常。
对于其他所有失败情况(包括根本就没意料到的失败情况),则引发在System.ComponentModel命名空间中定义的Win32Exception,并将Marshal.GetLastWin32Error返回的值传递给它。
3.4 CallingConvention
CallingConvention.Cdecl:
调用方清理堆栈。
它使您能够调用具有varargs的函数。
CallingConvention.StdCall:
被调用方清理堆栈。
它是从托管代码调用非托管函数的默认约定。
CallingConvention字段的默认值为Winapi,而后者又默认为StdCall约定。
可能是最不重要的一个DllImportAttribute属性是CallingConvention。
通过此属性,可以给CLR指示应该将哪种函数调用约定用于堆栈中的参数。
CallingConvention.Winapi的默认值是最好的选择,它在大多数情况下都可行。
然而,如果该调用不起作用,则可以检查PlatformSDK中的声明头文件,看看您调用的API函数是否是一个不符合调用约定标准的异常API。
通常,本机函数(例如WindowsAPI函数或C-运行时DLL函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。
大多数WindowsAPI函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。
相反,许多C-运行时DLL函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。
幸运的是,要让P/Invoke调用工作只需要让外围设备理解调用约定即可。
通常,从默认值CallingConvention.Winapi开始是最好的选择。
然后,在C运行时DLL函数和少数函数中,可能需要将约定更改为CallingConvention.Cdecl。
3.5 ExactSpelling
ExactSpelling指示是否应修改非托管DLL中的入口点的名称,以与CharSet字段中指定的CharSet值相对应。
如果为true,则当DllImportAttribute.CharSet字段设置为CharSet的Ansi值时,向方法名称中追加字母A,当DllImportAttribute.CharSet字段设置为CharSet的Unicode值时,向方法的名称中追加字母W。
此字段的默认值是false。
3.6 PreserveSig
PreserveSig指示托管方法签名不应转换成返回HRESULT、并且可能有一个对应于返回值的附加[out,retval]参数的非托管签名。
4 参数类型
l 数值型直接用对应的就可。
(DWORD->int,WORD->I
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- dllimport 使用