Skip to content

Commit

Permalink
Introduce COMEmbeddingEx to replace COMEmbedderWithDelegatedImplement…
Browse files Browse the repository at this point in the history
…ation (#420)
  • Loading branch information
tristanlabelle authored Dec 7, 2024
1 parent 490dec8 commit 34eb952
Show file tree
Hide file tree
Showing 18 changed files with 318 additions and 166 deletions.
10 changes: 4 additions & 6 deletions Support/Sources/COM/COMDelegatingExport.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import COM_ABI

/// Exposes a secondary COM interface whose implementation is delegated to a primary Swift exported object.
public final class COMDelegatingExport: COMEmbedderWithDelegatedImplementation {
private var comEmbedding: COMEmbedding
public let delegatedImplementation: AnyObject
public final class COMDelegatingExport {
private var comEmbedding: COMEmbeddingEx

public init(virtualTable: UnsafeRawPointer, implementer: IUnknown) {
comEmbedding = .init(virtualTable: virtualTable, embedder: nil)
delegatedImplementation = implementer
comEmbedding.initEmbedder(self)
comEmbedding = .null // Required before referring to self
comEmbedding = .init(virtualTable: virtualTable, embedder: self, externalImplementer: implementer)
}

public convenience init<Binding: COMTwoWayBinding>(binding: Binding.Type, implementer: Binding.SwiftObject) {
Expand Down
110 changes: 110 additions & 0 deletions Support/Sources/COM/COMEmbedding+statics.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import COM_ABI
import COM_PrivateABI

extension COMEmbedding {
fileprivate static func getUnmanagedEmbedderUnsafe<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>) -> Unmanaged<AnyObject> {
this.withMemoryRebound(to: SWRT_COMEmbedding.self, capacity: 1) {
let opaquePointer = UnsafeMutableRawPointer(bitPattern: $0.pointee.swiftEmbedderAndFlags & ~SWRT_COMEmbeddingFlags_Mask)
assert(opaquePointer != nil, "Bad COM object embedding. The embedder pointer is nil.")
return Unmanaged<AnyObject>.fromOpaque(opaquePointer!)
}
}

fileprivate static func getIUnknownUnsafe<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>) -> IUnknown {
// IUnknown can either be implemented by the embedder or by a separately stored implementer.
let opaquePointer = this.withMemoryRebound(to: SWRT_COMEmbedding.self, capacity: 1) {
if ($0.pointee.swiftEmbedderAndFlags & SWRT_COMEmbeddingFlags_ExternalImplementerIsIUnknown) == 0 {
// The embedder implements IUnknown.
return UnsafeMutableRawPointer(bitPattern: $0.pointee.swiftEmbedderAndFlags & ~SWRT_COMEmbeddingFlags_Mask)
} else {
// The separately stored external implementer implements IUnknown.
return $0.withMemoryRebound(to: SWRT_COMEmbeddingEx.self, capacity: 1) {
$0.pointee.swiftImplementer_retained
}
}
}

assert(opaquePointer != nil, "Bad COM object embedding. The IUnknown pointer is nil.")
return Unmanaged<AnyObject>.fromOpaque(opaquePointer!).takeUnretainedValue() as! IUnknown
}

/// Gets the Swift object that provides the implementation for the given COM interface,
/// assuming that it is an embedded COM interface, and otherwise crashes.
public static func getImplementerUnsafe<ABIStruct, Implementer>(
_ this: UnsafeMutablePointer<ABIStruct>, type: Implementer.Type = Implementer.self) -> Implementer {
let opaquePointer = this.withMemoryRebound(to: SWRT_COMEmbedding.self, capacity: 1) {
if ($0.pointee.swiftEmbedderAndFlags & SWRT_COMEmbeddingFlags_ExternalImplementer) == 0 {
// The embedder is the implementer.
return UnsafeMutableRawPointer(bitPattern: $0.pointee.swiftEmbedderAndFlags & ~SWRT_COMEmbeddingFlags_Mask)
} else {
// An external implementer is stored separately.
return $0.withMemoryRebound(to: SWRT_COMEmbeddingEx.self, capacity: 1) {
$0.pointee.swiftImplementer_retained
}
}
}

assert(opaquePointer != nil, "Bad COM object embedding. The implementer pointer is nil.")
return Unmanaged<AnyObject>.fromOpaque(opaquePointer!).takeUnretainedValue() as! Implementer
}

public static func getImplementer<ABIStruct, Implementer>(
_ this: UnsafeMutablePointer<ABIStruct>, type: Implementer.Type = Implementer.self) -> Implementer? {
do {
_ = try COMInterop(this).queryInterface(uuidof(SWRT_COMEmbedding.self))
} catch {
return nil
}

return getImplementerUnsafe(this, type: type)
}
}


internal func uuidof(_: SWRT_COMEmbedding.Type) -> COMInterfaceID {
.init(0x33934271, 0x7009, 0x4EF3, 0x90F1, 0x02090D7EBD64)
}

public enum IUnknownVirtualTable {
public static func AddRef<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>?) -> UInt32 {
guard let this else {
assertionFailure("COM this pointer was null")
return 0
}

let unmanaged = COMEmbedding.getUnmanagedEmbedderUnsafe(this)
_ = unmanaged.retain()
// Best effort refcount
return UInt32(_getRetainCount(unmanaged.takeUnretainedValue()))
}

public static func Release<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>?) -> UInt32 {
guard let this else {
assertionFailure("COM this pointer was null")
return 0
}

let unmanaged = COMEmbedding.getUnmanagedEmbedderUnsafe(this)
let oldRetainCount = _getRetainCount(unmanaged.takeUnretainedValue())
unmanaged.release()
// Best effort refcount
return UInt32(oldRetainCount - 1)
}

public static func QueryInterface<ABIStruct>(
_ this: UnsafeMutablePointer<ABIStruct>?,
_ iid: UnsafePointer<SWRT_Guid>?,
_ ppvObject: UnsafeMutablePointer<UnsafeMutableRawPointer?>?) -> SWRT_HResult {
guard let this, let iid, let ppvObject else { return COMError.toABI(hresult: HResult.invalidArg) }
ppvObject.pointee = nil

return COMError.toABI {
let id = GUIDBinding.fromABI(iid.pointee)
let this = IUnknownPointer(OpaquePointer(this))
let reference = id == uuidof(SWRT_COMEmbedding.self)
? IUnknownReference(addingRef: this)
: try COMEmbedding.getIUnknownUnsafe(this)._queryInterface(id)
ppvObject.pointee = UnsafeMutableRawPointer(reference.detach())
}
}
}
120 changes: 9 additions & 111 deletions Support/Sources/COM/COMEmbedding.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import COM_ABI
import COM_PrivateABI

