现在的位置: 首页 > 综合 > 正文

C#调用C++的Dll(dll,DLL)类型之间转换笔记

2014年01月14日 ⁄ 综合 ⁄ 共 15078字 ⁄ 字号 评论关闭

1, HWND <-> IntPtr

public staticexternbool Get_Msg_Test(IntPtr hwnd);

//hwnd为C#窗体的句柄

extern "C" __declspec(dllexport)bool Get_Msg_Test(HWND hwnd)

代码:

C#

[DllImport("Spy.dll",EntryPoint="Get_Msg_Test",CallingConvention =CallingConvention.Cdecl)]
        public static extern bool Get_Msg_Test(IntPtr hwnd);

private void FrmMain_Load(object sender, EventArgs e)
        {
            try
           {
	         Get_Msg_Test(this.Handle);
            }
            catch (System.Exception ex)
            {
 	          //程序收到C++发过来的消息后,此处有异常
            }
        }

        //用户自定义消息
        public const int WM_USER = 0x0400;
        public const int WM_TEST = WM_USER + 1;

protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                //旺旺最后一条聊天记录消息
                case WM_TEST:
                    LParamDataSruct lparam = (LParamDataSruct)m.GetLParam(typeof(LParamDataSruct));
                    String msg  = lparam.lParamData;//此处能得到C++ DLL发送的消息内容
                    break;
                default:
                    break;
            }
            base.WndProc(ref m);
        }

        /// <summary>
        /// Message 接收GetLParam方法返回值
        /// LParamData返回内容与LParamData结构体相同
        /// </summary>
        public struct LParamDataSruct
        {
            public String lParamData;
        }

C++

#define WM_TEST WM_USER+1//自定义消息

struct LParamDataSruct
{
public:
	char* lParamData;       //要发送的信息
};

//hwnd为C#窗体的句柄
extern "C" __declspec(dllexport) bool Get_Msg_Test(HWND hwnd)
{
	LParamDataSruct lparam;
	char* test = "This is test MSG 哈哈。";
	lparam.lParamData = test;

	::SendMessage(hwnd,WM_TEST,wPARAM,(LPARAM)&lparam);
	return true;
}
 
2,HBITMAP <-> IntPtr
C++:
HBITMAP DvrControl::GetImage() 
{
 // TODO: Add your control notification handler code here
 if(m_pDvr->get_Image())
          memcpy(m_pBuff,m_pDvr->get_Image(),(m_nWidth*m_nHeight*2)); 
 else
  memset(m_pBuff,0,m_nWidth*m_nHeight*2);
 
 //hBmp = m_pSnapshot->CreateBitmapFromYUV(this->m_hWnd,m_pBuff,m_nWidth,m_nHeight,&bi,m_nHalf); 
 hBmp = m_pSnapshot->CreateBitmapFromYUV(NULL, m_pBuff, m_nWidth, m_nHeight, bi, m_nHalf); 
 
 /*CClientDC dc(CWnd* cwindow);  
 CDC          dcmem;
  
 if(hBmp){     
   dcmem.CreateCompatibleDC(&dc);
   dcmem.SelectObject(hBmp);
   if(m_nHalf)
                dc.BitBlt(0,0,m_nWidth*2,m_nHeight*2,&dcmem,0,0,SRCCOPY);
   else
    dc.BitBlt(0,0,m_nWidth,m_nHeight,&dcmem,0,0,SRCCOPY);
   DeleteDC(dcmem);
   DeleteObject(hBmp);
 } */

 return hBmp;
}
C#:
// P/Invoke per TuaDllWrapper.dll:
//
//   extern "C" HBITMAP __stdcall GetSomeImage(void);
//
[DllImport("TuaDllWrapper.dll")]
static extern IntPtr GetSomeImage();
 
 
 
微软的.NET框架的优点之一是它提供了独立于语言的开发平台。你可以在VB、C++、C#等语言中编写一些类,而在其它语言中使用(源于.NET中使用了CLS),你甚至可以从另一种语言编写的类中继承。但是你要是想调用以前的非托管DLL,那又会怎么样呢?你必须以某种方式将.NET对象转换为结构体、char *、函数指针等类型。这也就是说,你的参数必须被marshal(注:不知道中文名称该叫什么,英文中指的是为了某个目的而组织人或事物,参见这里,此处指的是为了调用非托管函数而进行的参数转换)。

