C#与DLL和COM的混合编程(1)-C#调用C++写的非托管的DLL中导出的函数
Posted on 2007-08-12 13:11 sunrack 阅读(1088) 评论(0) 编辑 收藏 举报
C#调用C++写的非托管的DLL中导出的函数
Platform invoke是一个使得托管代码(managed code)能够调用DLL中实现的非托管函数(unmanaged functions)的服务(service),例如:那些Win32 API中的函数。它定位(locate)并且调用(invoke)导出的函数,在需要的时候,跨越交互边界列集(marshal)它的参数(integers, strings, arrays, structures等)。
Platform invoke 在运行时(run time)依赖元数据(metadata)来定位导出函数并列集参数.下图说明了这个过程:
下面介绍调用DLL非托管函数的过程
1. 指定DLL中的函数
至少,你要制定一个函数的名字和包含这个函数的DLL
注意ANSI和Unicode版本函数的差别
还可以改变DLL中的函数的名字,例如把MessageBoxA改为MsgBox
using System.Runtime.InteropServices;
public class Win32
{
[DllImport("user32.dll", EntryPoint = "MessageBoxA")]
public static extern int MsgBox(int hWnd, String text, String caption,
uint type);
}
2. 创建一个类来包含DLL中的函数
你可以使用一个已存在的类,或者为每个非托管函数创建一个单独的类,或者为一组非托管函数创建一个类
在一个托管类中包装(Wrapping)常用的DLL函数是一种封装平台相关功能的有效的方法,虽然在某些情况下它(Wrapping)并不是必须的。提供一个包装类是一种方便的方法,因为定义DLL的函数很麻烦又容易出错。如果你使用Visual Basic 或者C#,你必须在C#的Class或者Visual Basic的module内声明DLL的函数
在类的内部,为每一个你想调用的DLL函数定义一个static的方法。定义可以包含其他的信息:例如字符集(character set)或者传递方法参数的调用规则(calling convention),如果忽略了这些信息,将使用默认的设定。
一份完整的声明和默认设定的列表 see Creating Prototypes in Managed Code.
一旦包装完成,你可以像调用其他的static方法一样调用这些方法。Platform invoke自动处理下面的调出函数
当为platform invoke设计一个托管的类的时候,要考虑类和DLL函数之间的关系。例如:
" 在一个已经存在的类中声明DLL函数
" 为每一个DLL函数创建一个单独的类,使得函数独立并且容易找到
" 创建一个包含一系列相关DLL函数的类来逻辑分组并且减少开销
3. 在托管代码中创建函数签名(signature)
[C#] 使用 DllImportAttribute 来制定DLL和函数。把方法标记为static and extern.
4.
[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);
}
[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("Kernel32.dll")]
public static extern void GetSystemTime(MySystemTime st);
}
回调函数的理想用到的情形是一个任务重复的执行。另一个通常的用法是enumeration函数,例如:EnumFontFamilies,EnumPrinters, 和EnumWindows
实现一个回调函数
下面的过程描述了怎样在一个托管的应用中使用P/Invoke打印本机上的每一个窗口的handle。特别的是,这个例子使用EnumWindows函数来处理窗口的列表并且,一个托管的回调函数(named CallBack)用来打印窗口的handle的值
[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("Window handle is ");
Console.WriteLine(hwnd);
return true;
}
}
实现一个回调函数
(1). 在实现之间,查看一下EnumWindows函数. EnumWindows的(signature)如下:
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
一个线索是这个函数需要一个callback的参数lpEnumFunc
(2). 创建一个托管的函数。例子声明了一个delegate类型(called CallBack),包含两个参数(hwnd and lparam).
(3). 创建一个代理(delegate)然后把它作为EnumWindows的参数传递给EnumWindows,Platform invoke自动转换delegate为回调函数。
(4). 确定垃圾收集器在回调函数完成之前不会回收delegate .当你把delegate当作参数传递时,或者传递一个包含delegate的结构时,在调用的过程中,他不会被回收,这样,在下面这个enumeration的例子中,callback函数在函数调用返回之前完成,所以托管的调用者不需要额外的工作。
如果,callback在函数调用返回之后可以被调用,托管的调用者必须保证在回调函数完成之前delegate没有被垃圾收集。防止垃圾收集的信息参见Interop Marshaling" 实现回调函数(Implementing Callback Functions)
回调函数是在托管应用中的代码来帮助非托管的DLL函数完成一个功能。下面的例子说明了回调函数的元素和怎样在托管代码中实现回调。
回调函数基础
从托管代码中调用DLL中的函数,创建一个DLL中函数的托管的定义然后调用它,这个过程是很直接的
使用DLL的需要回调的函数需要一些额外的步骤。首先,必须通过查找帮助确定一个函数是否需要回调,然后,在你的托管代码中创建回调函数,最后,调用DLL的函数,传递指向回调函数的指针。下图描述了这个过程声明传递Classes
可以传递类的成员给一个非托管DLL的函数,由于类的成员是有固定的布局(layout)。下面的例子说明了怎样传递MySystemTime类顺序定义的成员给User32.dll中的GetSystemTime函数
GetSystemTime 的函数签名(signature)如下
void GetSystemTime(SYSTEMTIME* SystemTime);调用DLL中的函数.
调用你的托管类的方法像你调用其他的方法一样,传递结构体和实现回调函数的情形例外
" 传递结构体(Passing Structures)
声明传递结构
下面的例子显示了怎样在托管代码中定义一个Point和Rect 结构,然后把它们当作参数传递给User32.dll 中的PtInRect函数。
PtInRect的函数签名(signature)如下
BOOL PtInRect(const RECT *lprc, POINT pt);
注意必须传递一个 Rect的引用,因为这个函数需要一个指向RECT类型的指针
Platform invoke是一个使得托管代码(managed code)能够调用DLL中实现的非托管函数(unmanaged functions)的服务(service),例如:那些Win32 API中的函数。它定位(locate)并且调用(invoke)导出的函数,在需要的时候,跨越交互边界列集(marshal)它的参数(integers, strings, arrays, structures等)。
Platform invoke 在运行时(run time)依赖元数据(metadata)来定位导出函数并列集参数.下图说明了这个过程:
下面介绍调用DLL非托管函数的过程
1. 指定DLL中的函数
至少,你要制定一个函数的名字和包含这个函数的DLL
注意ANSI和Unicode版本函数的差别
还可以改变DLL中的函数的名字,例如把MessageBoxA改为MsgBox
using System.Runtime.InteropServices;
public class Win32
{
[DllImport("user32.dll", EntryPoint = "MessageBoxA")]
public static extern int MsgBox(int hWnd, String text, String caption,
uint type);
}
2. 创建一个类来包含DLL中的函数
你可以使用一个已存在的类,或者为每个非托管函数创建一个单独的类,或者为一组非托管函数创建一个类
在一个托管类中包装(Wrapping)常用的DLL函数是一种封装平台相关功能的有效的方法,虽然在某些情况下它(Wrapping)并不是必须的。提供一个包装类是一种方便的方法,因为定义DLL的函数很麻烦又容易出错。如果你使用Visual Basic 或者C#,你必须在C#的Class或者Visual Basic的module内声明DLL的函数
在类的内部,为每一个你想调用的DLL函数定义一个static的方法。定义可以包含其他的信息:例如字符集(character set)或者传递方法参数的调用规则(calling convention),如果忽略了这些信息,将使用默认的设定。
一份完整的声明和默认设定的列表 see Creating Prototypes in Managed Code.
一旦包装完成,你可以像调用其他的static方法一样调用这些方法。Platform invoke自动处理下面的调出函数
当为platform invoke设计一个托管的类的时候,要考虑类和DLL函数之间的关系。例如:
" 在一个已经存在的类中声明DLL函数
" 为每一个DLL函数创建一个单独的类,使得函数独立并且容易找到
" 创建一个包含一系列相关DLL函数的类来逻辑分组并且减少开销
3. 在托管代码中创建函数签名(signature)
[C#] 使用 DllImportAttribute 来制定DLL和函数。把方法标记为static and extern.
4.
[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);
}
[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("Kernel32.dll")]
public static extern void GetSystemTime(MySystemTime st);
}
回调函数的理想用到的情形是一个任务重复的执行。另一个通常的用法是enumeration函数,例如:EnumFontFamilies,EnumPrinters, 和EnumWindows
实现一个回调函数
下面的过程描述了怎样在一个托管的应用中使用P/Invoke打印本机上的每一个窗口的handle。特别的是,这个例子使用EnumWindows函数来处理窗口的列表并且,一个托管的回调函数(named CallBack)用来打印窗口的handle的值
[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("Window handle is ");
Console.WriteLine(hwnd);
return true;
}
}
实现一个回调函数
(1). 在实现之间,查看一下EnumWindows函数. EnumWindows的(signature)如下:
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
一个线索是这个函数需要一个callback的参数lpEnumFunc
(2). 创建一个托管的函数。例子声明了一个delegate类型(called CallBack),包含两个参数(hwnd and lparam).
(3). 创建一个代理(delegate)然后把它作为EnumWindows的参数传递给EnumWindows,Platform invoke自动转换delegate为回调函数。
(4). 确定垃圾收集器在回调函数完成之前不会回收delegate .当你把delegate当作参数传递时,或者传递一个包含delegate的结构时,在调用的过程中,他不会被回收,这样,在下面这个enumeration的例子中,callback函数在函数调用返回之前完成,所以托管的调用者不需要额外的工作。
如果,callback在函数调用返回之后可以被调用,托管的调用者必须保证在回调函数完成之前delegate没有被垃圾收集。防止垃圾收集的信息参见Interop Marshaling" 实现回调函数(Implementing Callback Functions)
回调函数是在托管应用中的代码来帮助非托管的DLL函数完成一个功能。下面的例子说明了回调函数的元素和怎样在托管代码中实现回调。
回调函数基础
从托管代码中调用DLL中的函数,创建一个DLL中函数的托管的定义然后调用它,这个过程是很直接的
使用DLL的需要回调的函数需要一些额外的步骤。首先,必须通过查找帮助确定一个函数是否需要回调,然后,在你的托管代码中创建回调函数,最后,调用DLL的函数,传递指向回调函数的指针。下图描述了这个过程声明传递Classes
可以传递类的成员给一个非托管DLL的函数,由于类的成员是有固定的布局(layout)。下面的例子说明了怎样传递MySystemTime类顺序定义的成员给User32.dll中的GetSystemTime函数
GetSystemTime 的函数签名(signature)如下
void GetSystemTime(SYSTEMTIME* SystemTime);调用DLL中的函数.
调用你的托管类的方法像你调用其他的方法一样,传递结构体和实现回调函数的情形例外
" 传递结构体(Passing Structures)
声明传递结构
下面的例子显示了怎样在托管代码中定义一个Point和Rect 结构,然后把它们当作参数传递给User32.dll 中的PtInRect函数。
PtInRect的函数签名(signature)如下
BOOL PtInRect(const RECT *lprc, POINT pt);
注意必须传递一个 Rect的引用,因为这个函数需要一个指向RECT类型的指针