/// Protocol for Swift objects which embed COM interfaces.
public protocol COMEmbedderWithDelegatedImplementation: AnyObject {
/// Gets the Swift object implementating the COM interface (often the same as self)
var delegatedImplementation: AnyObject { get }
}

/// Use as a stored property in a Swift object to embed a COM object
/// representation which shares its reference count.
/// In most cases, this is done via the `COMImplements<InterfaceBinding>` struct.
Expand All @@ -16,29 +10,31 @@ public struct COMEmbedding: ~Copyable {
public static var null: COMEmbedding { .init() }

private init() {
self.abi = SWRT_COMEmbedding(virtualTable: nil, swiftEmbedder: nil)
self.abi = .init()
}

/// Initializes an instance with a virtual table,
/// but delays setting the embedder since "self" wouldn't be available yet.
public init(virtualTable: UnsafeRawPointer, embedder: Never?) {
self.abi = SWRT_COMEmbedding(virtualTable: virtualTable, swiftEmbedder: nil)
self.abi = .init(virtualTable: virtualTable, swiftEmbedderAndFlags: 0)
}

public var virtualTable: UnsafeRawPointer? {
get { abi.virtualTable }
}

public var embedder: AnyObject? {
get { abi.swiftEmbedder.map { Unmanaged<AnyObject>.fromOpaque($0).takeUnretainedValue() } }
get {
UnsafeMutableRawPointer(bitPattern: abi.swiftEmbedderAndFlags & ~SWRT_COMEmbeddingFlags_Mask)
.map { Unmanaged<AnyObject>.fromOpaque($0).takeUnretainedValue() }
}
}

public mutating func initEmbedder(_ value: AnyObject) {
if let currentValue = abi.swiftEmbedder {
assert(Unmanaged<AnyObject>.fromOpaque(currentValue).takeUnretainedValue() === value,
"COM object already embedded in a different object.")
if abi.swiftEmbedderAndFlags != 0 {
assert(self.embedder === value, "COM object already embedded in a different object.")
} else {
abi.swiftEmbedder = Unmanaged<AnyObject>.passUnretained(value).toOpaque()
abi.swiftEmbedderAndFlags = UInt(bitPattern: Unmanaged<AnyObject>.passUnretained(value).toOpaque())
}
}

Expand All @@ -50,101 +46,3 @@ public struct COMEmbedding: ~Copyable {

public mutating func toCOM() -> IUnknownReference { .init(addingRef: asUnknownPointer()) }
}

extension COMEmbedding {
fileprivate static func getUnmanagedEmbedderUnsafe<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>) -> Unmanaged<AnyObject> {
this.withMemoryRebound(to: SWRT_COMEmbedding.self, capacity: 1) {
Unmanaged<AnyObject>.fromOpaque($0.pointee.swiftEmbedder)
}
}

public static func test<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>) -> Bool {
do { _ = try COMInterop(this).queryInterface(uuidof(SWRT_COMEmbedding.self)) } catch { return false }
return true
}

/// Gets the Swift object that embeds a given COM interface,
/// assuming that it is an embedded COM interface, and otherwise crashes.
public static func getEmbedderOrCrash<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>) -> AnyObject {
getUnmanagedEmbedderUnsafe(this).takeUnretainedValue()
}