C#中使用DLL函数之前,你必须使用DllImport声明要调用的函数:

public class Win32 {
  [DllImport("User32.Dll")]
  public static extern void SetWindowText(int h, String s);
  // 函数原型为:BOOL SetWindowText(HWND hWnd, LPCTSTR lpString);
}

DllImport告诉编译器被调函数的入口在哪里,并且将该入口绑定到类中你声明的函数。你可以给这个类起任意的名字,我给它命名为Win32。你甚至可以将类放到命名空间中,具体参见图一。要编译Win32API.cs,输入:

csc /t:library /out:Win32API.dll Win32API.cs

这样你就拥有了Win32API.dll,并且你可以在任意的C#项目中使用它:

using Win32API;
int hwnd = // get it...
String s = "I'm so cute."
Win32.SetWindowText(hwnd, s);

编译器知道去user32.dll中查找函数SetWindowText,并且在调用前自动将String转换LPTSTR (TCHAR*)。很惊奇是吧!那么.NET是如何做到的呢?每种C#类型有一个默认的marshal类型,String对应LPTSTR。但你若是试着调用GetWindowText会怎么样呢(此处字符串作为out参数,而不是in参数)?它无法正常调用,是因为String是无法修改的,你必须使用StringBuilder:

using System.Text; // for StringBuilder
public class Win32 {
  [DllImport("user32.dll")]
  public static extern int GetWindowText(int hwnd,
    StringBuilder buf, int nMaxCount);
  // 函数原型:int GetWindowText(HWND hWnd, LPTSTR lpString, int nMaxCount);
}

StringBuilder默认的marshal类型是LPTSTR,此时GetWindowText可以修改你的字符串:

int hwnd = // get it...
StringBuilder cb = new StringBuilder(256);
Win32.GetWindowText(hwnd, sb, sb.Capacity);

如果默认的类型转换无法满足你的要求,比如调用函数GetClassName,它总是将参数转换为类型LPSTR (char *),即便在定义Unicode的情况下使用,CLR仍然会将你传递的参数转换为TCHAR类型。不过不用着急,你可以使用MarshalAs覆盖掉默认的类型:

