本教程说明使用什么方法才能从 C# 调用非托管 DLL 函数。该教程所讨论的属性允许您调用这些函数并使数据类型得到正确封送。
示例文件
请参见“平台调用”示例以下载和生成该教程所讨论的示例文件。
平台调用
其他阅读材料
教程
C# 代码有以下两种可以直接调用非托管代码的方法:
对于这两种技术,都必须向 C# 编译器提供非托管函数的声明,并且还可能需要向 C# 编译器提供如何封送与非托管代码之间传递的参数和返回值的说明。
该教程由下列主题组成:
该教程包括下列示例:
直接从 C# 调用 DLL 导出
若要声明一个方法使其具有来自 DLL 导出的实现,请执行下列操作:
- 使用 C# 关键字 static 和 extern 声明方法。
- 将 DllImport 属性附加到该方法。DllImport 属性允许您指定包含该方法的 DLL
的名称。通常的做法是用与导出的方法相同的名称命名 C# 方法,但也可以对 C# 方法使用不同的名称。
- 还可以为方法的参数和返回值指定自定义封送处理信息,这将重写 .NET Framework 的默认封送处理。
示例 1
本示例显示如何使用 DllImport 属性通过调用 msvcrt.dll
中的 puts
输出消息。
// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
[DllImport("msvcrt.dll")]
public static extern int puts(string c);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();
public static void Main()
{
puts("Test");
_flushall();
}
}
输出
Test
代码讨论
前面的示例显示了声明在非托管 DLL 中实现的 C# 方法的最低要求。PlatformInvokeTest.puts
方法用 static 和 extern
修饰符声明并且具有 DllImport 属性,该属性使用默认名称 puts
通知编译器此实现来自 msvcrt.dll
。若要对 C# 方法使用不同的名称(如 putstring
),则必须在 DllImport 属性中使用 EntryPoint
选项,如下所示:
[DllImport("msvcrt.dll", EntryPoint="puts")]
有关 DllImport 属性的语法的更多信息,请参见 DllImportAttribute
类。
默认封送处理和为非托管方法的参数指定自定义封送处理
当从 C# 代码中调用非托管函数时,公共语言运行库必须封送参数和返回值。
对于每个 .NET Framework 类型均有一个默认非托管类型,公共语言运行库将使用此非托管类型在托管到非托管的函数调用中封送数据。例如,C#
字符串值的默认封送处理是封送为 LPTSTR(指向 TCHAR 字符缓冲区的指针)类型。可以在非托管函数的 C# 声明中使用 MarshalAs
属性重写默认封送处理。
示例 2
本示例使用 DllImport 属性输出一个字符串。它还显示如何通过使用 MarshalAs
属性重写函数参数的默认封送处理。
// Marshal.cs
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
[DllImport("msvcrt.dll")]
public static extern int puts(
[MarshalAs(UnmanagedType.LPStr)]
string m);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();
public static void Main()
{
puts("Hello World!");
_flushall();
}
}
输出
运行此示例时,字符串
Hello World!
将显示在控制台上。
代码讨论
在前面的示例中,puts
函数的参数的默认封送处理已从默认值
LPTSTR 重写为 LPSTR。
MarshalAs 属性可以放置在方法参数、方法返回值以及结构和类的字段上。若要设置方法返回值的封送处理,请将
MarshalAs 属性与返回属性位置重写一起放置在方法上的属性块中。例如,若要显式设置 puts
方法返回值的封送处理:
...
[DllImport("msvcrt.dll")]
[return : MarshalAs(UnmanagedType.I4)]
public static extern int puts(
...
有关 MarshalAs 属性的语法的更多信息,请参见 MarshalAsAttribute
类。
注意In 和 Out 属性可用于批注非托管方法的参数。它们与 MIDL
源文件中的 in 和 out 修饰符的工作方式类似。请注意,Out 属性与 C# 参数修饰符 out
不同。有关 In 和 Out 属性的更多信息,请参见 InAttribute
类和 OutAttribute
类。
为用户定义的结构指定自定义封送处理
可以为传递到非托管函数或从非托管函数返回的结构和类的字段指定自定义封送处理属性。通过向结构或类的字段中添加 MarshalAs
属性可以做到这一点。还必须使用 StructLayout 属性设置结构的布局,还可以控制字符串成员的默认封送处理,并设置默认封装大小。
示例 3
本示例说明如何为结构指定自定义封送处理属性。
请考虑下面的 C 结构:
typedef struct tagLOGFONT
{
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
在 C# 中,可以使用 StructLayout 和 MarshalAs 属性描述前面的结构,如下所示:
// logfont.cs
// compile with: /target:module
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public class LOGFONT
{
public const int LF_FACESIZE = 32;
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)]
public string lfFaceName;
}
有关 StructLayout 属性的语法的更多信息,请参见 StructLayoutAttribute
类。
然后即可将该结构用在 C# 代码中,如下所示:
// pinvoke.cs
// compile with: /addmodule:logfont.netmodule
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr CreateFontIndirect(
[In, MarshalAs(UnmanagedType.LPStruct)]
LOGFONT lplf // characteristics
);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(
IntPtr handle
);
public static void Main()
{
LOGFONT lf = new LOGFONT();
lf.lfHeight = 9;
lf.lfFaceName = "Arial";
IntPtr handle = CreateFontIndirect(lf);
if (IntPtr.Zero == handle)
{
Console.WriteLine("Can't creates a logical font.");
}
else
{
if (IntPtr.Size == 4)
Console.WriteLine("{0:X}", handle.ToInt32());
else
Console.WriteLine("{0:X}", handle.ToInt64());
// Delete the logical font created.
if (!DeleteObject(handle))
Console.WriteLine("Can't delete the logical font");
}
}
}
运行示例
C30A0AE5
代码讨论
在前面的示例中,CreateFontIndirect
方法使用了一个 LOGFONT 类型的参数。MarshalAs 和 In
属性用于限定此参数。程序将由此方法返回的数值显示为十六进制大写字符串。
注册回调方法
若要注册调用非托管函数的托管回调,请用相同的参数列表声明一个委托并通过 PInvoke 传递它的一个实例。在非托管端,它将显示为一个函数指针。有关
PInvoke 和回调的更多信息,请参见平台调用详解。
例如,考虑以下非托管函数 MyFunction
,此函数要求
callback 作为其参数之一:
typedef void (__stdcall *PFN_MYCALLBACK)();
int __stdcall MyFunction(PFN_ MYCALLBACK callback);
若要从托管代码调用 MyFunction
,请声明该委托,将
DllImport 附加到函数声明,并根据需要封送任何参数或返回值:
public delegate void MyCallback();
[DllImport("MYDLL.DLL")]
public static extern void MyFunction(MyCallback callback);
同时,请确保委托实例的生存期覆盖非托管代码的生存期;否则,委托在经过垃圾回收后将不再可用。
相关推荐
本教程将深入探讨如何使用C#进行平台调用,以便与非托管DLL进行通信。 首先,平台调用是.NET Framework提供的一种机制,它允许C#代码直接调用非托管DLL中的函数,这些函数通常是操作系统API或其他未受.NET管理的...
总之,掌握C#调用VISA进行GPIB、LAN和USB通信的能力,能够极大地扩展你的硬件控制能力,为实验室自动化、测量系统开发和嵌入式应用提供强大的支持。通过不断学习和实践,你将能够驾驭这些技术,解决各种复杂的硬件...
在本节“VS2010轻松学习C# - 从零到深入 - 天轰穿.NET4趣味编程视频教程_第25讲:委托与事件实例浅析”中,我们将深入探讨C#编程中的两个核心概念:委托(Delegates)和事件(Events)。这两个概念在.NET框架中扮演...
在本节《VS2010轻松学习C# - 从零到深入 - 天轰穿.NET4趣味编程视频教程》的第15讲中,主题聚焦于“重载”这一核心概念。重载是面向对象编程中的一个重要特性,尤其在C#中,它允许我们在同一个作用域内创建多个同名...
C#是一种广泛应用于软件开发,尤其是Windows平台和.NET框架下的编程语言。由微软公司于2000年推出,C#的设计目标是成为一个现代、类型安全、面向对象的编程语言,它结合了C++和Java的优点,同时也吸取了其他语言的...
C#电子教程-语法api调用手册
总结起来,这个项目或教程涉及了以下核心知识点: 1. ASP.NET WebAPI的使用,特别是如何在`PostController`中定义和调用API方法。 2. C#中的JSON序列化和反序列化,主要使用Json.NET库。 3. 使用`HttpClient`类进行...
在本节“VS2010轻松学习C# - 从零到深入 - 天轰穿.NET4趣味编程视频教程”的第14讲中,我们将深入探讨C#编程中的核心概念——类成员,特别是方法与静态关键字的使用。天轰穿老师的教程以其生动有趣的教学方式,使得...
C# API大全pdf,内容涉及使用C#调用windows API 入门: 一:入门,直接从C# 调用DLL 导出 二.背后的原理―― 知其所以然,相关的知识 从.NET 平台调用Win32 API的一些基础知识以及编程实例,windows ...
【C#-2008程序设计基础案例教程】中的第十一章主要讲解了ADO.NET和XML相关的知识,特别是如何使用C#来处理这两种技术。本章的核心是Web Service的概念和使用,以及如何通过ADO.NET读写XML。 Web Service是一种应用...
在本节“VS2010轻松学习C# - 从零到深入 - 天轰穿.NET4趣味编程视频教程_第22讲:深入数组”中,我们将深入探讨C#编程语言中的一个重要概念——数组。数组是C#中存储同类型数据集合的基本结构,它允许我们在一个变量...
**C# 操作 WebService 入门教程** 在软件开发中,Web Service是一种基于网络的、松散耦合的服务交互方式。它允许不同系统之间的数据交换,无视平台和语言的差异。C#作为.NET框架的主要编程语言,提供了丰富的工具和...
首先,要实现C#调用TensorFlow,我们需要借助TensorFlow的C API或者通过NuGet包管理器安装`TensorFlow.NET`库。`TensorFlow.NET`是C#对TensorFlow的官方封装,使得开发者可以在C#环境中直接操作TensorFlow的API。 1...
【ASP.NET-C#-2008-项目开发案例教程-郎登何-第8章 c#经典案例】 本章主要介绍了使用C#语言在ASP.NET框架下开发和使用XML Web服务的相关知识,旨在帮助读者深入理解XML Web服务的工作原理,并能够实际操作实现相关...
### STK与C#互联实例教程 #### 一、引言 随着计算机技术的发展,越来越多的应用需要跨平台或跨语言的交互与集成。本教程旨在详细介绍如何利用Microsoft Visual Studio 2010进行System Tool Kit (STK)的二次开发,...
【C#教程-清华版】是一份由清华大学出版社出版的C#编程学习资源,主要针对初学者和希望深入理解C#语言特性的开发者。这份教程以其权威性和系统性,为学习者提供了全面的C#知识体系。 C#是微软公司推出的一种面向...
本文将深入探讨“DLL注入教程——另类动态调用DLL”,特别是针对易语言用户,介绍如何利用易语言来实现DLL的注入和其他进程的调用。 首先,我们需要理解DLL的本质。DLL是Windows操作系统中的一种共享库文件,它包含...
通过上述步骤,VFP开发者可以学会如何调用C#编写的DLL来实现简繁体转换,进而扩展VFP的功能,满足开发中的特定需求。尽管VFP已不被微软官方支持,但通过借助.NET平台的强大功能,VFP的生命周期可以被延长,同时也为...
C#作为.NET Framework的一部分,提供了强大的能力来创建COM组件,这些组件可以被其他支持COM技术的语言和平台,如PowerBuilder,调用。本教程将深入探讨如何在C#中编写COM控件,并在PowerBuilder中成功调用。 首先...
《C#--数据库入门经典C#》是一本专注于教授如何使用C#语言进行数据库操作的教程,适合初学者深入理解数据库编程。C#是Microsoft公司推出的一种面向对象的编程语言,广泛应用于Windows平台上的应用程序开发,包括桌面...