Skip to content

Commit

Permalink
Update to non-prerelease version of Microsoft.CsWin32
Browse files Browse the repository at this point in the history
  • Loading branch information
jnm2 committed Oct 8, 2024
1 parent ad309da commit 634acd1
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 80 deletions.
47 changes: 28 additions & 19 deletions src/Techsola.InstantReplay/AnimatedCursorRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
65 changes: 43 additions & 22 deletions src/Techsola.InstantReplay/Composition.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
using Techsola.InstantReplay.Native;
using Windows.Win32;
using Windows.Win32.Graphics.Gdi;

Expand Down Expand Up @@ -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()
Expand All @@ -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();
}
}
}
}

41 changes: 26 additions & 15 deletions src/Techsola.InstantReplay/Frame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand All @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using Windows.Win32;
using Techsola.InstantReplay.Native;
using Windows.Win32.UI.WindowsAndMessaging;

namespace Techsola.InstantReplay
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using Techsola.InstantReplay.Native;
using Windows.Win32;

namespace Techsola.InstantReplay
{
Expand Down
9 changes: 5 additions & 4 deletions src/Techsola.InstantReplay/InstantReplayCamera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.

Expand All @@ -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
Expand Down
22 changes: 22 additions & 0 deletions src/Techsola.InstantReplay/Native/DeleteDCSafeHandle.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
32 changes: 15 additions & 17 deletions src/Techsola.InstantReplay/Native/WindowDeviceContextSafeHandle.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// <seealso href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc"/>
/// </summary>
[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;
}
}
2 changes: 2 additions & 0 deletions src/Techsola.InstantReplay/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ BITMAP
ClientToScreen
CreateCompatibleDC
CreateDIBSection
DeleteDC
DrawIconEx
EnumWindows
GdiFlush
Expand All @@ -13,5 +14,6 @@ GetIconInfo
GetObject
GetWindowThreadProcessId
IsWindowVisible
ReleaseDC
SelectObject
SetLastError
5 changes: 4 additions & 1 deletion src/Techsola.InstantReplay/Techsola.InstantReplay.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
<EmbedAllSources>true</EmbedAllSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>

<!-- Missing reference to recommended package: "System.Memory" - but the code is written to run on .NET 3.5 anyway, which System.Memory doesn't support -->
<NoWarn>$(NoWarn);PInvoke009</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetAnalyzers.DocumentationAnalyzers" Version="1.0.0-beta.59" PrivateAssets="all" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.506-beta" PrivateAssets="all" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106" PrivateAssets="all" />
</ItemGroup>

</Project>

0 comments on commit 634acd1

Please sign in to comment.