[DllImport("user32.dll")]
public static extern int GetClassName(int hwnd,
  [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
  int nMaxCount);
  // 函数原型:int GetClassNameA(HWND hWnd, LPTSTR lpClassName, int nMaxCount);

这样当你调用GetClassName时,.NET将字符串作为ANSI字符传递,而不是宽字符。

结构体和回调函数类型的参数又是如何传递的呢?.NET有一种方法可以处理它们。举个简单的例子,GetWindowRect,这个函数获取窗口的屏幕坐标,C++中我们这么处理:

// in C/C++
RECT rc;
HWND hwnd = FindWindow("foo",NULL);
::GetWindowRect(hwnd, &rc);

你可以使用C#结构体,只需使用另外一种C#属性StructLayout:

[StructLayout(LayoutKind.Sequential)]
public struct RECT {
  public int left;
  public int top;
  public int right;
  public int bottom;
}

一旦你定义了上面的结构体,你可以使用下面的函数声明形式 :

[DllImport("user32.dll")]
public static extern int 
  GetWindowRect(int hwnd, ref RECT rc);
  // 函数原型:BOOL GetWindowRect(HWND hWnd, LPRECT lpRect);

使用ref标识很重要,以至于CLR(通用语言运行时)将RECT变量作为引用传递到函数中,而不是无意义的栈拷贝。

定义了GetWindowRect之后,你就可以采用下面的方式调用

RECT rc = new RECT();
int hwnd = // get it ...
Win32.GetWindowRect(hwnd, ref rc);

注意你同样需要像声明中的那样使用ref关键字。C#结构体默认的marshal类型是LPStruct,因此没有必要使用MarshalAs

但如果你使用了类RECT而不是结构体RECT,那么你必须使用如下的声明形式:

// if RECT is a class, not struct
[DllImport("user32.dll")]
public static extern int 
  GetWindowRect(int hwnd, 
    [MarshalAs(UnmanagedType.LPStruct)] RECT rc);

C#和C++一样,一件事情有很多种实现方式。System.Drawing中已经有Rectangle结构体,用来处理矩形,那有为什么要“重新发明轮子”呢?

[DllImport("user32.dll")]
public static extern int GetWindowRect(int hwnd, ref Rectangle rc);

最后,又是怎样从C#中传递回调函数到非托管代码中的呢?你所要做的就是委托(delegate

delegate bool EnumWindowsCB(int hwnd, int lparam);

一旦你声明了你的回调函数,那么你需要调用的函数声明为:

[DllImport("user32")]
public static extern int 
  EnumWindows(EnumWindowsCB cb, int lparam);
  // 函数原型:BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);

由于上面的委托仅仅是声明了委托类型,你需要在你的类中提供实际的回调函数代码

// in your class
public static bool MyEWP(int hwnd, int lparam) {
  // do something
  return true;
}

然后传递给相应的委托变量:

EnumWindowsCB cb = new EnumWindowsCB(MyEWP);
Win32.EnumWindows(cb, 0);

你可能注意到参数lparam。在C语言中,如果你传递参数LPARAM给EnumWindows,Windows将它作为参数调用你的回调函数。通常lparam是包含了你要做的事情的上下文结构体或类指针,记住,在.NET中没有指针的概念!那该怎么做呢?上面的例子中,你可以申明lparam为IntPtr类型,并且使用GCHandle来封装它

// lparam is IntPtr now
delegate bool EnumWindowsCB(int hwnd,     IntPtr lparam);
// wrap object in GCHandle
MyClass obj = new MyClass();
GCHandle gch = GCHandle.Alloc(obj);
EnumWindowsCB cb = new EnumWindowsCB(MyEWP);
   Win32.EnumWindows(cb, (IntPtr)gch);
   gch.Free();

不要忘了使用完之后手动释放它!有时,你需要按照以前那种方式在C#中释放内存。可以使用GCHandle.Target的方式在你的回调函数中使用“指针”。

public static bool MyEWP(int hwnd, IntPtr param) {
  GCHandle gch = (GCHandle)param;
  MyClass c = (MyClass)gch.Target;
  // ... use it
  return true;
 图2是将EnumWindows封装到数组中的类。你只需要按如下的方式使用即可,而不要纠结于委托和回调中。
WindowArray wins = new WindowArray();
foreach (int hwnd in wins) {
 // do something
}
 

C#如何直接调用非托管代码,通常有2种方法:

1. 直接调用DLL 导出的函数。

2. 调用 COM 对象上的接口方法

我主要讨论从dll中导出函数,基本步骤如下:

1.使用 C# 关键字 staticextern 声明方法。

2.将 DllImport 属性附加到该方法。DllImport 属性允许您指定包含该方法的DLL 的名称。

3.如果需要,为方法的参数和返回值指定自定义封送处理信息,这将重写 .NET Framework 的默认封送处理。

好,我们开始

1.首先我们查询MSDN找到GetShortPathName的定义

The GetShortPathName function retrieves the short path form of the specified path.

DWORD GetShortPathName(

LPCTSTR lpszLongPath,

LPTSTR lpszShortPath,

DWORD cchBuffer

);

2.查找对照表进行数据类型的转换(出处:http://msdn.microsoft.com/msdnmag/issues/03/07/NET/default.aspx?fig=trueData Types

Win32
Types

Specification

CLR Type

char, INT8, SBYTE, CHARâ€

8-bit signed integer

System.SByte

short,
short int, INT16, SHORT

16-bit signed integer

System.Int16

int, long, long int, INT32, LONG32, BOOL†, INT

32-bit signed integer

System.Int32

__int64,
INT64, LONGLONG

64-bit signed integer

System.Int64

unsigned char, UINT8, UCHAR†, BYTE

8-bit unsigned integer

System.Byte

unsigned
short, UINT16, USHORT, WORD, ATOM, WCHAR†, __wchar_t

16-bit unsigned integer

System.UInt16

unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT

32-bit
unsigned integer

System.UInt32

unsigned __int64, UINT64, DWORDLONG, ULONGLONG

64-bit unsigned integer

System.UInt64

float,
FLOAT

Single-precision floating point

System.Single

double, long double, DOUBLE

Double-precision floating point

System.Double

â€
In Win32 this type is an integer with a specially assigned meaning; in contrast, the CLR provides a specific type devoted to this meaning.

3.调用GetShortPathName这个API,简单的写法如下(编译通过的话),

using System;

using System.Runtime.InteropServices;

public class MSSQL_ServerHandler

{

[DllImport("kernel32.dll")]

public static extern int GetShortPathName

(

string path,

StringBuilder shortPath,

int shortPathLength

)

}

而我们之前的例子:

using System;

using System.Runtime.InteropServices;

public class MSSQL_ServerHandler

{

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]

public static extern int GetShortPathName

(

[MarshalAs(UnmanagedType.LPTStr)] string path,

[MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath,

int shortPathLength

)

}

对比可知,其中DllImport ,static,extern基本上是必须有的,其他CharSet,MarshalAs(…)是可选项,在这里即使没有,程序也是可以调用此API了。

说明:

1.MSSQL_ServerHandler. GetShortPathName 方法用 staticextern 修饰符声明并且具有 DllImport 属性,该属性使用默认名称GetShortPathName 通知编译器此实现来自kernel32.dll。若要对C# 方法使用不同的名称(如 getShort),则必须在DllImport 属性中使用 EntryPoint 选项,如下所示:

[DllImport("kernel32.dll", EntryPoint="getShort")]

2.使用MarshalAs(UnmanagedType.LPTStr)保证了在任何平台上都会得到LPTStr,否则默认的方式会把从C#中的字符串作为BStr传递。

现在如果是仅含有简单参数和返回值的WIN32 API,就都可以利用这种方法进行对照,简单的改写和调用了。

二.背后的原理 ―― 知其所以然,相关的知识

1.平台调用详原理

平台调用依赖于元数据在运行时查找导出的函数并封送其参数。下图显示了这一过程。

对非托管 DLL 函数的“平台调用调用

当“平台调用调用非托管函数时,它将依次执行以下操作:

查找包含该函数的 DLL

将该 DLL 加载到内存中。

查找函数在内存中的地址并将其参数推到堆栈上,以封送所需的数据。

注意 只在第一次调用函数时,才会查找和加载DLL 并查找函数在内存中的地址。

将控制权转移给非托管函数。

平台调用会向托管调用方引发由非托管函数生成的异常。

2.关于Attribute(属性,注意蓝色字)

属性可以放置在几乎所有声明中(但特定的属性可能限制它在其上有效的声明类型)。在语法上,属性的指定方法为:将括在方括号中的属性名置于其适用的实体声明之前。例如,具有DllImport 属性的类将声明如下:

[DllImport] public class MyDllimportClass { ... }

有关更多信息,请参见 DllImportAttribute 类

许多属性都带参数,而这些参数可以是定位(未命名)参数也可以是命名参数。任何定位参数都必须按特定顺序指定并且不能省略,而命名参数是可选的且可以按任意顺序指定。首先指定定位参数。例如,这三个属性是等效的:

[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]

[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]

[DllImport("user32.dll")]

第一个参数(DLL 名称)是定位参数并且总是第一个出现,其他参数为命名参数。在此例中,两个命名参数都默认为假,因此它们可以省略(有关默认参数值的信息,请参见各个属性的文档)。

在一个声明中可以放置多个属性,可分开放置,也可放在同一组括号中:

bool AMethod([In][Out]ref double x);

bool AMethod([Out][In]ref double x);

bool AMethod([In,Out]ref double x);

某些属性对于给定实体可以指定多次。此类可多次使用的属性的一个示例是 Conditional

[Conditional("DEBUG"), Conditional("TEST1")] void TraceMethod() {...}

注意 根据约定,所有属性名称都以单词“Attribute”结束,以便将它们与 .NET Framework 中的其他项区分。但是,在代码中使用属性时不需要指定属性后缀。例如,[DllImport] 虽等效于 [DllImportAttribute],但DllImportAttribute 才是该属性在 .NET Framework 中的实际名称。

3.MarshalAsAttribute 类

指示如何在托管代码和非托管代码之间封送数据。可将该属性应用于参数、字段或返回值。

该属性为可选属性,因为每个数据类型都有默认的封送处理行为。

大多数情况下,该属性只是使用 UnmanagedType 枚举标识非托管数据的格式。

例如,默认情况下,公共语言运行库将字符串参数作为 BStr 封送到 COM 方法,但是可以通过制定MarshalAs属性
将字符串作为 LPStrLPWStrLPTStrBStr 封送到非托管代码。某些 UnmanagedType 枚举成员需要附加信息。

三:进阶,如何处理含有复杂的参数和返回值类型的API的调用(To Be Continue…)
Api函数是构筑Windws应用程序的基石,每一种Windows应用程序开发工具,它提供的底层函数都间接或直接地调用了Windows API函数,同时为了实现功能扩展,一般也都提供了调用WindowsAPI函数的接口, 也就是说具备调用动态连接库的能力。VisualC#和其它开发工具一样也能够调用动态链接库的API函数。.NET框架本身提供了这样一种服务,允许受管辖的代码调用动态链接库中实现的非受管辖函数,包括操作系统提供的Windows API函数。它能够定位和调用输出函数,根据需要,组织其各个参数(整型、字符串类型、数组、和结构等等)跨越互操作边界。

下面以C#为例简单介绍调用API的基本过程:
动态链接库函数的声明
  动态链接库函数使用前必须声明,相对于VB,C#函数声明显得更加罗嗦,前者通过 Api Viewer粘贴以后,可以直接使用,而后者则需要对参数作些额外的变化工作。

  动态链接库函数声明部分一般由下列两部分组成,一是函数名或索引号,二是动态链接库的文件名。
  譬如,你想调用User32.DLL中的MessageBox函数,我们必须指明函数的名字MessageBoxA或MessageBoxW,以及库名字User32.dll,我们知道Win32 API对每一个涉及字符串和字符的函数一般都存在两个版本,单字节字符的ANSI版本和双字节字符的UNICODE版本。

  下面是一个调用API函数的例子:
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);

  其中入口点EntryPoint标识函数在动态链接库的入口位置,在一个受管辖的工程中,目标函数的原始名字和序号入口点不仅标识一个跨越互操作界限的函数。而且,你还可以把这个入口点映射为一个不同的名字,也就是对函数进行重命名。重命名可以给调用函数带来种种便利,通过重命名,一方面我们不用为函数的大小写伤透脑筋,同时它也可以保证与已有的命名规则保持一致,允许带有不同参数类型的函数共存,更重要的是它简化了对ANSI和Unicode版本的调用。CharSet用于标识函数调用所采用的是Unicode或是ANSI版本,ExactSpelling=false将告诉编译器,让编译器决定使用Unicode或者是Ansi版本。其它的参数请参考MSDN在线帮助.

  在C#中,你可以在EntryPoint域通过名字和序号声明一个动态链接库函数,如果在方法定义中使用的函数名与DLL入口点相同,你不需要在EntryPoint域显示声明函数。否则,你必须使用下列属性格式指示一个名字和序号。

[DllImport("dllname", EntryPoint="Functionname")]
[DllImport("dllname", EntryPoint="#123")]
值得注意的是,你必须在数字序号前加“#”
下面是一个用MsgBox替换MessageBox名字的例子:
[C#]
using System.Runtime.InteropServices;

public class Win32 {
[DllImport("user32.dll", EntryPoint="MessageBox")]
public static extern int MsgBox(int hWnd, String text, String caption, uint type);
}
许多受管辖的动态链接库函数期望你能够传递一个复杂的参数类型给函数,譬如一个用户定义的结构类型成员或者受管辖代码定义的一个类成员,这时你必须提供额外的信息格式化这个类型,以保持参数原有的布局和对齐。
结构体
C#提供了一个StructLayoutAttribute类,通过它你可以定义自己的格式化类型,在受管辖代码中,格式化类型是一个用StructLayoutAttribute说明的结构或类成员,通过它能够保证其内部成员预期的布局信息。布局的选项共有三种:

布局选项
描述
LayoutKind.Automatic
为了提高效率允许运行态对类型成员重新排序。
注意:永远不要使用这个选项来调用不受管辖的动态链接库函数。
LayoutKind.Explicit
对每个域按照FieldOffset属性对类型成员排序
LayoutKind.Sequential
对出现在受管辖类型定义地方的不受管辖内存中的类型成员进行排序。
传递结构成员
下面的例子说明如何在受管辖代码中定义一个点和矩形类型,并作为一个参数传递给User32.dll库中的PtInRect函数,
函数的不受管辖原型声明如下:
BOOL PtInRect(const RECT *lprc, POINT pt);
注意你必须通过引用传递Rect结构参数,因为函数需要一个Rect的结构指针。
[C#]
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}

[StructLayout(LayoutKind.Explicit]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}

class Win32API {
[DllImport("User32.dll")]
public static extern Bool PtInRect(ref Rect r, Point p);
}
类似你可以调用GetSystemInfo函数获得系统信息:
? using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO {
public uint dwOemId;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}
[DllImport("kernel32")]
static extern void GetSystemInfo(ref SYSTEM_INFO pSI);

SYSTEM_INFO pSI = new SYSTEM_INFO();
GetSystemInfo(ref pSI);

类成员的传递
同样只要类具有一个固定的类成员布局,你也可以传递一个类成员给一个不受管辖的动态链接库函数,下面的例子主要说明如何传递一个sequential顺序定义的MySystemTime类给User32.dll的GetSystemTime函数, 函数用C/C++调用规范如下:

void GetSystemTime(SYSTEMTIME* SystemTime);
不像传值类型,类总是通过引用传递参数.
[C#]
[StructLayout(LayoutKind.Sequential)]
public class MySystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
class Win32API {
[DllImport("User32.dll")]
public static extern void GetSystemTime(MySystemTime st);
}
回调函数的传递:
从受管辖的代码中调用大多数动态链接库函数,你只需创建一个受管辖的函数定义,然后调用它即可,这个过程非常直接。
如果一个动态链接库函数需要一个函数指针作为参数,你还需要做以下几步:
首先,你必须参考有关这个函数的文档,确定这个函数是否需要一个回调;第二,你必须在受管辖代码中创建一个回调函数;最后,你可以把指向这个函数的指针作为一个参数创递给DLL函数,.

回调函数及其实现:
回调函数经常用在任务需要重复执行的场合,譬如用于枚举函数,譬如Win32 API 中的EnumFontFamilies(字体枚举), EnumPrinters(打印机), EnumWindows (窗口枚举)函数. 下面以窗口枚举为例,谈谈如何通过调用EnumWindow 函数遍历系统中存在的所有窗口

分下面几个步骤:
1. 在实现调用前先参考函数的声明
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)
显然这个函数需要一个回调函数地址作为参数.
2. 创建一个受管辖的回调函数,这个例子声明为代表类型(delegate),也就是我们所说的回调,它带有两个参数hwnd和lparam,第一个参数是一个窗口句柄,第二个参数由应用程序定义,两个参数均为整形。

   当这个回调函数返回一个非零值时,标示执行成功,零则暗示失败,这个例子总是返回True值,以便持续枚举。
3. 最后创建以代表对象(delegate),并把它作为一个参数传递给EnumWindows 函数,平台会自动地 把代表转化成函数能够识别的回调格式。

[C#]
using System;
using System.Runtime.InteropServices;

public delegate bool CallBack(int hwnd, int lParam);

public class EnumReportApp {

[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);

public static void Main()
{
CallBack myCallBack = new CallBack(EnumReportApp.Report);
EnumWindows(myCallBack, 0);
}

public static bool Report(int hwnd, int lParam) {
Console.Write("窗口句柄为");
Console.WriteLine(hwnd);
return true;
}
}
指针类型参数传递:
  在Windows API函数调用时,大部分函数采用指针传递参数,对一个结构变量指针,我们除了使用上面的类和结构方法传递参数之外,我们有时还可以采用数组传递参数。

  下面这个函数通过调用GetUserName获得用户名
BOOL GetUserName(
LPTSTR lpBuffer, // 用户名缓冲区
LPDWORD nSize // 存放缓冲区大小的地址指针
);
 
[DllImport("Advapi32.dll",
EntryPoint="GetComputerName",
ExactSpelling=false,
SetLastError=true)]
static extern bool GetComputerName (
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,
  [MarshalAs(UnmanagedType.LPArray)] Int32[] nSize );
  这个函数接受两个参数,char * 和int *,因为你必须分配一个字符串缓冲区以接受字符串指针,
你可以使用String类代替这个参数类型,当然你还可以声明一个字节数组传递ANSI字符串,同样你也可以声明一个只有一个元素的长整型数组,使用数组名作为第二个参数。上面的函数可以调用如下:

byte[] str=new byte[20];
Int32[] len=new Int32[1];
len[0]=20;
GetComputerName (str,len);
MessageBox.Show(System.Text.Encoding.ASCII.GetString(str));
  最后需要提醒的是,每一种方法使用前必须在文件头加上:
  using System.Runtime.InteropServices;

 
http://blog.csdn.net/f7anty/article/details/7288806
http://blog.csdn.net/f7anty/article/details/7288743

抱歉!评论已关闭.