C# 程序员参考--平台调用教程

转载 2007年10月10日 09:00:00
 平台调用服务 (PInvoke) 允许托管代码调用在 DLL 中实现的非托管函数。

本教程说明使用什么方法才能从 C# 调用非托管 DLL 函数。该教程所讨论的属性允许您调用这些函数并使数据类型得到正确封送。

教程

C# 代码有以下两种可以直接调用非托管代码的方法:

  • 直接调用从 DLL 导出的函数。
  • 调用 COM 对象上的接口方法(有关更多信息,请参见 COM Interop 第一部分:C# 客户端教程)。

对于这两种技术,都必须向 C# 编译器提供非托管函数的声明,并且还可能需要向 C# 编译器提供如何封送与非托管代码之间传递的参数和返回值的说明。

该教程由下列主题组成:

该教程包括下列示例:

直接从 C# 调用 DLL 导出

若要声明一个方法使其具有来自 DLL 导出的实现,请执行下列操作:

  • 使用 C# 关键字 staticextern 声明方法。
  • 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 方法用 staticextern 修饰符声明并且具有 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 类

注意   InOut 属性可用于批注非托管方法的参数。它们与 MIDL 源文件中的 inout 修饰符的工作方式类似。请注意,Out 属性与 C# 参数修饰符 out 不同。有关 InOut 属性的更多信息,请参见 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# 中,可以使用 StructLayoutMarshalAs 属性描述前面的结构,如下所示:

// 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 类型的参数。MarshalAsIn 属性用于限定此参数。程序将由此方法返回的数值显示为十六进制大写字符串。

注册回调方法

若要注册调用非托管函数的托管回调,请用相同的参数列表声明一个委托并通过 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#平台调用

原文地址http://www.51one.net/study/cc/2625.html#pinvoke_callingdllexport   ...
  • 500ML
  • 500ML
  • 2007年05月05日 00:06
  • 2774

C#平台调用教程

 平台调用服务 (PInvoke) 允许托管代码调用在 DLL 中实现的非托管函数。本教程说明使用什么方法才能从 C# 调用非托管 DLL 函数。该教程所讨论的属性允许您调用这些函数并使数据类型得到正...
  • hangel_hw
  • hangel_hw
  • 2008年03月18日 10:01
  • 301

C# 程序员参考--属性教程

本教程展示如何创建自定义属性类,如何在代码中使用它们,以及如何通过反射查询它们。教程属性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。与程序实体关联后,属性可在运行时查询,...
  • qdzx2008
  • qdzx2008
  • 2006年02月27日 22:26
  • 739

C# 程序员参考--数组教程

1.数组概述  2.声明数组  3.初始化数组  4.访问数组成员  5.数组是对象  6.对数组使用 foreach 数组概述 C# 数组从零开始建立索引,即数组索引从零开始.C# 中...
  • lmw525
  • lmw525
  • 2013年05月01日 21:47
  • 409

C# 程序员参考--线程处理教程

线程处理的优点是可以创建使用多个执行线程的应用程序。例如,某一进程可以具有管理与用户交互的用户界面线程,以及在用户界面线程等待用户输入时执行其他任务的辅助线程。该教程说明各种线程活动:创建和执行线程线...
  • shaily
  • shaily
  • 2009年10月19日 12:56
  • 430

C# 程序员参考--属性教程

本教程展示如何创建自定义属性类,如何在代码中使用它们,以及如何通过反射查询它们。教程属性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。与程序实体关联后,属性可在运行时查询,...
  • ycl111
  • ycl111
  • 2005年10月08日 16:34
  • 1297

平台调用教程

平台调用服务 (PInvoke) 允许托管代码调用在 DLL 中实现的非托管函数。本教程说明使用什么方法才能从 C# 调用非托管 DLL 函数。该教程所讨论的属性允许您调用这些函数并使数据类型得到正确...
  • edwardq2266
  • edwardq2266
  • 2008年05月14日 14:50
  • 290

c# 平台调用

http://tech.ddvip.com/2010-09/1283763341159872.html
  • playStudy
  • playStudy
  • 2011年06月21日 15:02
  • 828

C# 程序员参考-委托教程(From MSDN)

本教程演示委托类型。它说明如何将委托映射到静态方法和实例方法,以及如何组合委托(多路广播)。示例文件请参见“委托”示例以下载和生成本教程中讨论的示例文件。其他阅读材料 delegate ...
  • hyde82
  • hyde82
  • 2006年02月11日 17:12
  • 770

力荐!程序员必看书单大全

IT派 - {技术青年圈}持续关注互联网、大数据、人工智能领域关注Stack Overflow 创始人 Jeff Atwood 推荐给程序员的书。《代码大全2》Steve McConnell所著的《代...
  • j2IaYU7Y
  • j2IaYU7Y
  • 2018年01月02日 00:00
  • 1034
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C# 程序员参考--平台调用教程
举报原因:
原因补充:

(最多只允许输入30个字)