diff --git a/Sources/WindowsMetadata/InterfaceID.swift b/Sources/WindowsMetadata/InterfaceID.swift index 659b5a6..01f6548 100644 --- a/Sources/WindowsMetadata/InterfaceID.swift +++ b/Sources/WindowsMetadata/InterfaceID.swift @@ -6,7 +6,8 @@ public func getInterfaceID(_ typeDefinition: TypeDefinition, genericArgs: [TypeN throw UnexpectedTypeError(typeDefinition.fullName, context: #function, reason: "Only interfaces and delegates have interface IDs") } if let genericArgs = genericArgs, genericArgs.count > 0 { - return try getParameterizedInterfaceID(typeDefinition.bindType(genericArgs: genericArgs)) + let signature = try WinRTTypeSignature(typeDefinition.bindType(genericArgs: genericArgs)) + return signature.parameterizedID } else { guard let guid = try typeDefinition.findAttribute(GuidAttribute.self) else { throw WinMDError.missingAttribute } @@ -16,112 +17,4 @@ public func getInterfaceID(_ typeDefinition: TypeDefinition, genericArgs: [TypeN public func getInterfaceID(_ type: BoundType) throws -> UUID { try getInterfaceID(type.definition, genericArgs: type.genericArgs) -} - -// https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types -fileprivate let parameterizedInterfaceGuidBytes: [UInt8] = [ - 0x11, 0xf4, 0x7a, 0xd5, - 0x7b, 0x73, - 0x42, 0xc0, - 0xab, 0xae, 0x87, 0x8b, 0x1e, 0x16, 0xad, 0xee -]; - -public func getParameterizedInterfaceID(_ type: BoundType) throws -> UUID { - var signature: String = "" - try appendSignature(type, to: &signature) - - var sha1 = SHA1() - sha1.process(parameterizedInterfaceGuidBytes) - sha1.process(Array(signature.utf8)) - let hash = sha1.finalize() - return UUID(uuid: ( - hash[0], hash[1], hash[2], hash[3], - hash[4], hash[5], - (hash[6] & 0x0F) | 0x50, hash[7], - (hash[8] & 0x3F) | 0x80, hash[9], - hash[10], hash[11], hash[12], hash[13], hash[14], hash[15])) -} - -fileprivate func appendGuid(_ guid: UUID, to signature: inout String) { - signature.append("{") - signature.append(guid.uuidString.lowercased()) - signature.append("}") -} - -fileprivate func appendSignature(_ type: BoundType, to signature: inout String) throws { - if type.genericArgs.count > 0 { - signature.append("pinterface(") - appendGuid(try type.definition.findAttribute(GuidAttribute.self)!, to: &signature) - signature.append(";") - for (index, arg) in type.genericArgs.enumerated() { - if index > 0 { signature.append(";") } - guard case .bound(let arg) = arg else { - throw UnexpectedTypeError(arg.description, context: "WinRT generic argument", reason: "Not a bound type") - } - try appendSignature(arg, to: &signature) - } - signature.append(")") - return - } - - let typeDefinition = type.definition - if typeDefinition.namespace == "System" { - switch typeDefinition.name { - case "Boolean": signature.append("b1") - case "Byte": signature.append("u1") - case "SByte": signature.append("i1") - case "Int16": signature.append("i2") - case "UInt16": signature.append("u2") - case "Int32": signature.append("i4") - case "UInt32": signature.append("u4") - case "Int64": signature.append("i8") - case "UInt64": signature.append("u8") - case "Single": signature.append("f4") - case "Double": signature.append("f8") - case "Char": signature.append("c2") - case "String": signature.append("string") - case "Guid": signature.append("g16") - case "Object": signature.append("cinterface(IInspectable)") - default: throw UnexpectedTypeError(typeDefinition.fullName, reason: "Not a well-known WinRT System type") - } - - return - } - - switch typeDefinition { - case let structDefinition as StructDefinition: - signature.append("struct(") - signature.append(structDefinition.fullName) - signature.append(";") - for (index, field) in structDefinition.fields.enumerated() { - guard field.isInstance else { continue } - if index > 0 { signature.append(";") } - let fieldType = try field.type - guard case .bound(let fieldType) = fieldType else { - throw UnexpectedTypeError(fieldType.description, context: "WinRT struct field", reason: "Not a bound type") - } - try appendSignature(fieldType, to: &signature) - } - signature.append(")") - case let enumDefinition as EnumDefinition: - signature.append("enum(") - signature.append(enumDefinition.fullName) - signature.append(";") - try appendSignature(enumDefinition.underlyingType.bindType(), to: &signature) - signature.append(")") - case let delegateDefinition as DelegateDefinition: - signature.append("delegate(") - appendGuid(try delegateDefinition.findAttribute(GuidAttribute.self)!, to: &signature) - signature.append(")") - case let interfaceDefinition as InterfaceDefinition: - appendGuid(try interfaceDefinition.findAttribute(GuidAttribute.self)!, to: &signature) - case let classDefinition as ClassDefinition: - signature.append("rc(") - signature.append(classDefinition.fullName) - signature.append(";") - try appendSignature(DefaultAttribute.getDefaultInterface(classDefinition)!.asBoundType, to: &signature) - signature.append(")") - default: - fatalError("Unexpected type definition: \(typeDefinition)") - } } \ No newline at end of file diff --git a/Sources/WindowsMetadata/WinRTTypeSignature+fromBoundType.swift b/Sources/WindowsMetadata/WinRTTypeSignature+fromBoundType.swift new file mode 100644 index 0000000..775509f --- /dev/null +++ b/Sources/WindowsMetadata/WinRTTypeSignature+fromBoundType.swift @@ -0,0 +1,72 @@ +import DotNetMetadata + +extension WinRTTypeSignature { + public init(_ type: BoundType) throws { + self = try WinRTTypeSignature.fromBoundType(type) + } + + private static func fromBoundType(_ type: BoundType) throws -> WinRTTypeSignature { + if type.genericArgs.count > 0 { + let id = try type.definition.findAttribute(GuidAttribute.self)! + let args = try type.genericArgs.map { + guard case .bound(let arg) = $0 else { + throw UnexpectedTypeError($0.description, context: "WinRT generic argument", reason: "Not a bound type") + } + return try fromBoundType(arg) + } + return type.definition is DelegateDefinition + ? .delegate(id: id, args: args) + : .interface(id: id, args: args) + } + + if type.definition.namespace == "System" { + switch type.definition.name { + case "Boolean": return .baseType(.boolean) + case "Byte": return .baseType(.uint8) + case "Int16": return .baseType(.int16) + case "UInt16": return .baseType(.uint16) + case "Int32": return .baseType(.int32) + case "UInt32": return .baseType(.uint32) + case "Int64": return .baseType(.int64) + case "UInt64": return .baseType(.uint64) + case "Single": return .baseType(.single) + case "Double": return .baseType(.double) + case "Char": return .baseType(.char16) + case "String": return .baseType(.string) + case "Guid": return .baseType(.guid) + case "Object": return .comInterface + default: throw UnexpectedTypeError(type.definition.fullName, reason: "Not a well-known WinRT System type") + } + } + + switch type.definition { + case is StructDefinition: + let fields = try type.definition.fields.map { + let type = try $0.type + guard case .bound(let arg) = type else { + throw UnexpectedTypeError(type.description, context: "WinRT field", reason: "Not a bound type") + } + return try fromBoundType(arg) + } + return .struct(name: type.definition.fullName, fields: fields) + + case is EnumDefinition: + return .enum(name: type.definition.fullName, flags: try type.definition.hasAttribute(FlagsAttribute.self)) + + case is InterfaceDefinition: + let id = try type.definition.findAttribute(GuidAttribute.self)! + return .interface(id: id) + + case is DelegateDefinition: + let id = try type.definition.findAttribute(GuidAttribute.self)! + return .delegate(id: id) + + case let classDefinition as ClassDefinition: + let defaultInterface = try DefaultAttribute.getDefaultInterface(classDefinition)! + return .runtimeClass(name: classDefinition.fullName, defaultInterface: try fromBoundType(defaultInterface.asBoundType)) + + default: + fatalError("Unexpected type definition: \(type.definition)") + } + } +} \ No newline at end of file diff --git a/Sources/WindowsMetadata/WinRTTypeSignature+parameterizedID.swift b/Sources/WindowsMetadata/WinRTTypeSignature+parameterizedID.swift new file mode 100644 index 0000000..ed088f7 --- /dev/null +++ b/Sources/WindowsMetadata/WinRTTypeSignature+parameterizedID.swift @@ -0,0 +1,32 @@ +import struct Foundation.UUID + +// https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types +fileprivate let parameterizedInterfaceGuidBytes: [UInt8] = [ + 0x11, 0xf4, 0x7a, 0xd5, + 0x7b, 0x73, + 0x42, 0xc0, + 0xab, 0xae, 0x87, 0x8b, 0x1e, 0x16, 0xad, 0xee +]; + +extension WinRTTypeSignature { + public var parameterizedID: UUID { + get { + switch self { + case let .interface(_, args), let .delegate(_, args): precondition(!args.isEmpty) + default: preconditionFailure("Only interfaces and delegates have parameterized IDs") + } + + var sha1 = SHA1() + sha1.process(parameterizedInterfaceGuidBytes) + sha1.process(Array(self.toString().utf8)) + let hash = sha1.finalize() + + return UUID(uuid: ( + hash[0], hash[1], hash[2], hash[3], + hash[4], hash[5], + (hash[6] & 0x0F) | 0x50, hash[7], + (hash[8] & 0x3F) | 0x80, hash[9], + hash[10], hash[11], hash[12], hash[13], hash[14], hash[15])) + } + } +} \ No newline at end of file diff --git a/Sources/WindowsMetadata/WinRTTypeSignature+string.swift b/Sources/WindowsMetadata/WinRTTypeSignature+string.swift new file mode 100644 index 0000000..a4f8052 --- /dev/null +++ b/Sources/WindowsMetadata/WinRTTypeSignature+string.swift @@ -0,0 +1,83 @@ +import struct Foundation.UUID + +extension WinRTTypeSignature { + public func toString() -> String { + var string = "" + appendString(&string) + return string + } + + public func appendString(_ string: inout String) { + switch self { + case let .interface(id, args): + if args.isEmpty { + appendGuid(id, to: &string) + } + else { + appendParameterizedInterface(id, args: args, to: &string) + } + + case let .delegate(id, args): + if args.isEmpty { + string.append("delegate(") + appendGuid(id, to: &string) + string.append(")") + } + else { + appendParameterizedInterface(id, args: args, to: &string) + } + + case let .baseType(baseType): + string.append(baseType.rawValue) + + case .comInterface: + string.append("cinterface(IInspectable)") + + case let .interfaceGroup(name, defaultInterface): + string.append("ig(") + string.append(name) + string.append(";") + defaultInterface.appendString(&string) + string.append(")") + + case let .runtimeClass(name, defaultInterface): + string.append("rc(") + string.append(name) + string.append(";") + defaultInterface.appendString(&string) + string.append(")") + + case let .struct(name, fields): + string.append("struct(") + string.append(name) + for field in fields { + string.append(";") + field.appendString(&string) + } + string.append(")") + + case let .enum(name, flags): + string.append("enum(") + string.append(name) + string.append(";") + string.append(flags ? "u4" : "i4") + string.append(")") + } + } +} + +fileprivate func appendParameterizedInterface(_ id: UUID, args: [WinRTTypeSignature], to string: inout String) { + string.append("pinterface(") + appendGuid(id, to: &string) + for arg in args { + string.append(";") + arg.appendString(&string) + } + string.append(")") +} + +fileprivate func appendGuid(_ guid: UUID, to string: inout String) { + string.append("{") + string.append(guid.uuidString.lowercased()) + string.append("}") +} \ No newline at end of file diff --git a/Sources/WindowsMetadata/WinRTTypeSignature.swift b/Sources/WindowsMetadata/WinRTTypeSignature.swift new file mode 100644 index 0000000..3a57989 --- /dev/null +++ b/Sources/WindowsMetadata/WinRTTypeSignature.swift @@ -0,0 +1,30 @@ +import struct Foundation.UUID + +/// A WinRT type signature, used to generate GUIDs for parameterized interface and delegate types. +/// See https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types +public enum WinRTTypeSignature: Hashable { + case interface(id: UUID, args: [WinRTTypeSignature] = []) + case delegate(id: UUID, args: [WinRTTypeSignature] = []) + case baseType(BaseType) + case comInterface + indirect case interfaceGroup(name: String, default: WinRTTypeSignature) + indirect case runtimeClass(name: String, defaultInterface: WinRTTypeSignature) + case `struct`(name: String, fields: [WinRTTypeSignature]) + case `enum`(name: String, flags: Bool) + + public enum BaseType: String, Hashable { + case boolean = "b1" + case uint8 = "u1" + case int16 = "i2" // Undocumented but presumably valid + case uint16 = "u2" // Undocumented but presumably valid + case int32 = "i4" + case uint32 = "u4" + case int64 = "i8" + case uint64 = "u8" + case single = "f4" + case double = "f8" + case char16 = "c2" + case string = "string" + case guid = "g16" + } +} \ No newline at end of file diff --git a/Tests/WindowsMetadata/WinRTTypeSignatureTests.swift b/Tests/WindowsMetadata/WinRTTypeSignatureTests.swift new file mode 100644 index 0000000..97c20b3 --- /dev/null +++ b/Tests/WindowsMetadata/WinRTTypeSignatureTests.swift @@ -0,0 +1,10 @@ +import XCTest +import WindowsMetadata + +final class WinRTTypeSignatureTests: XCTestCase { + public func testInterfaceID() throws { + let ireferenceInterfaceID = UUID(uuidString: "61c17706-2d65-11e0-9ae8-d48564015472")! + let signature: WinRTTypeSignature = .interface(id: ireferenceInterfaceID, args: [.baseType(.int32)]) + XCTAssertEqual(signature.parameterizedID, UUID(uuidString: "548cefbd-bc8a-5fa0-8df2-957440fc8bf4")) // IReference + } +} \ No newline at end of file