




已阅读5页,还剩12页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Microsoft .NET Compact Framework 上的高级 P/Invoke 发布日期 : 10/29/2004 | 更新日期 : 10/29/2004Jon Box,Dan FoxQuilogy 编著:Jonathan WellsMicrosoft Corporation适用于:Microsoft_ .NET Compact Framework 1.0Microsoft_ Visual Studio_ .NET 2003摘要:探讨 .NET Compact Framework 的高级互操作性。本页内容简介 封送处理复杂类型 封送处理结构中的字符串 封送处理结构中的定长字符串 小结 简介在上一篇文章“Microsoft .NET Compact Framework 上的 P/Invoke 和封送处理简介”中,我们讨论了 Microsoft .NET Compact Framework 和 Microsoft .NET Framework 中的平台调用服务如何允许托管代码调用驻留在非托管 DLL 中的函数,进而允许自定义以及操作系统 (Windows CE) API 可由为上述任何一种框架编写的应用程序访问。虽然此服务的很多功能在这两种框架中都是一样的,但由于 .NET Compact Framework 是完整的 .NET Framework 的一个子集,所以存在一些差异,有些差异我们已经在上一篇文章中进行了探讨。在本白皮书中,我们将集中讨论在封送处理结构时产生的两个特定问题,以及在 .NET Compact Framework 中如何对它们进行处理。返回页首 封送处理复杂类型正如上一篇文章中所提及的,.NET Compact Framework 中的封送拆收器和完整的 .NET Framework 中的封送拆收器之间的一个主要差异是:较轻型的 .NET Compact Framework 封送拆收器不能封送处理结构或类中的复杂对象(引用类型)。这就意味着,如果结构或类中有字段定义为在 .NET Compact Framework 和非托管代码之间不存在通用表示形式的类型(称为 blittable 类型,这些类型在上一篇文章中列出), 则该结构或类不能被完全地进行封送处理。从实际的角度来说,这意味着包含字符串指针或者定长字符缓冲的结构或类均不能被正确地封送处理。 作为一个例子,请考虑 Windows CE 上可用的用户通知 API。使用此 API,应用程序可以在特定的时间,或者在响应某个事件(比如同步)时,或者在更换 PC 卡时,显示通知对话框或引发某个应用程序的执行。因为 .NET Compact Framework 不包括执行此功能的托管类,所以需要该功能的开发人员就需要使用 P/Invoke 进行正确的操作系统调用。 要使用 Windows CE 通知 API (CeSetUserNotificationEx),用于定义什么事件激活此通知的结构 ( CE_NOTIFICATION_TRIGGER) 需要在托管代码中进行声明,并在 VB .NET 中进行如下的直接转换,其中 SYSTEMTIME 是另一个完全由 blittable 类型组成的结构, NotificationTypes 和 EventTypes 是映射至整数的枚举。Private Structure CE_NOTIFICATION_TRIGGER Dim dwSize As Integer Dim dwType As NotificationTypes Dim dwEvent As EventTypes Dim lpszApplication As String Dim lpszArguments As String Dim startTime As SYSTEMTIME Dim endTime As SYSTEMTIMEEnd Structure不幸的是,用于指定要执行的应用程序及其命令行参数的两个字符串值在非托管代码中定义为指向以 null 终止的 Unicode 字符串 (WCHAR *) 的指针。因此,.NET Compact Framework 封送拆收器不能正确地封送处理该结构,因为 String 是引用类型 (System.String)。注 正如我们上一篇文章所提及的, System.String 在 .NET Compact Framework 中是 blittable 类型,因为所有字符串均可视为 Unicode。但是,这只在将 String 直接传递给非托管函数时才适用,字符串用在结构或类中时并非如此。在完整的 .NET Framework 中,封送拆收器可以处理这种情况,因为它包含 MarshalAsAttribute。使用此属性,该结构可被重写为:Private Structure CE_NOTIFICATION_TRIGGER Dim dwSize As Integer Dim dwType As NotificationTypes Dim dwEvent As EventTypes Dim lpszApplication As String Dim lpszArguments As String Dim startTime As SYSTEMTIME Dim endTime As SYSTEMTIMEEnd Structure因此,在完整的 .NET Framework 中,结构中引用的字符串将被封送处理为以 null 终止的 Unicode 字符串,正如非托管函数所期望的那样。因为 .NET Compact Framework 不包括这种行为,所以您需要解决这个问题。返回页首 封送处理结构中的字符串要允许结构和类中的字符串指针被 .NET Compact Framework 正确地封送处理,有三个主要的解决方法:调用 thunking 层,使用不安全块,以及创建一个处理字符串指针的自定义类。 使用 thunking 层术语 thunking 以前表示将参数和返回值从 16 位转换为 32 位表示方式(或者相反)的代码。但是,在更一般的用法中,该术语仅是指创建一些处理数据转换的中间代码。对于 .NET Compact Framework 和 P/Invoke,thunking 层是一个非托管函数,该函数接受构成该结构的参数,创建非托管结构,并调用适当的函数。这就是 Visual Studio .NET 帮助和 MSDN 中描述的在结构中传递复杂对象的方法。要在上文的通知示例中使用该方法,您可以在嵌入式 Visual C+ 中创建一个非托管 DLL,该 DLL 导出一个接受 CE_NOTIFICATION_TRIGGER 结构的所有参数的函数。例如,您可以在 C 中创建一个使用通知服务( CeSetUserNotificationEx 函数)安排应用程序在特定时间运行的非托管函数,如下所示:HANDLE RunApplicationThunk (WCHAR *lpszApplication, WCHAR *lpszArguments, SYSTEMTIME startTime) HANDLE returnCode; CE_NOTIFICATION_TRIGGER trigger; / populate the structure trigger.dwSize = sizeof(trigger); trigger.dwType = 2; /CNT_TIME trigger.dwEvent = 0; /NONE trigger.lpszApplication = lpszApplication; trigger.lpszArguments = lpszArguments; trigger.startTime = startTime; trigger.endTime = 0; /empty, not used / call the native Windows CE API function returnCode = CeSetUserNotificationEx(0,&trigger, 0); return returnCode;注意,在上面的列表中,非托管函数默认设定了该结构的 dwType、dwEvent 和 endTime 成员,并且将这些参数传递给了 CeSetUserNotificationEx 函数。在托管代码中,此函数使用 DLLImportAttribute 在 C# 中以如下方式进行声明。因为当字符串直接传递给非托管函数时 .NET Compact Framework 将其视为 blittable 类型,所以您的声明只需简单地使用字符串:DLLImport(MyNotification.DLL, SetLastError=trueprivate static extern IntPtr RunApplicationThunk( string lpszApplication, string lpszArguments, SYSTEMTIME startTime);您的非托管 thunking 函数(以 C# 显示如下)应该封装到一个托管类中,正如我们的上一篇文章中所讨论的,并且通过该类的一个静态(VB 中的 shared)方法进行公开。public static void RunApplication(string application, string arguments, DateTime startTime) / translate the DateTime values into managed SYSTEMTIME structures SYSTEMTIME startStruct = DateTimeToSystemTime( start ); try / call the thunking layer IntPtr hNotify = RunApplicationThunk(application, arguments, startStruct); / handle errors if(hNotify = IntPtr.Zero) int errorNum = Marshal.GetLastWin32Error(); HandleCeError(New WinCeException(Could not set notification , errorNum), RunApplication); catch (Exception ex) HandleCeError(ex, RunApplication); 注意在这种情况下,您甚至不需要在托管代码中声明 CE_NOTIFICATION_TRIGGER 结构,因为托管方法可以编写为接受所有适当的参数,这样就更完全地封装了底层的操作系统交互。或者,您可以创建此结构的托管版本,如上文所示,然后只需将该结构传递给方法。如果遇到错误(返回句柄是零),则将创建一个自定义异常并以从操作系统获取的错误代码进行填充,正如上一篇文章中所讨论的一样。注您可以看到,用于将托管代码中的 DateTime 变量转换为 SYSTEMTIME 结构的代码没有给出,它包括在自定义的方法 DateTimeToSystemTime 中。该方法调用位于 coredll.dll 中的 FileTimeToLocalFileTime 和 FileTimeToSystemFileTime Windows CE API 函数。 这种方法的明显不足就是开发人员必须安装和使用嵌入式 Visual C+,对于很多 Visual Basic 和 .NET Framework 开发人员而言,这是一个令人生畏的任务。正是由于这个原因,我们还将讨论解决相同问题的其它两种方法,这两种方法只使用 .NET Compact Framework 中的托管代码。使用不安全字符串指针传递结构中字符串的第二种方法是使用 C# 中的 unsafe 和 fixed 关键字(在 VB 中没有相对应的关键字)。虽然这种方法允许您只编写托管代码,但它是以禁用公共语言运行库的代码验证功能为代价的,该功能用于验证托管代码只访问分配的内存,并且所有的方法调用在参数数量和类型上都符合方法签名。使用不安全代码就意味着您希望使用指针对内存进行直接管理。 注正如上一篇文章中所述,除了创建无法验证的代码,完整的 .NET Framework 中的不安全代码只能在受信任的环境中执行。但 .NET Compact Framework 1.0 版中没有包括代码访问安全性 (CAS),所以目前这还不是问题。fixed 关键字与 unsafe 关键字一起使用,它用于确保公共语言运行库的垃圾回收器 (GC) 在对象被非托管函数访问时不会试图释放该对象。 当然,除了丧失了代码验证功能以外,这种方法的另一个不足就是不能在 VB 中直接使用。但是,您可以在 C# 中创建一个使用不安全代码的程序集,然后从 VB 中调用该程序集。 要使用 unsafe 和 fixed 关键字,您需要将声明了指针的结构以及使用了指针的方法标记为不安全的。例如,可以使用不安全代码创建相同的 RunApplication 方法,如下所示。DllImport(coredll.dll,SetLastError=true)private static extern IntPtr CeSetUserNotificationEx( IntPtr h, ref CE_NOTIFICATION_TRIGGER nt, IntPtr un );private unsafe struct CE_NOTIFICATION_TRIGGER public uint dwSize; public CNT_TYPE NotificationTypes; /enumeration public NOTIFICATION_EVENT EventTypes; /enumeration public char *lpszApplication; public char *lpszArguments; public SYSTEMTIME startTime; public SYSTEMTIME endTime;public static unsafe void RunApplication( string application, string arguments, DateTime start ) CE_NOTIFICATION_TRIGGER nt = new CE_NOTIFICATION_TRIGGER(); nt.dwSize = (uint)Marshal.SizeOf( typeof(CE_NOTIFICATION_TRIGGER) ); nt.dwType = NotificationTypes.Time; nt.dwEvent = EventTypes.None; nt.startTime = DateTimeToSystemTime( start ); nt.endTime = new SYSTEMTIME(); /skip this member try if (application = null) | (application.Length = 0) throw new ArgumentNullException(); fixed (char *pApp = application.ToCharArray( ) if (arguments = null) | (arguments.Length = 0) ) arguments = ; fixed (char *pArgs = arguments.ToCharArray() nt.lpszApplication = pApp; nt.lpszArguments = pArgs; / call the native function IntPtr hNotify = CeSetUserNotificationEx(IntPtr.Zero, ref nt, IntPtr.Zero); if( hNotify = IntPtr.Zero ) int errorNum = Marshal.GetLastWin32Error(); HandleCeError(New WinCeException( Could not set notification , errorNum), RunApplication); catch (Exception ex) HandleCeError(ex, RunApplication); 在这个例子中,您可以看到我们首先使用 DllImportAttribute 对 CeSetUserNotificationEx Windows CE API 函数进行了声明。 CE_NOTIFICATION_TRIGGER 被标记为 ref 参数(在 VB 中为 ByRef ),因为它被定义为结构。这样做是有必要的,因为只有将结构声明为 ref 参数,.NET Compact Framework 才传递结构的地址。如果 CE_NOTIFICATION_TRIGGER 被声明为类,则 CeSetUserNotificationEx 的参数可能是通过值传递的,因为 .NET Compact Framework 自动传递引用类型的地址。虽然,在很多情况下,在托管代码中将非托管函数所期望的结构声明为结构或声明为类都没有关系,但这里的 SYSTEMTIME structure 是一个例外。此处, SYSTEMTIME 必须声明为结构,因为 CeSetUserNotificationEx 函数期望 CE_NOTIFICATION_TRIGGER 内联包含整个结构。如果 SYSTEMTIME 声明为类,则只能封送处理指向该类的 4 字节的指针。接着,声明了 CE_NOTIFICATION_TRIGGER 结构,它具有指向成员 lpszApplication 和 lpszArguments 的字符数组的指针,因此使用 unsafe 关键字对它进行了标记。注记住,与完整的 .NET Framework 不同,您不需要使用 .NET Compact Framework 中的 StructLayoutAttribute 修饰您的结构,因为所有的结构都自动为 LayoutKind.Sequential。最后,RunApplication 方法以与前面示例中相同的签名进行声明,只是此时该方法创建了一个 CE_NOTIFICATION_TRIGGER 的实例并继而填充其成员。如前面示例中所示,一些成员被设置为执行给定应用程序所需的值,而 SYSTEMTIME DateTimeToSystemTime 方法进行填充。要填充嵌入于该结构中的字符串指针,该方法首先进行检查以确保应用程序名非空而且不是空字符串。如果为空或者是空字符串,则会引发 ArgumentNullException 。如果都不是,则字符数组的指针会在固定语句中声明,并且使用 String 类的 ToCharArray 方法进行填充。注意,字符数组会固定于固定块范围内的内存中。然后参数指针以同样的方式进行填充。该结构的 lpszApplication 和 lpszArguments 成员以传递给 CeSetUserNotificationEx 函数的指针和结构进行填充。 注在这个例子中, CeSetUserNotificationEx 函数声明的第三个参数为 IntPtr ,它事实上需要一个 CE_USER_NOTIFICATION 类型的结构。这个声明是必需的,这样 RunApplication 方法就可以将 IntPtr.Zero 传递为该函数的这一参数。对于 CeSetUserNotificationEx 的其他用法,您需要真正地传递该结构(例如弹出一个通知对话框)。在这些情况下,您可以进行 CeSetUserNotificationEx 的第二次声明以重载第一次的声明。编译器将根据参数选择适当的声明。如果有错误发生(函数返回的句柄为空),则自定义的 WinCeException 将被创建并传递给错误处理程序。使用托管字符串指针最后一种您可以用来在 .NET Compact Framework 中封送处理结构中字符串的方法是创建您自己的托管字符串指针类。这种方法的优点是它在 C# 和 VB 中都可以使用,并且一旦字符串指针类创建完毕就可以在各种情况下使用。但是,它确实需要与 Windows CE 操作系统的内存分配 API 进行一些交互。首先,您可以创建一个声明和调用必要的内存管理 API 的托管类。该类可以公开一些共享的方法以分配非托管的内存块,释放该内存,调整非托管内存块的大小,将托管字符串复制到非托管内存中。Memory 类的 VB 版显示如下。Public Class Memory _ Private Shared Function LocalAlloc(ByVal uFlags As Integer, _ ByVal uBytes As Integer) As IntPtr End Function _ Private Shared Function LocalFree(ByVal hMem As IntPtr) As IntPtr End Function _ Private Shared Function LocalReAlloc(ByVal hMem As IntPtr, _ ByVal uBytes As Integer, ByVal fuFlags As Integer) As IntPtr End Function Private Const LMEM_FIXED As Integer = 0 Private Const LMEM_MOVEABLE As Integer = 2 Private Const LMEM_ZEROINIT As Integer = &H40 Private Const LPTR = (LMEM_FIXED Or LMEM_ZEROINIT) Allocates a block of memory using LocalAlloc Public Shared Function AllocHLocal(ByVal cb As Integer) As IntPtr Return LocalAlloc(LPTR, cb) End Function Frees memory allocated by AllocHLocal Public Shared Sub FreeHLocal(ByVal hlocal As IntPtr) If Not hlocal.Equals(IntPtr.Zero) Then If Not IntPtr.Zero.Equals(LocalFree(hlocal) Then Throw New Win32Exception(Marshal.GetLastWin32Error() End If hlocal = IntPtr.Zero End If End Sub Resizes a block of memory previously allocated with AllocHLocal Public Shared Function ReAllocHLocal(ByVal pv As IntPtr, _ ByVal cb As Integer) As IntPtr Dim newMem As IntPtr = LocalReAlloc(pv, cb, LMEM_MOVEABLE) If newMem.Equals(IntPtr.Zero) Then Throw New OutOfMemoryException End If Return newMem End Function Copies the contents of a managed string to unmanaged memory Public Shared Function StringToHLocalUni( _ ByVal s As String) As IntPtr If s Is Nothing Then Return IntPtr.Zero Else Dim nc As Integer = s.Length Dim len As Integer = 2 * (1 + nc) Dim hLocal As IntPtr = AllocHLocal(len) If hLocal.Equals(IntPtr.Zero) Then Throw New OutOfMemoryException Else Marshal.Copy(s.ToCharArray(), 0, hLocal, s.Length) Return hLocal End If End If End FunctionEnd Class正如列表中所示,非托管函数 LocalAlloc, LocalFree,和 LocalRealloc 使用 DllImportAttribute 进行声明,并分别包装于 AllocHLocal, FreeHLocal和 ReAllocHLocal 方法中。最后, StringToHLocalUni 方法为给定的字符串分配适当大小的非托管内存块(每个字符 2 个字节,因为 .NET Compact Framework 只支持 Unicode),然后将字符数组复制到指向非托管块的 IntPtr 中。一旦内存类创建完成,就可以创建一个简单的托管字符串指针结构,如下所示。Public Structure StringPtr Private szString As IntPtr Public Sub New(ByVal s As String) Me.szString = Memory.StringToHLocalUni(s) End Sub Public Overrides Function ToString() As String Return Marshal.PtrToStringUni(Me.szString) End Function Public Sub Free() Memory.FreeHLocal(Me.szString) End SubEnd Structure正如您所见, StringPtr 结构包含了私有的 IntPtr 以跟踪非托管内存中的字符串指针。接下来,该指针通过调用 Memory 类的 StringToHLocalUni 方法(将托管字符串传递给该方法)在构造函数中进行填充。然后, ToString 方法重写 System.ToString 方法以返回给定指针的托管字符串,而 Free 方法则释放为该字符串分配的非托管内存。然后, Memory 类和 StringPtr 结构都可以放置于程序集中,并能够从需要托管字符串指针的各个智能设备项目中进行引用。要在 CE_NOTIFICATION_TRIGGER 结构的情况下使用使用 StringPtr 结构,您可以对该结构的 lpszApplication 和 lpszArguments 成员使用 StringPtr 结构进行声明。另外,因为这些成员需要使用 StringPtr 类的 Free 方法进行释放,因此 CE_NOTIFICATION_TRIGGER 实现 IDisposable 接口是非常合理的。然后 Dispose 方法可以用来调用这两个成员的 Free 方法。您还可以将公共的构造函数添加到该类中,不仅用于创建 StringPtr 成员,而且还对建立通知所需要的其它成员进行默认设置。该结构的 C# 代码如下所示。 private struct CE_NOTIFICATION_TRIGGER: IDisposable public uint dwSize; public CNT_TYPE dwType; public NOTIFICATION_EVENT dwEvent; public StringPtr lpszApplication; public StringPtr lpszArguments; public SYSTEMTIME startTime; public SYSTEMTIME endTime; public CE_NOTIFICATION_TRIGGER( string application, string arguments, DateTime start ) dwSize = (uint)Marshal.SizeOf( typeof(CE_NOTIFICATION_TRIGGER) ); dwType = CNT_TYPE.CNT_TIME; dwEvent = NOTIFICATION_EVENT.NONE; lpszApplication = new StringPtr( application ); lpszArguments = new StringPtr( arguments ); startTime = DateTimeToSystemTime( start ); endTime = new SYSTEMTIME(); /other constructors possible here public void Dispose() lpszApplication.Free(); lpszArguments.Free(); 最后, RunApplication 方法可以简单地创建一个 CE_NOTIFICATION_TRIGGER 结构的新实例并调用 CeSetUserNotificationEx 函数。因为该结构实现了 IDisposable 接口,所以在 C# 中,您可以使用 using 语句以确保编译器在调用非托管函数之后自动调用 Dispose 方法。public static void RunApplication( string application, string arguments, DateTime start ) CE_NOTIFICATION_TRIGGER nt = new CE_NOTIFICATION_TRIGGER( application, arguments, start ); using( nt ) IntPtr hNotify = CeSetUserNotificationEx( IntPtr.Zero, ref nt, IntPtr.Zero ); if( hNotify = IntPtr.Zero ) int errorNum = Marshal.GetLastWin32Error(); HandleCeError(New WinCeException( Could not set notification , errorNum), RunApplication); 返回页首 封送处理结构中的定长字符串使用 .NET Compact Framework 的 P/Invoke 服务时会引起的第二种情况涉及到封送处理结构中的固定长度字符串或字符数组。例如,用来将应用程序图标放置于系统栏中或者从其中移除的 Shell_NotifyIcon 函数接受一个指向在 Windows CE SDK 中定义的结构的指针,如下所示:typedef struct _NOTIFYICONDATA DWORD cbSize;HWND hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; HICON hIcon; WCHAR szTip64; NOTIFYICONDATA, *PNOTIFYICONDATA; 您可以看到该结构中最后一个字段(该字段保存了当光标处于图标上方时显示的工具提示的文本)被定义为 64 个元素的字符数组。在 VB .NET 中对该结构的直接转换如下所示:Private Structure NOTIFYICONDATA Public cbSize As Integer Public hWnd As IntPtr Public uID As Integer Public uFlags As Integer Public uCallbackMessage As Integer Public hIcon As IntPtr Public szTip() As Char Public Sub New(ByVal toolTip As String) szTip = toolTip.ToCharArray End SubEnd Structure不幸的是,这个简单的转换不能正常工作,因为虽然 System.Char 是 blittable 类型,字符数组在运行时却作为 4 字节的数组指针进行封送处理。如前所述,完整的 .NET Framework 通过 MarshalAs 属性确实支持这种情况,在这个例子中,通过使用此属性修饰该数组并将 UnmangedType 枚举的 ByValTStr 值传递给它的构造函数。因为存在这种情况,所以本质上您有两种选择。第一种是创建一个正确总长度的字节数组,然后使用 Marshal 类的方法,或直接通过不安全代码中的指针,将该结构的各个字段复制到此字节数组中或从其中取出;基本上这是手工进行封送处理的。然后可以创建字节数组的指针并传递给非托管函数。但是,因为这种方法较复杂,所以本文的以下部分将集中介绍一种联合使用 .NET Compact Framework 的封送拆收器以及少量自定义封送处理的方法。注这种方法的关键是 NOTIFYICONDATA 的 szTip 成员是该结构的最后一个成员。如果情况不是如此,则将需要自定义程度更高的方法。要使用这种方法,您首先必须在您的类中进行适当的非托管声明。如前所述,一种最佳做法是在一个类中将这些函数声明为私有函数,然后让该类公开静态方法(VB 中的共享方法)以执行该功能。在这种情况下, DestroyIcon, RegisterWindowMessage,和 Shell_NotifyIcon 函数,以及 NOTIFYICONDATA 结构和一些常量都需要进行声明,如下所示。Private Structure NOTIFYICONDATA Public cbSize As Integer Public hWnd As IntPtr Public uID As Integer Public uFlags As Integer Public uCallbackMessage As Integer Public hIcon As IntPtrEnd Structure _Private Shared Function RegisterWindowMessage(ByVal lpMessage As String) As IntegerEnd Function _Private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As IntPtrEnd Function _Private Shared Function Shell_NotifyIcon( _ ByVal dwMessage As TrayConstants, _ ByVal lpData As IntPtr) As BooleanEnd FunctionPrivate Enum TrayConstants As Integer NIM_ADD = 0 NIM_DELETE = 2 NIF_ICON = 2 NIF_MESSAGE = 1 NIF_TIP = 4 NIF_ALL = NIF_ICON Or NIF_MESSAGE Or NIF_TIPEnd Enum此处需要注意的关键一点是 szTip 字段不包括在 NOTIFYICONDATA 结构中,因为它需要手动进行封送处理。另外,RegisterWindowMessage 函数将被用以创建唯一的消息标识符来填充 uCallbackMessage 字段,并最终在通知图标被用户激活时通知应用程序。DestroyIcon 函数用于释放系统栏中图标所使用的内存。最后,您可以看到,Shell_NotifyIcon 方法被声明为接受一个 TrayConstants 类型的参数(该参数部分定义了该函数将执行哪种操作,比如添加或移除图标)和一个需要用包含 NOTIFYICONDATA 结构的指针进行填充的非托管指针。然后对 Shell_NotifyIcon 函数的调用可以包装在一个名为 CreateNotifyIcon 的共享方法中,如下所示。 Sets up the notify icon and returns the message to hook events forPublic Shared Function CreateNotifyIcon( _ ByVal notifyWindow
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2022年度企业内部审计工作报告
- 光伏施工安全措施方案
- 2025年绿色建筑材料可持续性可行性研究报告
- 2025年旅游市场调研可行性研究报告乡村旅游产业发展前景
- 电商行业竞争策略2025年趋势分析可行性报告
- 2025年家居用品行业采购成本分析及降低策略可行性研究报告
- 【正版授权】 ISO/TR 24315-2:2025 EN Intelligent transport systems - Management of electronic traffic regulations (METR) - Part 2: Operational concepts (ConOps)
- 2025江苏连云港恒驰实业有限公司招聘5人模拟试卷及答案详解(易错题)
- 2025春季国家电投广东公司校园招聘模拟试卷附答案详解
- 2025安徽淮南高新区部分学校引进紧缺专业人才招聘39人考前自测高频考点模拟试题及1套完整答案详解
- 2025年国企中层干部竞聘笔试题含答案
- 禁止攀爬安全课件
- 渝22TS02 市政排水管道附属设施标准图集 DJBT50-159
- 中国金融黑灰产治理研究报告2025-非法代理维权的识别标准与溯源治理505mb
- 涉密测绘成果管理制度
- 2025-2030中国汽车结构胶行业市场发展趋势与前景展望战略研究报告
- T/CHC 1005-2023破壁灵芝孢子粉
- 高一上学期《国庆假期安全教育+时间规划》主题班会课件
- 2025年铁路货装值班员(高级)职业技能鉴定参考试题库(含答案)
- 2024年单招数学函数的性质专项复习试题和答案
- 星级酒店的各类客房类型介绍
评论
0/150
提交评论