Skip to content

Commit 2b45ed1

Browse files
authored
Allow attaching an IWICBitmapSource instance directly. (#1266)
This PR adjusts the experimental WIC bitmap attachment support I recently added so that a test author can attach an instance of `IWICBitmapSource` (which is the parent type of the already-supported `IWICBitmap`.) Protocols and conformances are adjusted to match. This PR then explicitly adds conformances to every COM class in WIC that subclasses `IWICBitmapSource` since we can't see COM class inheritance in Swift (yet?) I could have left the protocols as-is, but then you'd have to convert an `IWICBitmapSource` to an `IWICBitmap` (by allocating a new COM object) then cast it back to an `IWICBitmapSource` (by calling `QueryInterface()` and juggling the refcount) in order to actually attach it. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 9faef9b commit 2b45ed1

9 files changed

+194
-110
lines changed

Sources/Overlays/_Testing_WinSDK/Attachments/AttachableAsIWICBitmap.swift renamed to Sources/Overlays/_Testing_WinSDK/Attachments/AttachableAsIWICBitmapSource.swift

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,33 @@ public import WinSDK
2626
///
2727
/// - [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps)
2828
/// - [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons)
29-
/// - [`IWICBitmap`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmap)
29+
/// - [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource)
30+
/// (including its subclasses declared by Windows Imaging Component)
3031
///
3132
/// You do not generally need to add your own conformances to this protocol. If
3233
/// you have an image in another format that needs to be attached to a test,
3334
/// first convert it to an instance of one of the types above.
3435
@_spi(Experimental)
35-
public protocol _AttachableByAddressAsIWICBitmap {
36-
/// Create a WIC bitmap representing an instance of this type at the given
37-
/// address.
36+
public protocol _AttachableByAddressAsIWICBitmapSource {
37+
/// Create a WIC bitmap source representing an instance of this type at the
38+
/// given address.
3839
///
3940
/// - Parameters:
4041
/// - imageAddress: The address of the instance of this type.
4142
/// - factory: A WIC imaging factory that can be used to create additional
4243
/// WIC objects.
4344
///
44-
/// - Returns: A pointer to a new WIC bitmap representing this image. The
45-
/// caller is responsible for releasing this image when done with it.
45+
/// - Returns: A pointer to a new WIC bitmap source representing this image.
46+
/// The caller is responsible for releasing this image when done with it.
4647
///
4748
/// - Throws: Any error that prevented the creation of the WIC bitmap.
4849
///
4950
/// This function is not part of the public interface of the testing library.
5051
/// It may be removed in a future update.
51-
static func _copyAttachableIWICBitmap(
52+
static func _copyAttachableIWICBitmapSource(
5253
from imageAddress: UnsafeMutablePointer<Self>,
5354
using factory: UnsafeMutablePointer<IWICImagingFactory>
54-
) throws -> UnsafeMutablePointer<IWICBitmap>
55+
) throws -> UnsafeMutablePointer<IWICBitmapSource>
5556

5657
/// Make a copy of the instance of this type at the given address.
5758
///
@@ -84,7 +85,7 @@ public protocol _AttachableByAddressAsIWICBitmap {
8485
/// does not call this function.
8586
///
8687
/// This function is not responsible for releasing the image returned from
87-
/// `_copyAttachableIWICBitmap(from:using:)`.
88+
/// `_copyAttachableIWICBitmapSource(from:using:)`.
8889
///
8990
/// This function is not part of the public interface of the testing library.
9091
/// It may be removed in a future update.
@@ -104,13 +105,14 @@ public protocol _AttachableByAddressAsIWICBitmap {
104105
///
105106
/// - [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps)
106107
/// - [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons)
107-
/// - [`IWICBitmap`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmap)
108+
/// - [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource)
109+
/// (including its subclasses declared by Windows Imaging Component)
108110
///
109111
/// You do not generally need to add your own conformances to this protocol. If
110112
/// you have an image in another format that needs to be attached to a test,
111113
/// first convert it to an instance of one of the types above.
112114
@_spi(Experimental)
113-
public protocol AttachableAsIWICBitmap {
115+
public protocol AttachableAsIWICBitmapSource {
114116
/// Create a WIC bitmap representing an instance of this type.
115117
///
116118
/// - Parameters:
@@ -124,9 +126,9 @@ public protocol AttachableAsIWICBitmap {
124126
///
125127
/// This function is not part of the public interface of the testing library.
126128
/// It may be removed in a future update.
127-
borrowing func _copyAttachableIWICBitmap(
129+
borrowing func _copyAttachableIWICBitmapSource(
128130
using factory: UnsafeMutablePointer<IWICImagingFactory>
129-
) throws -> UnsafeMutablePointer<IWICBitmap>
131+
) throws -> UnsafeMutablePointer<IWICBitmapSource>
130132

131133
/// Make a copy of this instance.
132134
///
@@ -152,7 +154,7 @@ public protocol AttachableAsIWICBitmap {
152154
/// automatically invokes this function as needed.
153155
///
154156
/// This function is not responsible for releasing the image returned from
155-
/// `_copyAttachableIWICBitmap(using:)`.
157+
/// `_copyAttachableIWICBitmapSource(using:)`.
156158
///
157159
/// The default implementation of this function when `Self` conforms to
158160
/// `Sendable` does nothing.
@@ -162,42 +164,7 @@ public protocol AttachableAsIWICBitmap {
162164
func _deinitializeAttachableValue()
163165
}
164166

165-
extension AttachableAsIWICBitmap {
166-
/// Create a WIC bitmap representing an instance of this type and return it as
167-
/// an instance of `IWICBitmapSource`.
168-
///
169-
/// - Parameters:
170-
/// - factory: A WIC imaging factory that can be used to create additional
171-
/// WIC objects.
172-
///
173-
/// - Returns: A pointer to a new WIC bitmap representing this image. The
174-
/// caller is responsible for releasing this image when done with it.
175-
///
176-
/// - Throws: Any error that prevented the creation of the WIC bitmap.
177-
///
178-
/// This function is a convenience over `_copyAttachableIWICBitmap(using:)`
179-
/// that casts the result of that function to `IWICBitmapSource` (as needed
180-
/// by WIC when it encodes the image.)
181-
borrowing func copyAttachableIWICBitmapSource(
182-
using factory: UnsafeMutablePointer<IWICImagingFactory>
183-
) throws -> UnsafeMutablePointer<IWICBitmapSource> {
184-
let bitmap = try _copyAttachableIWICBitmap(using: factory)
185-
defer {
186-
_ = bitmap.pointee.lpVtbl.pointee.Release(bitmap)
187-
}
188-
189-
return try withUnsafePointer(to: IID_IWICBitmapSource) { IID_IWICBitmapSource in
190-
var bitmapSource: UnsafeMutableRawPointer?
191-
let rQuery = bitmap.pointee.lpVtbl.pointee.QueryInterface(bitmap, IID_IWICBitmapSource, &bitmapSource)
192-
guard rQuery == S_OK, let bitmapSource else {
193-
throw ImageAttachmentError.queryInterfaceFailed(IWICBitmapSource.self, rQuery)
194-
}
195-
return bitmapSource.assumingMemoryBound(to: IWICBitmapSource.self)
196-
}
197-
}
198-
}
199-
200-
extension AttachableAsIWICBitmap where Self: Sendable {
167+
extension AttachableAsIWICBitmapSource where Self: Sendable {
201168
public func _copyAttachableValue() -> Self {
202169
self
203170
}

Sources/Overlays/_Testing_WinSDK/Attachments/Attachment+AttachableAsIWICBitmap.swift renamed to Sources/Overlays/_Testing_WinSDK/Attachments/Attachment+AttachableAsIWICBitmapSource.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ extension Attachment where AttachableValue: ~Copyable {
2727
/// attachment.
2828
///
2929
/// The following system-provided image types conform to the
30-
/// ``AttachableAsIWICBitmap`` protocol and can be attached to a test:
30+
/// ``AttachableAsIWICBitmapSource`` protocol and can be attached to a test:
3131
///
3232
/// - [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps)
3333
/// - [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons)
34-
/// - [`IWICBitmap`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmap)
34+
/// - [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource)
35+
/// (including its subclasses declared by Windows Imaging Component)
3536
///
3637
/// The testing library uses the image format specified by `imageFormat`. Pass
3738
/// `nil` to let the testing library decide which image format to use. If you
@@ -66,11 +67,12 @@ extension Attachment where AttachableValue: ~Copyable {
6667
/// and immediately attaches it to the current test.
6768
///
6869
/// The following system-provided image types conform to the
69-
/// ``AttachableAsIWICBitmap`` protocol and can be attached to a test:
70+
/// ``AttachableAsIWICBitmapSource`` protocol and can be attached to a test:
7071
///
7172
/// - [`HBITMAP`](https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps)
7273
/// - [`HICON`](https://learn.microsoft.com/en-us/windows/win32/menurc/icons)
73-
/// - [`IWICBitmap`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmap)
74+
/// - [`IWICBitmapSource`](https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicbitmapsource)
75+
/// (including its subclasses declared by Windows Imaging Component)
7476
///
7577
/// The testing library uses the image format specified by `imageFormat`. Pass
7678
/// `nil` to let the testing library decide which image format to use. If you
@@ -92,7 +94,7 @@ extension Attachment where AttachableValue: ~Copyable {
9294
}
9395

9496
@_spi(Experimental)
95-
extension Attachment where AttachableValue: AttachableWrapper, AttachableValue.Wrapped: AttachableAsIWICBitmap {
97+
extension Attachment where AttachableValue: AttachableWrapper, AttachableValue.Wrapped: AttachableAsIWICBitmapSource {
9698
/// The image format to use when encoding the represented image.
9799
@_disfavoredOverload
98100
public var imageFormat: AttachableImageFormat? {

Sources/Overlays/_Testing_WinSDK/Attachments/HBITMAP+AttachableAsIWICBitmap.swift renamed to Sources/Overlays/_Testing_WinSDK/Attachments/HBITMAP+AttachableAsIWICBitmapSource.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ import Testing
1414
public import WinSDK
1515

1616
@_spi(Experimental)
17-
extension HBITMAP__: _AttachableByAddressAsIWICBitmap {
18-
public static func _copyAttachableIWICBitmap(
17+
extension HBITMAP__: _AttachableByAddressAsIWICBitmapSource {
18+
public static func _copyAttachableIWICBitmapSource(
1919
from imageAddress: UnsafeMutablePointer<Self>,
2020
using factory: UnsafeMutablePointer<IWICImagingFactory>
21-
) throws -> UnsafeMutablePointer<IWICBitmap> {
22-
var bitmap: UnsafeMutablePointer<IWICBitmap>!
21+
) throws -> UnsafeMutablePointer<IWICBitmapSource> {
22+
var bitmap: UnsafeMutablePointer<IWICBitmap>?
2323
let rCreate = factory.pointee.lpVtbl.pointee.CreateBitmapFromHBITMAP(factory, imageAddress, nil, WICBitmapUsePremultipliedAlpha, &bitmap)
2424
guard rCreate == S_OK, let bitmap else {
2525
throw ImageAttachmentError.comObjectCreationFailed(IWICBitmap.self, rCreate)
2626
}
27-
return bitmap
27+
return try bitmap.cast(to: IWICBitmapSource.self)
2828
}
2929

3030
public static func _copyAttachableValue(at imageAddress: UnsafeMutablePointer<Self>) -> UnsafeMutablePointer<Self> {

Sources/Overlays/_Testing_WinSDK/Attachments/HICON+AttachableAsIWICBitmap.swift renamed to Sources/Overlays/_Testing_WinSDK/Attachments/HICON+AttachableAsIWICBitmapSource.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ import Testing
1414
public import WinSDK
1515

1616
@_spi(Experimental)
17-
extension HICON__: _AttachableByAddressAsIWICBitmap {
18-
public static func _copyAttachableIWICBitmap(
17+
extension HICON__: _AttachableByAddressAsIWICBitmapSource {
18+
public static func _copyAttachableIWICBitmapSource(
1919
from imageAddress: UnsafeMutablePointer<Self>,
2020
using factory: UnsafeMutablePointer<IWICImagingFactory>
21-
) throws -> UnsafeMutablePointer<IWICBitmap> {
22-
var bitmap: UnsafeMutablePointer<IWICBitmap>!
21+
) throws -> UnsafeMutablePointer<IWICBitmapSource> {
22+
var bitmap: UnsafeMutablePointer<IWICBitmap>?
2323
let rCreate = factory.pointee.lpVtbl.pointee.CreateBitmapFromHICON(factory, imageAddress, &bitmap)
2424
guard rCreate == S_OK, let bitmap else {
2525
throw ImageAttachmentError.comObjectCreationFailed(IWICBitmap.self, rCreate)
2626
}
27-
return bitmap
27+
return try bitmap.cast(to: IWICBitmapSource.self)
2828
}
2929

3030
public static func _copyAttachableValue(at imageAddress: UnsafeMutablePointer<Self>) -> UnsafeMutablePointer<Self> {

Sources/Overlays/_Testing_WinSDK/Attachments/IWICBitmap+AttachableAsIWICBitmap.swift

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
#if os(Windows)
12+
import Testing
13+
14+
public import WinSDK
15+
16+
/// - Important: The casts in this file to `IUnknown` are safe insofar as we use
17+
/// them to access fixed members of the COM vtable. The casts would become
18+
/// unsafe if we allowed the resulting pointers to escape _and_ if any of the
19+
/// types we use them on have multiple non-virtual inheritance to `IUnknown`.
20+
21+
/// A protocol that identifies a type as a COM subclass of `IWICBitmapSource`.
22+
///
23+
/// Because COM class inheritance is not visible in Swift, we must manually
24+
/// apply conformances to this protocol to each COM type that inherits from
25+
/// `IWICBitmapSource`.
26+
///
27+
/// Because this protocol is not `public`, we must also explicitly restate
28+
/// conformance to the public protocol `_AttachableByAddressAsIWICBitmapSource`
29+
/// even though this protocol refines that one. This protocol refines
30+
/// `_AttachableByAddressAsIWICBitmapSource` because otherwise the compiler will
31+
/// not allow us to declare `public` members in its extension that provides the
32+
/// implementation of `_AttachableByAddressAsIWICBitmapSource` below.
33+
///
34+
/// This protocol is not part of the public interface of the testing library. It
35+
/// allows us to reuse code across all subclasses of `IWICBitmapSource`.
36+
protocol IWICBitmapSourceProtocol: _AttachableByAddressAsIWICBitmapSource {}
37+
38+
@_spi(Experimental)
39+
extension IWICBitmapSource: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
40+
41+
@_spi(Experimental)
42+
extension IWICBitmap: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
43+
44+
@_spi(Experimental)
45+
extension IWICBitmapClipper: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
46+
47+
@_spi(Experimental)
48+
extension IWICBitmapFlipRotator: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
49+
50+
@_spi(Experimental)
51+
extension IWICBitmapFrameDecode: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
52+
53+
@_spi(Experimental)
54+
extension IWICBitmapScaler: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
55+
56+
@_spi(Experimental)
57+
extension IWICColorTransform: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
58+
59+
@_spi(Experimental)
60+
extension IWICFormatConverter: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
61+
62+
@_spi(Experimental)
63+
extension IWICPlanarFormatConverter: _AttachableByAddressAsIWICBitmapSource, IWICBitmapSourceProtocol {}
64+
65+
// MARK: - Upcasting conveniences
66+
67+
extension UnsafeMutablePointer where Pointee: IWICBitmapSourceProtocol {
68+
/// Upcast this WIC bitmap to a WIC bitmap source (its parent type).
69+
///
70+
/// - Returns: `self`, cast to the parent type via `QueryInterface()`. The
71+
/// caller is responsible for releasing the resulting object.
72+
///
73+
/// - Throws: Any error that occurs while calling `QueryInterface()`. In
74+
/// practice, this function is not expected to throw an error as it should
75+
/// always be possible to cast a valid instance of `IWICBitmap` to
76+
/// `IWICBitmapSource`.
77+
///
78+
/// - Important: This function consumes a reference to `self` even if the cast
79+
/// fails.
80+
consuming func cast(to _: IWICBitmapSource.Type) throws -> UnsafeMutablePointer<IWICBitmapSource> {
81+
try self.withMemoryRebound(to: IUnknown.self, capacity: 1) { `self` in
82+
defer {
83+
_ = self.pointee.lpVtbl.pointee.Release(self)
84+
}
85+
86+
return try withUnsafePointer(to: IID_IWICBitmapSource) { IID_IWICBitmapSource in
87+
var bitmapSource: UnsafeMutableRawPointer?
88+
let rQuery = self.pointee.lpVtbl.pointee.QueryInterface(self, IID_IWICBitmapSource, &bitmapSource)
89+
guard rQuery == S_OK, let bitmapSource else {
90+
throw ImageAttachmentError.queryInterfaceFailed(IWICBitmapSource.self, rQuery)
91+
}
92+
return bitmapSource.assumingMemoryBound(to: IWICBitmapSource.self)
93+
}
94+
}
95+
}
96+
}
97+
98+
// MARK: - _AttachableByAddressAsIWICBitmapSource implementation
99+
100+
extension IWICBitmapSourceProtocol {
101+
public static func _copyAttachableIWICBitmapSource(
102+
from imageAddress: UnsafeMutablePointer<Self>,
103+
using factory: UnsafeMutablePointer<IWICImagingFactory>
104+
) throws -> UnsafeMutablePointer<IWICBitmapSource> {
105+
try _copyAttachableValue(at: imageAddress).cast(to: IWICBitmapSource.self)
106+
}
107+
108+
public static func _copyAttachableValue(at imageAddress: UnsafeMutablePointer<Self>) -> UnsafeMutablePointer<Self> {
109+
imageAddress.withMemoryRebound(to: IUnknown.self, capacity: 1) { imageAddress in
110+
_ = imageAddress.pointee.lpVtbl.pointee.AddRef(imageAddress)
111+
}
112+
return imageAddress
113+
}
114+
115+
public static func _deinitializeAttachableValue(at imageAddress: UnsafeMutablePointer<Self>) {
116+
imageAddress.withMemoryRebound(to: IUnknown.self, capacity: 1) { imageAddress in
117+
_ = imageAddress.pointee.lpVtbl.pointee.Release(imageAddress)
118+
}
119+
}
120+
}
121+
122+
extension IWICBitmapSource {
123+
public static func _copyAttachableIWICBitmapSource(
124+
from imageAddress: UnsafeMutablePointer<Self>,
125+
using factory: UnsafeMutablePointer<IWICImagingFactory>
126+
) throws -> UnsafeMutablePointer<IWICBitmapSource> {
127+
_ = imageAddress.pointee.lpVtbl.pointee.AddRef(imageAddress)
128+
return imageAddress
129+
}
130+
}
131+
#endif

Sources/Overlays/_Testing_WinSDK/Attachments/UnsafeMutablePointer+AttachableAsIWICBitmap.swift renamed to Sources/Overlays/_Testing_WinSDK/Attachments/UnsafeMutablePointer+AttachableAsIWICBitmapSource.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import Testing
1414
public import WinSDK
1515

1616
@_spi(Experimental)
17-
extension UnsafeMutablePointer: AttachableAsIWICBitmap where Pointee: _AttachableByAddressAsIWICBitmap {
18-
public func _copyAttachableIWICBitmap(using factory: UnsafeMutablePointer<IWICImagingFactory>) throws -> UnsafeMutablePointer<IWICBitmap> {
19-
try Pointee._copyAttachableIWICBitmap(from: self, using: factory)
17+
extension UnsafeMutablePointer: AttachableAsIWICBitmapSource where Pointee: _AttachableByAddressAsIWICBitmapSource {
18+
public func _copyAttachableIWICBitmapSource(using factory: UnsafeMutablePointer<IWICImagingFactory>) throws -> UnsafeMutablePointer<IWICBitmapSource> {
19+
try Pointee._copyAttachableIWICBitmapSource(from: self, using: factory)
2020
}
2121

2222
public func _copyAttachableValue() -> Self {

0 commit comments

Comments
 (0)