public static func getEmbedder<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>) -> AnyObject? {
test(this) ? getEmbedderOrCrash(this) : nil
}

/// Gets the Swift object that provides the implementation for the given COM interface,
/// assuming that it is an embedded COM interface, and otherwise crashes.
public static func getImplementationOrCrash<ABIStruct, Implementation>(
_ this: UnsafeMutablePointer<ABIStruct>, type: Implementation.Type = Implementation.self) -> Implementation {
let embedderObject = getUnmanagedEmbedderUnsafe(this).takeUnretainedValue()
// Typical case: the embedder provides the implementation
if let implementation = embedderObject as? Implementation { return implementation }
// Less common case: the embedder delegates the implementation
if let embedder = embedderObject as? COMEmbedderWithDelegatedImplementation,
let implementation = embedder.delegatedImplementation as? Implementation {
return implementation
}
fatalError("COM object does not provide the expected implementation")
}

public static func getImplementation<ABIStruct, Implementation>(
_ this: UnsafeMutablePointer<ABIStruct>, type: Implementation.Type = Implementation.self) -> Implementation? {
guard test(this) else { return nil }
let embedderObject = getUnmanagedEmbedderUnsafe(this).takeUnretainedValue()
// Typical case: the embedder provides the implementation
if let implementation = embedderObject as? Implementation { return implementation }
// Less common case: the embedder delegates the implementation
if let embedder = embedderObject as? COMEmbedderWithDelegatedImplementation,
let implementation = embedder.delegatedImplementation as? Implementation {
return implementation
}
return nil
}
}

internal func uuidof(_: SWRT_COMEmbedding.Type) -> COMInterfaceID {
.init(0x33934271, 0x7009, 0x4EF3, 0x90F1, 0x02090D7EBD64)
}

public enum IUnknownVirtualTable {
public static func AddRef<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>?) -> UInt32 {
guard let this else {
assertionFailure("COM this pointer was null")
return 0
}
let unmanaged = COMEmbedding.getUnmanagedEmbedderUnsafe(this)
_ = unmanaged.retain()
// Best effort refcount
return UInt32(_getRetainCount(unmanaged.takeUnretainedValue()))
}

public static func Release<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>?) -> UInt32 {
guard let this else {
assertionFailure("COM this pointer was null")
return 0
}
let unmanaged = COMEmbedding.getUnmanagedEmbedderUnsafe(this)
let oldRetainCount = _getRetainCount(unmanaged.takeUnretainedValue())
unmanaged.release()
// Best effort refcount
return UInt32(oldRetainCount - 1)
}

public static func QueryInterface<ABIStruct>(
_ this: UnsafeMutablePointer<ABIStruct>?,
_ iid: UnsafePointer<COM_ABI.SWRT_Guid>?,
_ ppvObject: UnsafeMutablePointer<UnsafeMutableRawPointer?>?) -> COM_ABI.SWRT_HResult {
guard let this, let iid, let ppvObject else { return COMError.toABI(hresult: HResult.invalidArg) }
ppvObject.pointee = nil

return COMError.toABI {
let id = GUIDBinding.fromABI(iid.pointee)
let this = IUnknownPointer(OpaquePointer(this))
let reference = id == uuidof(SWRT_COMEmbedding.self)
? IUnknownReference(addingRef: this)
: try (COMEmbedding.getEmbedderOrCrash(this) as! IUnknown)._queryInterface(id)
ppvObject.pointee = UnsafeMutableRawPointer(reference.detach())
}
}
}
42 changes: 42 additions & 0 deletions Support/Sources/COM/COMEmbeddingEx.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import COM_ABI
import COM_PrivateABI

