选择叠加 DLL





5.00/5 (7投票s)
一个显示选择覆盖层并通知调用者何时调整大小以及最终矩形的 API。
引言
这是一个显示选择覆盖层并通知调用者何时调整大小以及最终矩形的 API。
背景
我是荷兰皇家海军的一名程序员。几周前,一位同事问我是否知道一种显示选择覆盖层的方法(例如 Windows 资源管理器在其列表视图中选择文件时使用的透明正方形)。本示例提供的源代码就是该需求的成果。
使用代码
要使用该代码,您需要包含 SelectionOverlay.h 并链接到 SelectionOverlay.lib 或延迟加载这些调用。此外,在包含 h 文件之前,您还需要包含 windows.h。
在 VB 或 C# 中,您可以使用包装器。(为方便起见,我已将 DLL 的编译压缩版本放在 VB Test 目录中。)
您将填充用于调用的结构,其中包含必要的信息,例如此示例摘自 test.cpp,其中已重写 Alpha Bitmap 的填充(可选):
... OurInstance = new CallBackTest(); ... CallBackTest::CallBackTest() { BorderWidth = 2; CheckWidth = 1 + BorderWidth; ColorBorder = CreateAlphaColor(RGB(0, 0, 200), 200); ColorBackground = CreateAlphaColor(RGB(75, 128, 255), 50); ColorBackground2 = CreateAlphaColor(RGB(100, 128, 255), 75); ColorBackground3 = CreateAlphaColor(RGB(0, 64, 128), 63); } bool CallBackTest::OnFillAlpha(void* Tag, HBITMAP BitmapDIBHandle) { bool Result = false; BITMAP Bitmap = {0}; GetObject(BitmapDIBHandle, sizeof(BITMAP), &Bitmap); if ((Bitmap.bmWidthBytes * Bitmap.bmHeight) >= sizeof(long)) { long* Pixels = (long*) Bitmap.bmBits; if (Pixels) { Result = true; long BorderRight = Bitmap.bmWidth - CheckWidth; long BorderBottom = Bitmap.bmHeight - CheckWidth; for (long Y = 0; Y < Bitmap.bmHeight; Y++) { for (long X = 0; X < Bitmap.bmWidth; X++) { long Index = ((Y * Bitmap.bmWidth) + X); if ((X < BorderWidth) || (X > BorderRight) || (Y < BorderWidth) || (Y > BorderBottom)) { Pixels[Index] = ColorBorder; } else if (((Y % 8) == 1) || ((X % 8) == 1)) { Pixels[Index] = ColorBackground3; } else if ((X & 2 & Y) != 2) { Pixels[Index] = ColorBackground; } else { Pixels[Index] = ColorBackground2; } } } } } return Result; } bool CallBackTest::OnFillRGB(void* Tag, HDC hDC, int Width, int Height) { //no need to be implemented, but this way u can place a breakpoint return false; } void CallBackTest::OnMove(void* Tag, RECT Overlay) { //no need to be implemented, but this way u can place a breakpoint int c =2; } void CallBackTest::OnEnd(void* Tag, RECT Overlay) { //just a breakpoint moment here atm, but implementation required int c =2; } //... Start of the code on a mouse down SELECTIONOVERLAYDATA_CLASS Temp = { 0 }; //... Needed even if u implement FillAlpha, in case user fails we fill it Temp.AlphaLevel = 50; Temp.BorderAlphaLevel = 200; Temp.BorderWidth = 1; //... Needed even if u implement FillAlpha, in case user fails we fill it Temp.OverlayColor = RGB(75, 128, 255); Temp.BorderColor = RGB(0, 0, 200); Temp.Parent = hWnd; Temp.Destination = (ISelectionOverlay*)OurInstance; switch (message) { case WM_LBUTTONDOWN: Temp.ButtonLead = LBUTTON; break; case WM_MBUTTONDOWN: Temp.ButtonLead = MBUTTON; break; case WM_RBUTTONDOWN: Temp.ButtonLead = RBUTTON; break; case WM_XBUTTONDOWN: switch ((wParam & 0x0000FFFF)) { case MK_XBUTTON1: Temp.ButtonLead = XBUTTON1; break; case MK_XBUTTON2: Temp.ButtonLead = XBUTTON2; break; } break; } SelectionOverlay(Temp);
在此之后,覆盖层将询问调用者是否要自行绘制位图(或在绘制失败时询问调用者是否要绘制 dc),然后覆盖层将通过 Move 数据(在覆盖层需要调整大小时创建)通知调用者,最后覆盖层将调用调用者(鼠标按钮抬起并且最终矩形已知时创建的)End 数据。
我将在本文档中跳过 SelectionOverlay.h。如果您想了解六种不同结构的外观,可以从 h 文件中阅读。结构之间的差异是为了提供我所能提供的最广泛的选择。
我将解释结构中存在的每个参数,以解释它们的用途
Parent
- 覆盖窗口的父窗口(请注意,创建时是我们的父窗口,但覆盖层是 WS_CHILD),必需。OverlayColor
- 覆盖层的颜色(RGB),必需。BorderColor
- 覆盖层边框的颜色(RGB),必需。BorderWidth
- 边框的宽度(以像素为单位),必需。AlphaLevel
- 用于混合覆盖层的 Alpha 值,必需。BorderAlphaLevel
- 用于混合覆盖层边框的 Alpha 值,必需。Tag
- 可以传递给我们最基本结构的值,在通知时会返回给监听窗口。ButtonLead
- 指示监控哪个按钮:LBUTTON
、MBUTTON
、RBUTTON
、XBUTTON1
或XBUTTON2
;如果未提供,则默认为LBUTTON
。Listener
- 要监听WM_ONMOVE
和WM_ONEND
通知窗口;如果未提供,则使用父窗口。ClientRect
- 提供覆盖层必须保持在其内的屏幕矩形(用于 3 个 RECT 结构),必需。Destination
- 用于回拨的接口(在CLASS
结构中使用),必需。Dummy
- 字面意思,未使用,用于与其基结构SELECTIONOVERLAYDATA
对齐(在 CB 结构中使用)。Move
- 回调OnMove
(在 CB 结构中使用)。EndCall
- 回调OnEnd
(在 CB 结构中使用),必需。FillAlpha
- 回调OnFillAlpha
(在 CB 结构中使用)。FillRgb
- 回调OnFillRgb
(在 CB 结构中使用)。
总结
RECT
结构及其相应的调用,用于您希望使用不同于父窗口客户区矩形的屏幕坐标的情况。CB
结构及其相应的调用提供了在标准调用方法上回拨的可能性。CLASS
结构及其相应的调用提供了在类接口上回拨的可能性(其中 OnMove, OnFillAlpha 和 OnFillRgb
有实现,而 OnEnd
没有(是纯虚函数))
规则:
实际上不多,但 API 要求在调用时 supplied 鼠标按钮处于按下状态。并且每个必需值都需要 supplied。关注点
包装器
我 supplied 了两个包装器,一个用于 Microsoft Visual Basic 6.0,另一个用于 Microsoft C# (v4.0)。它们都支持普通和 ClientRect 以及覆盖填充函数。为什么选择 VB?原因相当方便,VB 往往会在非由其自身线程生成的事件上崩溃。这有助于使调用接近线程安全。如果 VB 能够继续存在,我可能会提供比仅 C/C++ 更广泛的用途。因此,只要调用 API 的触发器与我们回拨的窗口来自同一线程,我们就会回到正确的线程。这意味着我们不需要在 C# 中调用 Invoke,或在其他语言中使用相应的技术。当然,这仅在触发器及其结果仅在同一线程中的窗口上使用时才有效。但请注意,位图/dc 的填充来自其自身的线程,尽管 VB6 如 VB 测试示例所示,可以以这种方式处理(感谢 cSuperclass)。
对于 VB6 中的窗口过程挂钩,我使用了:
大小:
如今,大多数事情不再是关于大小,尽管我仍然倾向于尽可能小。为了减小尺寸,我使用了一个来自旧 MSDN 的库(我的版本来自 MSDN 2001 年 10 月(Microsoft Visual Studio 6.0 的最后一份完整信息 MSDN))称为 LIBCTINY.LIB,其技术在其文档中有很好的解释
MSDN 2001 年 10 月,深入探讨:使用 LIBCTINY.LIB 减小 EXE 和 DLL 的大小。此外,我还使用了一个出色的压缩器工具 UPX 进一步压缩了 DLL 的发布版本。
32 位
由于我没有 64 位机器,也没有访问权限,所以我只设计了这个 API 来处理 32 位。LIBCTINY 本身,我预计无法处理 64 位,但像SetWindowLong
这样的调用也应该在 64 位机器上被替换使用。如果有人愿意将此 API 转换为 64 位,我很乐意将其添加到此项目中。延迟加载
我想缩小此 API 的依赖性,以便在 32 位 Windows 中获得最广泛的支持。所有对内核的调用都已链接,所有对 User32 和 GDI32 的调用都已延迟加载(使用loadlibrary
和 getprocadress
)。因此,DLL 依赖于四个 DLL:Kernel、NTDLL、User32 和 GDI32(这也是使用 LIBCTINY 的结果之一)。工作原理:
线程,保持它们存活和跨越桥梁
由于我想让调用异步(从而在调用我们后不阻塞调用者),我需要第二个线程。一个线程,我可以在其中接收低级鼠标钩子的数据。低级鼠标钩子的问题在于,您只有一定的时间来处理,如果花费太长时间,您将被踢出钩子链。为了确保覆盖层保持活动,我需要将所有绘图移到另一个线程。
所以,简而言之,我们有三个线程:调用者、鼠标钩子、我们的覆盖层。调用者自行保持活动,所以我们不需要保持它存活,但我们的两个线程确实需要。我使用了一种我很久没用过的技术,来自编写纯 C 窗口的初期。
为了让线程保持活动,我使用了 GetMessage
。正如大多数人所见,我只检查其结果是否为 0,而不翻译或分派。我不使用它的原因是简单的,我不想处理错误,只想继续并祈祷
这是我的想法。我不翻译或分派,因为我的输入是鼠标钩子和我自己生成的消息。
现在我仍然需要弥合线程之间的差距。这个解决方案也回到了过去,SendNotifyMessage
,它在调用结果返回时返回,如果调用在您的线程中,但如果它向另一个线程中的窗口过程添加消息,则在不等待结果的情况下返回。为了能够接收,我需要通知窗口。
我使用了父窗口上的 HWND_MESSAGE
来获取消息窗口。
CreateWindowEx(WS_EX_NOPARENTNOTIFY, "STATIC", NULL, 0, -1, -1, 0, 0, HWND_MESSAGE, NULL, 0, 0);它们与普通窗口相比重量级。反正我不需要显示它们。
颜色
我想让用户能够为覆盖层的那些部分 supplied RGB 和 alpha 值,为了实现这一点,我需要将 RGB 转换为 alpha 颜色。在阅读了 Vorlath - Project V: Advanced Alpha Blending之后,我知道 alpha 颜色实际上是什么,以及我需要做什么来创建一个源 alpha。我采用了 100% 验证的、不使用除法的方法,LIBCTINY 不提供它,而通过这个解决方案我就可以继续使用 LIBCTINY。
覆盖层
覆盖层是一个普通的静态窗口,具有以下特性:WS_EX_NOPARENTNOTIFY
、WS_EX_LAYERED
、WS_EX_TRANSPARENT
、WS_POPUP
和 WS_VISIBLE
。
WS_EX_NOPARENTNOTIFY
:我们不希望父窗口收到我们窗口发送的通知。WS_EX_LAYERED
:我们需要这个来实现 alpha 混合。WS_EX_TRANSPARENT
:我们不想遮挡我们之上的窗口。有了这个标志,我们就能确保在我们收到WM_PAINT
时,我们之下的所有内容都已完成绘图。WS_POPUP
:我们的窗口充当弹出窗口。WS_VISIBLE
:我们希望被看到。
Alpha 混合:
我们创建一个 32 位位图,并用 alpha 混合的颜色填充它。如果无法创建 32 位位图,我们将使用 2 个 fillrect 命令填充屏幕的 dc。之后,将使用正确的标志调用 Updatelayeredwindow
。完成后,所有创建的对象将被再次销毁。
缓存
为了确保我能尽快想到,我决定尽可能多地缓存,并对正确对齐的数据使用类型转换,但实际上请求的是另一个结构;
(POINT*)&crPos, (SIZE*)&Info.bmiHeader.biWidth
都是这样做的例子。
简而言之:
我们创建必要的颜色和画笔。我们创建一个线程来监听低级鼠标钩子。我们创建一个线程来更新一个分层窗口,该窗口还会通过 Move 和 End 信息通知调用者。
为了保持我们自己的线程存活,我们使用一个窗口和 GetMessage
。
下面您将看到 C# 和 VB 的源代码示例如何使用 DLL,它们都重写了 FillAlpha。这样做是为了让每个人都能阅读如何在每种语言中处理指针,尽管 DLL 的绘图功能仍然存在,所以您不需要重写填充,这是可选的。
C# 示例:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using SelectionOverlayWrap; namespace SelectionOverlayTest { /// <summary> /// We want to get OnMove and OnEnd notifications, so implement both /// </summary> public partial class Form1: Form, ISelectionOverlayOnEnd, ISelectionOverlayOnMove, ISelectionOverlayOnFillAlpha, ISelectionOverlayOnFillRGB { [StructLayout(LayoutKind.Sequential)] private struct BITMAP { internal int bmType; internal int bmWidth; internal int bmHeight; internal int bmWidthBytes; internal Int16 bmPlanes; internal Int16 bmBitsPixel; internal IntPtr bmBits; } [DllImport("gdi32.dll", EntryPoint = "GetObjectW")] private static extern int GetObject(IntPtr hObject, int nCount, ref BITMAP lpObject); private static int BorderWidth = 2; private static int CheckWidth = 1 + BorderWidth; private static int ColorBorder = SelectionOverlay.CreateAlphaColor(SelectionOverlay.RGB(0, 0, 200), 200); private static int ColorBackground = SelectionOverlay.CreateAlphaColor(SelectionOverlay.RGB(75, 128, 255), 50); private static int ColorBackground2 = SelectionOverlay.CreateAlphaColor(SelectionOverlay.RGB(100, 128, 255), 75); private static int ColorBackground3 = SelectionOverlay.CreateAlphaColor(SelectionOverlay.RGB(0, 64, 128), 63); /// <summary> /// Will be called on OnMove notification /// </summary> /// <param name="Tag">ignored</param> /// <param name="Rectangle">The square of the overlay</param> public void OnMove(object Tag, Rectangle Rectangle) { button1.Text = Rectangle.Left.ToString() + ", " + Rectangle.Top.ToString() + ", " + Rectangle.Right.ToString() + ", " + Rectangle.Bottom.ToString(); } /// <summary> /// Will be called on OnEnd notification /// </summary> /// <param name="Tag">ignored</param> /// <param name="Rectangle">The square of the overlay</param> public void OnEnd(object Tag, Rectangle Rectangle) { button2.Text = Rectangle.Left.ToString() + ", " + Rectangle.Top.ToString() + ", " + Rectangle.Right.ToString() + ", " + Rectangle.Bottom.ToString(); } public bool OnFillRGB(object Tag, IntPtr hDC, int Width, int Height) { return false; } public bool OnFillAlpha(object Tag, IntPtr BitmapDIBHandle) { bool Result = false; if (BitmapDIBHandle != IntPtr.Zero) { BITMAP Retriever = new BITMAP(); GetObject(BitmapDIBHandle, Marshal.SizeOf(Retriever), ref Retriever); int lSize = ((Retriever.bmWidthBytes * Retriever.bmHeight)/4); if ((lSize >= 4) && (Retriever.bmBits != IntPtr.Zero)) { int[] lPixels = new int[lSize]; try { Marshal.Copy(Retriever.bmBits, lPixels, 0, lSize); Result = true; } catch { } if (Result) { long BorderRight = Retriever.bmWidth - CheckWidth; long BorderBottom = Retriever.bmHeight - CheckWidth; for (long Y = 0; Y < Retriever.bmHeight; Y++) { for (long X = 0; X < Retriever.bmWidth; X++) { long Index = ((Y * Retriever.bmWidth) + X); if ((X < BorderWidth) || (X > BorderRight) || (Y < BorderWidth) || (Y > BorderBottom)) { lPixels[Index] = ColorBorder; } else if (((Y % 8) == 1) || ((X % 8) == 1)) { lPixels[Index] = ColorBackground3; } else if ((X & 2 & Y) != 2) { lPixels[Index] = ColorBackground; } else { lPixels[Index] = ColorBackground2; } } } Result = false; try { Marshal.Copy(lPixels, 0, Retriever.bmBits, lSize); Result = true; } catch { } } } } return Result; } /// <summary> /// Starts us /// </summary> public Form1() { InitializeComponent(); } /// <summary> /// On mousedown checks which button was fired and if it's an supported one /// it opens a selection overlay for that button /// </summary> /// <param name="sender">Whisperer</param> /// <param name="e">Mouse data</param> private void Form1_MouseDown(object sender, MouseEventArgs e) { bool NoGo = false; VKKeys ToUse = VKKeys.LBUTTON; if ((e.Button & System.Windows.Forms.MouseButtons.Left) == System.Windows.Forms.MouseButtons.Left) { ToUse = VKKeys.LBUTTON; } else if ((e.Button & System.Windows.Forms.MouseButtons.Right) == System.Windows.Forms.MouseButtons.Right) { ToUse = VKKeys.RBUTTON; } else if ((e.Button & System.Windows.Forms.MouseButtons.Middle) == System.Windows.Forms.MouseButtons.Middle) { ToUse = VKKeys.MBUTTON; } else if ((e.Button & System.Windows.Forms.MouseButtons.XButton1) == System.Windows.Forms.MouseButtons.XButton1) { ToUse = VKKeys.XBUTTON1; } else if ((e.Button & System.Windows.Forms.MouseButtons.XButton2) == System.Windows.Forms.MouseButtons.XButton2) { ToUse = VKKeys.XBUTTON2; } else { NoGo = true; } if (!NoGo) { SelectionOverlay.Show(Color.FromArgb(50, 75, 128, 255), Color.FromArgb(200, 0, 0, 200), 1, this, this.Handle, ToUse); } } } }
VB 示例:
Option Explicit 'Only need to implement the functions u want to use (IOnEnd is mandatory though) Implements IOnEnd Implements IOnMove Implements IOnFillAlpha Implements IOnFillRGB Dim blaat As Double Dim Client As New VBRect Private Type BITMAP bmType As Long bmWidth As Long bmHeight As Long bmWidthBytes As Long bmPlanes As Integer bmBitsPixel As Integer bmBits As Long End Type Dim BorderWidth As Long Dim CheckWidth As Long Dim ColorBorder As Long Dim ColorBackground As Long Dim ColorBackground2 As Long Dim ColorBackground3 As Long Private Declare Function GetObject Lib "gdi32.dll" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, ByVal lpObject As Long) As Long Private Declare Sub RtlMoveMemory Lib "kernel32.dll" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long) Private Sub Form_Load() BorderWidth = 2 CheckWidth = 1 + BorderWidth ColorBorder = CreateAlphaColor(RGB(0, 0, 200), 200) ColorBackground = CreateAlphaColor(RGB(75, 128, 255), 50) ColorBackground2 = CreateAlphaColor(RGB(100, 128, 255), 75) ColorBackground3 = CreateAlphaColor(RGB(0, 64, 128), 63) End Sub Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) ShowOverlay Me.hWnd, 50, 200, 1, RGB(75, 128, 255), RGB(0, 0, 200), Me ' Client.Left = 25 ' Client.Top = 25 ' Client.Right = 400 ' Client.Bottom = 300 ' ShowOverlay Me.hWnd, 50, 200, 1, RGB(75, 128, 255), RGB(0, 0, 200), Me, , Client End Sub Private Sub IOnEnd_OnEnd(ByVal Tag As Object, ByVal Left As Long, ByVal Top As Long, ByVal Right As Long, ByVal Bottom As Long) Print Left & ", " & Top & ", " & Right & ", " & Bottom End Sub Private Function IOnFillAlpha_OnFillAlpha(ByVal Tag As Object, ByVal hBitmapHandle As Long) As Boolean Dim bResult As Boolean Dim oBitmap As BITMAP Dim lSize As Long bResult = False Call GetObject(hBitmapHandle, Len(oBitmap), VarPtr(oBitmap)) lSize = oBitmap.bmWidth * oBitmap.bmHeight If ((lSize >= 4) And (oBitmap.bmBits <> 0)) Then bResult = True Dim lPixels() As Long Dim BorderRight As Long Dim BorderBottom As Long Dim X As Long Dim Y As Long Dim LoopHeight As Long Dim LoopWidth As Long Dim Index As Long LoopHeight = oBitmap.bmHeight - 1 LoopWidth = oBitmap.bmWidth - 1 BorderRight = oBitmap.bmWidth - CheckWidth BorderBottom = oBitmap.bmHeight - CheckWidth ReDim lPixels(lSize) RtlMoveMemory lPixels(0), ByVal oBitmap.bmBits, lSize * 4 For Y = 0 To LoopHeight For X = 0 To LoopWidth Index = (Y * oBitmap.bmWidth) + X If ((X < BorderWidth) Or (X > BorderRight) Or _ (Y < BorderWidth) Or (Y > BorderBottom)) Then lPixels(Index) = ColorBorder ElseIf (((Y Mod 8) = 1) Or ((X Mod 8) = 1)) Then lPixels(Index) = ColorBackground3 ElseIf ((X And 2 And Y) <> 2) Then lPixels(Index) = ColorBackground Else lPixels(Index) = ColorBackground2 End If Next X, Y RtlMoveMemory ByVal oBitmap.bmBits, lPixels(0), lSize * 4 End If IOnFillAlpha_OnFillAlpha = bResult End Function Public Function IOnFillRGB_OnFillRGB(ByVal Tag As Object, ByVal hDC As Long, ByVal Width As Long, ByVal Height As Long) As Boolean IOnFillRGB_OnFillRGB = False End Function Private Sub IOnMove_OnMove(ByVal Tag As Object, ByVal Left As Long, ByVal Top As Long, ByVal Right As Long, ByVal Bottom As Long) Command1.Caption = Left & ", " & Top & ", " & Right & ", " & Bottom End Sub
历史
版本 9:通过回调扩展了 DLL 以覆盖位图/dc 的填充,更新了 C# 和 VB 包装器,调整了 C++ test.exe 的 test.cpp,调整了此页面。在 DLL 中添加了 CreateAlphaColor。将 VB 包装器重构为只有一个 SelectionOverlay 函数,rect 已成为可选。
版本 7:cpp 中的错误修复。在类方法变体中使用的两个回调中缺少 _stdcall。这个 bug somehow 溜过去了,原因我不明白,使用 _stdcall 原型的 _cdecl 调用在调试中没有崩溃,而在发布版本中当然会崩溃。一个很好的发现和解决。
版本 5:调整了一些语法。以及关于 windows.h 的遗漏提示