diff --git a/src/Techsola.InstantReplay/AnimatedCursorRenderer.cs b/src/Techsola.InstantReplay/AnimatedCursorRenderer.cs
index 239f9a7..23a7ae6 100644
--- a/src/Techsola.InstantReplay/AnimatedCursorRenderer.cs
+++ b/src/Techsola.InstantReplay/AnimatedCursorRenderer.cs
@@ -42,28 +42,37 @@ public void Render(DeleteDCSafeHandle deviceContext, HCURSOR cursorHandle, int c
if (!cursorAnimationStepByHandle.TryGetValue(cursorHandle, out var cursorAnimationStep))
cursorAnimationStep = (Current: 0, Max: uint.MaxValue);
- while (!PInvoke.DrawIconEx(
- deviceContext,
- cursorX - (int)cursorInfo.Hotspot.X,
- cursorY - (int)cursorInfo.Hotspot.Y,
- /* Workaround for https://github.com/microsoft/CsWin32/issues/256
- ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ */
- new UnownedHandle(cursorHandle),
- cxWidth: 0,
- cyWidth: 0,
- cursorAnimationStep.Current,
- hbrFlickerFreeDraw: null,
- DI_FLAGS.DI_NORMAL))
+ var deviceContextNeedsRelease = false;
+ deviceContext.DangerousAddRef(ref deviceContextNeedsRelease);
+ try
{
- var lastError = Marshal.GetLastWin32Error();
-
- if ((ERROR)lastError == ERROR.INVALID_PARAMETER && cursorAnimationStep.Current > 0)
+ while (!PInvoke.DrawIconEx(
+ (HDC)deviceContext.DangerousGetHandle(),
+ cursorX - (int)cursorInfo.Hotspot.X,
+ cursorY - (int)cursorInfo.Hotspot.Y,
+ /* Workaround for https://github.com/microsoft/CsWin32/issues/256
+ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ */
+ new UnownedHandle(cursorHandle),
+ cxWidth: 0,
+ cyWidth: 0,
+ cursorAnimationStep.Current,
+ hbrFlickerFreeDraw: null,
+ DI_FLAGS.DI_NORMAL))
{
- cursorAnimationStep = (Current: 0, Max: cursorAnimationStep.Current - 1);
- continue;
- }
+ var lastError = Marshal.GetLastWin32Error();
+
+ if ((ERROR)lastError == ERROR.INVALID_PARAMETER && cursorAnimationStep.Current > 0)
+ {
+ cursorAnimationStep = (Current: 0, Max: cursorAnimationStep.Current - 1);
+ continue;
+ }
- throw new Win32Exception(lastError);
+ throw new Win32Exception(lastError);
+ }
+ }
+ finally
+ {
+ if (deviceContextNeedsRelease) deviceContext.DangerousRelease();
}
cursorAnimationStep.Current = cursorAnimationStep.Current == cursorAnimationStep.Max ? 0 : cursorAnimationStep.Current + 1;
diff --git a/src/Techsola.InstantReplay/Composition.cs b/src/Techsola.InstantReplay/Composition.cs
index cdde78f..25ff0f3 100644
--- a/src/Techsola.InstantReplay/Composition.cs
+++ b/src/Techsola.InstantReplay/Composition.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
+using Techsola.InstantReplay.Native;
using Windows.Win32;
using Windows.Win32.Graphics.Gdi;
@@ -34,27 +35,37 @@ public Composition(uint width, uint height, ushort bitsPerPixel)
BytesPerPixel = (byte)(bitsPerPixel >> 3);
Stride = (((width * BytesPerPixel) + 3) / 4) * 4;
- DeviceContext = PInvoke.CreateCompatibleDC(null).ThrowWithoutLastErrorAvailableIfInvalid(nameof(PInvoke.CreateCompatibleDC));
- unsafe
+ DeviceContext = new DeleteDCSafeHandle(PInvoke.CreateCompatibleDC(default)).ThrowWithoutLastErrorAvailableIfInvalid(nameof(PInvoke.CreateCompatibleDC));
+ var deviceContextNeedsRelease = false;
+ DeviceContext.DangerousAddRef(ref deviceContextNeedsRelease);
+ try
{
- bitmap = PInvoke.CreateDIBSection(DeviceContext, new()
+ unsafe
{
- bmiHeader =
+ var bitmapInfo = new BITMAPINFO
{
- biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)),
- biWidth = (int)width,
- biHeight = -(int)height,
- biPlanes = 1,
- biBitCount = bitsPerPixel,
- },
- }, DIB_USAGE.DIB_RGB_COLORS, out var pointer, hSection: null, offset: 0).ThrowLastErrorIfInvalid();
+ bmiHeader =
+ {
+ biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)),
+ biWidth = (int)width,
+ biHeight = -(int)height,
+ biPlanes = 1,
+ biBitCount = bitsPerPixel,
+ },
+ };
- PixelDataPointer = (byte*)pointer;
- }
+ bitmap = PInvoke.CreateDIBSection((HDC)DeviceContext.DangerousGetHandle(), &bitmapInfo, DIB_USAGE.DIB_RGB_COLORS, out var pointer, hSection: null, offset: 0).ThrowLastErrorIfInvalid();
+
+ PixelDataPointer = (byte*)pointer;
+ }
- // Workaround for https://github.com/microsoft/CsWin32/issues/199
- if (PInvoke.SelectObject(DeviceContext, (HGDIOBJ)bitmap.DangerousGetHandle()).IsNull)
- throw new Win32Exception("SelectObject failed.");
+ if (PInvoke.SelectObject((HDC)DeviceContext.DangerousGetHandle(), (HGDIOBJ)bitmap.DangerousGetHandle()).IsNull)
+ throw new Win32Exception("SelectObject failed.");
+ }
+ finally
+ {
+ if (deviceContextNeedsRelease) DeviceContext.DangerousRelease();
+ }
}
public void Dispose()
@@ -67,16 +78,26 @@ public void Clear(int x, int y, int width, int height, ref bool needsGdiFlush)
{
if (width <= 0 || height <= 0) return;
- if (!PInvoke.BitBlt(DeviceContext, x, y, width, height, null, 0, 0, ROP_CODE.BLACKNESS))
+ var deviceContextNeedsRelease = false;
+ DeviceContext.DangerousAddRef(ref deviceContextNeedsRelease);
+ try
{
- var lastError = Marshal.GetLastWin32Error();
- if (lastError != 0) throw new Win32Exception(lastError);
- needsGdiFlush = true;
+ if (!PInvoke.BitBlt((HDC)DeviceContext.DangerousGetHandle(), x, y, width, height, hdcSrc: default, 0, 0, ROP_CODE.BLACKNESS))
+ {
+ var lastError = Marshal.GetLastWin32Error();
+ if (lastError != 0) throw new Win32Exception(lastError);
+ needsGdiFlush = true;
+ }
+ else
+ {
+ needsGdiFlush = false;
+ }
}
- else
+ finally
{
- needsGdiFlush = false;
+ if (deviceContextNeedsRelease) DeviceContext.DangerousRelease();
}
}
}
}
+
diff --git a/src/Techsola.InstantReplay/Frame.cs b/src/Techsola.InstantReplay/Frame.cs
index a42d99b..87653e0 100644
--- a/src/Techsola.InstantReplay/Frame.cs
+++ b/src/Techsola.InstantReplay/Frame.cs
@@ -52,29 +52,40 @@ public void Overwrite(
bitmap.Dispose();
}
- unsafe
+ var bitmapDCNeedsRelease = false;
+ bitmapDC.DangerousAddRef(ref bitmapDCNeedsRelease);
+ try
{
- bitmap = PInvoke.CreateDIBSection(bitmapDC, new()
+ unsafe
{
- bmiHeader =
+ var bitmapInfo = new BITMAPINFO
{
- biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)),
- biWidth = bitmapWidth,
- biHeight = -bitmapHeight,
- biPlanes = 1,
- biBitCount = BitsPerPixel,
- },
- }, DIB_USAGE.DIB_RGB_COLORS, ppvBits: out _, hSection: null, offset: 0).ThrowLastErrorIfInvalid();
+ bmiHeader =
+ {
+ biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)),
+ biWidth = bitmapWidth,
+ biHeight = -bitmapHeight,
+ biPlanes = 1,
+ biBitCount = BitsPerPixel,
+ },
+ };
+
+ bitmap = PInvoke.CreateDIBSection((HDC)bitmapDC.DangerousGetHandle(), &bitmapInfo, DIB_USAGE.DIB_RGB_COLORS, ppvBits: out _, hSection: null, offset: 0).ThrowLastErrorIfInvalid();
+ }
+ }
+ finally
+ {
+ if (bitmapDCNeedsRelease) bitmapDC.DangerousRelease();
}
}
// Workaround for https://github.com/microsoft/CsWin32/issues/199
- if (PInvoke.SelectObject(bitmapDC, (HGDIOBJ)bitmap.DangerousGetHandle()).IsNull)
+ if (PInvoke.SelectObject((HDC)bitmapDC.DangerousGetHandle(), (HGDIOBJ)bitmap.DangerousGetHandle()).IsNull)
throw new Win32Exception("SelectObject failed.");
retryBitBlt:
PInvoke.SetLastError(0); // BitBlt doesn't set the last error if it returns false to indicate that the operation has been batched
- if (!PInvoke.BitBlt(bitmapDC, 0, 0, windowMetrics.ClientWidth, windowMetrics.ClientHeight, windowDC, 0, 0, ROP_CODE.SRCCOPY))
+ if (!PInvoke.BitBlt((HDC)bitmapDC.DangerousGetHandle(), 0, 0, windowMetrics.ClientWidth, windowMetrics.ClientHeight, (HDC)windowDC.DangerousGetHandle(), 0, 0, ROP_CODE.SRCCOPY))
{
var lastError = Marshal.GetLastWin32Error();
if ((ERROR)lastError is ERROR.INVALID_WINDOW_HANDLE or ERROR.DC_NOT_FOUND)
@@ -125,7 +136,7 @@ public void Compose(
}
// Workaround for https://github.com/microsoft/CsWin32/issues/199
- if (PInvoke.SelectObject(bitmapDC, (HGDIOBJ)bitmap.DangerousGetHandle()).IsNull)
+ if (PInvoke.SelectObject((HDC)bitmapDC.DangerousGetHandle(), (HGDIOBJ)bitmap.DangerousGetHandle()).IsNull)
throw new Win32Exception("SelectObject failed.");
changedArea = new(
@@ -136,12 +147,12 @@ public void Compose(
PInvoke.SetLastError(0); // BitBlt doesn't set the last error if it returns false to indicate that the operation has been batched
if (!PInvoke.BitBlt(
- compositionDC,
+ (HDC)compositionDC.DangerousGetHandle(),
changedArea.Left,
changedArea.Top,
changedArea.Width,
changedArea.Height,
- bitmapDC,
+ (HDC)bitmapDC.DangerousGetHandle(),
0,
0,
ROP_CODE.SRCCOPY))
diff --git a/src/Techsola.InstantReplay/InstantReplayCamera.CompositionRenderer.cs b/src/Techsola.InstantReplay/InstantReplayCamera.CompositionRenderer.cs
index 4c55c9e..105916e 100644
--- a/src/Techsola.InstantReplay/InstantReplayCamera.CompositionRenderer.cs
+++ b/src/Techsola.InstantReplay/InstantReplayCamera.CompositionRenderer.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-using Windows.Win32;
+using Techsola.InstantReplay.Native;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Techsola.InstantReplay
diff --git a/src/Techsola.InstantReplay/InstantReplayCamera.WindowInfo.cs b/src/Techsola.InstantReplay/InstantReplayCamera.WindowInfo.cs
index 51410df..ae89882 100644
--- a/src/Techsola.InstantReplay/InstantReplayCamera.WindowInfo.cs
+++ b/src/Techsola.InstantReplay/InstantReplayCamera.WindowInfo.cs
@@ -1,6 +1,5 @@
using System;
using Techsola.InstantReplay.Native;
-using Windows.Win32;
namespace Techsola.InstantReplay
{
diff --git a/src/Techsola.InstantReplay/InstantReplayCamera.cs b/src/Techsola.InstantReplay/InstantReplayCamera.cs
index b30ff8a..6fe650e 100644
--- a/src/Techsola.InstantReplay/InstantReplayCamera.cs
+++ b/src/Techsola.InstantReplay/InstantReplayCamera.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
+using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
@@ -115,14 +116,14 @@ private static void AddFrames(object? state)
var currentWindows = (windowEnumerator ??= new()).GetCurrentWindowHandlesInZOrder();
- bitmapDC ??= PInvoke.CreateCompatibleDC(null).ThrowWithoutLastErrorAvailableIfInvalid(nameof(PInvoke.CreateCompatibleDC));
+ bitmapDC ??= new DeleteDCSafeHandle(PInvoke.CreateCompatibleDC(default)).ThrowWithoutLastErrorAvailableIfInvalid(nameof(PInvoke.CreateCompatibleDC));
lock (InfoByWindowHandle)
{
Frames.Add((
Timestamp: now,
Cursor: (cursorInfo.flags & (CURSORINFO_FLAGS.CURSOR_SHOWING | CURSORINFO_FLAGS.CURSOR_SUPPRESSED)) == CURSORINFO_FLAGS.CURSOR_SHOWING
- ? (cursorInfo.ptScreenPos.x, cursorInfo.ptScreenPos.y, cursorInfo.hCursor)
+ ? (cursorInfo.ptScreenPos.X, cursorInfo.ptScreenPos.Y, cursorInfo.hCursor)
: null));
var zOrder = 0u;
@@ -215,7 +216,7 @@ private static void AddFrames(object? state)
private static WindowMetrics? GetWindowMetricsIfExists(HWND window)
{
- var clientTopLeft = default(POINT);
+ var clientTopLeft = default(Point);
if (!PInvoke.ClientToScreen(window, ref clientTopLeft))
return null; // This is what happens when the window handle becomes invalid.
@@ -226,7 +227,7 @@ private static void AddFrames(object? state)
throw new Win32Exception(lastError);
}
- return new(clientTopLeft.x, clientTopLeft.y, clientRect.right, clientRect.bottom);
+ return new(clientTopLeft.X, clientTopLeft.Y, clientRect.right, clientRect.bottom);
}
#if !NET35
diff --git a/src/Techsola.InstantReplay/Native/DeleteDCSafeHandle.cs b/src/Techsola.InstantReplay/Native/DeleteDCSafeHandle.cs
new file mode 100644
index 0000000..22a0de1
--- /dev/null
+++ b/src/Techsola.InstantReplay/Native/DeleteDCSafeHandle.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Graphics.Gdi;
+
+namespace Techsola.InstantReplay.Native;
+
+// Workaround for https://github.com/microsoft/CsWin32/issues/209
+internal sealed class DeleteDCSafeHandle : SafeHandle
+{
+ public DeleteDCSafeHandle(IntPtr handle) : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true)
+ {
+ SetHandle(handle);
+ }
+
+ public override bool IsInvalid => handle == IntPtr.Zero;
+
+ protected override bool ReleaseHandle()
+ {
+ return (bool)PInvoke.DeleteDC((HDC)handle);
+ }
+}
diff --git a/src/Techsola.InstantReplay/Native/WindowDeviceContextSafeHandle.cs b/src/Techsola.InstantReplay/Native/WindowDeviceContextSafeHandle.cs
index 7c97c41..94af73d 100644
--- a/src/Techsola.InstantReplay/Native/WindowDeviceContextSafeHandle.cs
+++ b/src/Techsola.InstantReplay/Native/WindowDeviceContextSafeHandle.cs
@@ -1,29 +1,27 @@
using System;
using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
using Windows.Win32;
using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Gdi;
-namespace Techsola.InstantReplay.Native
+namespace Techsola.InstantReplay.Native;
+
+// Workaround for https://github.com/microsoft/CsWin32/issues/209
+internal sealed class WindowDeviceContextSafeHandle : SafeHandle
{
- // Workaround for https://github.com/microsoft/CsWin32/issues/209
- internal sealed class WindowDeviceContextSafeHandle : DeleteDCSafeHandle
+ public WindowDeviceContextSafeHandle(HWND hWnd, IntPtr handle)
+ : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true)
{
- public WindowDeviceContextSafeHandle(HWND hWnd, IntPtr handle)
- : base(handle)
- {
- HWnd = hWnd;
- }
+ HWnd = hWnd;
+ SetHandle(handle);
+ }
- public HWND HWnd { get; }
+ public HWND HWnd { get; }
- protected override bool ReleaseHandle() => ReleaseDC(HWnd, handle);
+ public override bool IsInvalid => handle == IntPtr.Zero;
- ///
- ///
- ///
- [SupportedOSPlatform("windows")]
- [DllImport("user32.dll")]
- private static extern bool ReleaseDC(HWND hWnd, IntPtr hDC);
+ protected override bool ReleaseHandle()
+ {
+ return PInvoke.ReleaseDC(HWnd, (HDC)handle) == 1;
}
}
diff --git a/src/Techsola.InstantReplay/NativeMethods.txt b/src/Techsola.InstantReplay/NativeMethods.txt
index b9ac0fb..83f261b 100644
--- a/src/Techsola.InstantReplay/NativeMethods.txt
+++ b/src/Techsola.InstantReplay/NativeMethods.txt
@@ -3,6 +3,7 @@ BITMAP
ClientToScreen
CreateCompatibleDC
CreateDIBSection
+DeleteDC
DrawIconEx
EnumWindows
GdiFlush
@@ -13,5 +14,6 @@ GetIconInfo
GetObject
GetWindowThreadProcessId
IsWindowVisible
+ReleaseDC
SelectObject
SetLastError
diff --git a/src/Techsola.InstantReplay/Techsola.InstantReplay.csproj b/src/Techsola.InstantReplay/Techsola.InstantReplay.csproj
index 31094dc..ef4062b 100644
--- a/src/Techsola.InstantReplay/Techsola.InstantReplay.csproj
+++ b/src/Techsola.InstantReplay/Techsola.InstantReplay.csproj
@@ -19,11 +19,14 @@
true
true
snupkg
+
+
+ $(NoWarn);PInvoke009
-
+