/// Use as a stored property in a Swift object to embed a COM object
/// representation which shares its reference count and delegates the implementation.
public struct COMEmbeddingEx: ~Copyable {
private var abi: SWRT_COMEmbeddingEx

public static var null: COMEmbeddingEx { .init() }

private init() {
self.abi = .init()
}

public init(virtualTable: UnsafeRawPointer, embedder: AnyObject, externalImplementer: AnyObject) {
// The don't reference count the embedder since this object is part of it.
// Do reference the implementer since it's an object external to the embedder.
// They can't be the same or we'd have a reference cycle.
assert(externalImplementer !== embedder, "The implementer object should be external to the embedder object.")
self.abi = .init(
base: .init(
virtualTable: virtualTable,
swiftEmbedderAndFlags: UInt(bitPattern: Unmanaged.passUnretained(embedder).toOpaque())
| SWRT_COMEmbeddingFlags_ExternalImplementer
| (embedder is IUnknown ? 0 : SWRT_COMEmbeddingFlags_ExternalImplementerIsIUnknown)),
swiftImplementer_retained: Unmanaged<AnyObject>.passRetained(externalImplementer).toOpaque())
}

deinit {
if let implementerOpaquePointer = abi.swiftImplementer_retained {
Unmanaged<AnyObject>.fromOpaque(implementerOpaquePointer).release()
}
}

public mutating func asUnknownPointer() -> IUnknownPointer {
withUnsafeMutablePointer(to: &abi) {
IUnknownPointer(OpaquePointer($0))
}
}

public mutating func toCOM() -> IUnknownReference { .init(addingRef: asUnknownPointer()) }
}
4 changes: 1 addition & 3 deletions Support/Sources/COM/COMExport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ open class COMExport<PrimaryInterfaceBinding: COMTwoWayBinding>: IUnknownProtoco

open func _queryInterface(_ id: COMInterfaceID) throws -> IUnknownReference {
switch id {
case PrimaryInterfaceBinding.interfaceID:
return toCOM().cast()
case IUnknownBinding.interfaceID:
case PrimaryInterfaceBinding.interfaceID, IUnknownBinding.interfaceID:
return toCOM().cast()
case IAgileObjectBinding.interfaceID where Self.implementIAgileObject:
return toCOM().cast()
Expand Down
4 changes: 2 additions & 2 deletions Support/Sources/COM/COMTwoWayBinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ public protocol COMTwoWayBinding: COMBinding {

extension COMTwoWayBinding {
public static func _unwrap(_ pointer: ABIPointer) -> SwiftObject? {
COMEmbedding.getImplementation(pointer)
COMEmbedding.getImplementer(pointer)
}

/// Helper for implementing virtual tables
public static func _implement<This>(_ this: UnsafeMutablePointer<This>?, _ body: (SwiftObject) throws -> Void) -> COM_ABI.SWRT_HResult {
guard let this else { return COMError.toABI(hresult: HResult.pointer, description: "COM 'this' pointer was null") }
let implementation: SwiftObject = COMEmbedding.getImplementationOrCrash(this)
let implementation: SwiftObject = COMEmbedding.getImplementerUnsafe(this)
return COMError.toABI { try body(implementation) }
}

Expand Down
6 changes: 3 additions & 3 deletions Support/Sources/COM/FreeThreadedMarshal.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import COM_ABI

/// Provides an implementation of IMarshal based on the COM free-threaded marshaler.
internal class FreeThreadedMarshal: COMSecondaryExport<FreeThreadedMarshalBinding> {
public final class FreeThreadedMarshal: COMSecondaryExport<FreeThreadedMarshalBinding> {
private let marshaler: COMReference<SWRT_IMarshal>

public init(_ identity: IUnknown) throws {
Expand Down Expand Up @@ -38,11 +38,11 @@ internal class FreeThreadedMarshal: COMSecondaryExport<FreeThreadedMarshalBindin
}
}

internal func uuidof(_: COM_ABI.SWRT_IMarshal.Type) -> COMInterfaceID {
public func uuidof(_: COM_ABI.SWRT_IMarshal.Type) -> COMInterfaceID {
.init(0x00000003, 0x0000, 0x0000, 0xC000, 0x000000000046)
}

internal enum FreeThreadedMarshalBinding: COMTwoWayBinding {
public enum FreeThreadedMarshalBinding: COMTwoWayBinding {
public typealias SwiftObject = FreeThreadedMarshal
public typealias ABIStruct = COM_ABI.SWRT_IMarshal

Expand Down
Loading

0 comments on commit 34eb952

Please sign in to comment.