package codegen import ( "bytes" "fmt" "go/format" "os" "path/filepath" "strings" "git.savin.nyc/alex/go-winrt" "git.savin.nyc/alex/go-winrt/internal/winmd" "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/tdakkota/win32metadata/types" "golang.org/x/tools/imports" ) // Delegate constants const ( invokeMethodName = "Invoke" ) type generator struct { class string validateOnly bool methodFilter *MethodFilter logger log.Logger genDataFiles []*genDataFile mdStore *winmd.Store } // Generate generates the code for the given config. func Generate(cfg *Config, logger log.Logger) error { if err := cfg.Validate(); err != nil { return err } mdStore, err := winmd.NewStore(logger) if err != nil { return err } g := &generator{ class: cfg.Class, validateOnly: cfg.ValidateOnly, methodFilter: cfg.MethodFilter(), logger: logger, mdStore: mdStore, } return g.run() } func (g *generator) run() error { _ = level.Debug(g.logger).Log("msg", "starting code generation", "class", g.class) typeDef, err := g.mdStore.TypeDefByName(g.class) if err != nil { return err } return g.generate(typeDef) } func (g *generator) generate(typeDef *winmd.TypeDef) error { // we only support WinRT types: check the tdWindowsRuntime flag (0x4000) // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#runtime-classes if typeDef.Flags&0x4000 == 0 { return fmt.Errorf("%s.%s is not a WinRT class", typeDef.TypeNamespace, typeDef.TypeName) } // get data & execute templates if err := g.loadCodeGenData(typeDef); err != nil { return err } for _, fData := range g.genDataFiles { if err := g.generateDataFile(fData, typeDef); err != nil { return err } } return nil } func (g *generator) generateDataFile(fData *genDataFile, typeDef *winmd.TypeDef) error { // get templates tmpl, err := getTemplates() if err != nil { return err } fData.Data.ComputeImports(typeDef) var buf bytes.Buffer if err := tmpl.ExecuteTemplate(&buf, "file.tmpl", fData.Data); err != nil { return err } // use go imports to cleanup imports goimported, err := imports.Process(fData.Filename, buf.Bytes(), nil) if err != nil { return err } // format the output source code formatted, err := format.Source(goimported) if err != nil { return err } if g.validateOnly { // validate existing file content return g.validateFileContent(fData, formatted) } // create file & write contents return g.writeFile(fData, formatted) } func (g *generator) validateFileContent(fData *genDataFile, genContent []byte) error { // validate existing content existingContent, err := os.ReadFile(fData.Filename) if err != nil { return err } // compare existing content to generated _ = level.Debug(g.logger).Log("msg", "validating generated code", "filename", fData.Filename) if string(existingContent) != string(genContent) { return fmt.Errorf("file %s does not contain expected content", fData.Filename) } return nil } func (g *generator) writeFile(fData *genDataFile, content []byte) error { parts := strings.Split(fData.Filename, "/") folder := strings.Join(parts[:len(parts)-1], "/") err := os.MkdirAll(folder, os.ModePerm) if err != nil { return err } file, err := os.Create(filepath.Clean(fData.Filename)) if err != nil { return err } defer func() { _ = file.Close() }() // and write it to file _, err = file.Write(content) if err != nil { return err } return nil } func (g *generator) loadCodeGenData(typeDef *winmd.TypeDef) error { f := g.addFile(typeDef, "") switch { case typeDef.IsInterface(): _ = level.Info(g.logger).Log("msg", "generating interface", "interface", typeDef.TypeNamespace+"."+typeDef.TypeName) if err := g.validateInterface(typeDef); err != nil { return err } iface, err := g.createGenInterface(typeDef, false) if err != nil { return err } f.Data.Interfaces = append(f.Data.Interfaces, iface) case typeDef.IsEnum(): _ = level.Info(g.logger).Log("msg", "generating enum", "enum", typeDef.TypeNamespace+"."+typeDef.TypeName) enum, err := g.createGenEnum(typeDef) if err != nil { return err } f.Data.Enums = append(f.Data.Enums, enum) case typeDef.IsStruct(): _ = level.Info(g.logger).Log("msg", "generating struct", "struct", typeDef.TypeNamespace+"."+typeDef.TypeName) genStruct, err := g.createGenStruct(typeDef) if err != nil { return err } f.Data.Structs = append(f.Data.Structs, genStruct) case typeDef.IsDelegate(): delegate, err := g.createGenDelegate(typeDef) if err != nil { return err } f.Data.Delegates = append(f.Data.Delegates, delegate) default: _ = level.Info(g.logger).Log("msg", "generating class", "class", typeDef.TypeNamespace+"."+typeDef.TypeName) class, err := g.createGenClass(typeDef) if err != nil { return err } f.Data.Classes = append(f.Data.Classes, class) } return nil } func (g *generator) addFile(typeDef *winmd.TypeDef, suffix string) *genDataFile { folder := typeToFolder(typeDef.TypeNamespace, typeDef.TypeName) filename := folder + "/" + typeFilename(typeDef.TypeName) + suffix + ".go" f := genDataFile{ Filename: filename, Data: genData{ Package: typePackage(typeDef.TypeNamespace, typeDef.TypeName), }, } g.genDataFiles = append(g.genDataFiles, &f) return &f } func (g *generator) validateInterface(typeDef *winmd.TypeDef) error { // Any WinRT interface with private visibility must have a single ExclusiveToAttribute. // the ExclusiveToAttribute must reference a runtime class. // we do not support generating these types of classes, they are exclusive to a runtime class, // and thus will be generated when the runtime class is generated. if typeDef.Flags.NotPublic() { return fmt.Errorf("interface %s is not public", typeDef.TypeNamespace+"."+typeDef.TypeName) } return nil } // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#interfaces func (g *generator) createGenInterface(typeDef *winmd.TypeDef, requiresActivation bool) (*genInterface, error) { funcs, err := g.getGenFuncs(typeDef, requiresActivation) if err != nil { return nil, err } // Interfaces' TypeDef rows must have a GuidAttribute as well as a VersionAttribute. guid, err := typeDef.GUID() if err != nil { return nil, err } typeSig, err := g.Signature(typeDef) if err != nil { return nil, err } return &genInterface{ Name: typeDefGoName(typeDef.TypeName, typeDef.Flags.Public()), GUID: guid, Signature: typeSig, Funcs: funcs, }, nil } // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#runtime-classes func (g *generator) createGenClass(typeDef *winmd.TypeDef) (*genClass, error) { var requiredImports []*genImport var exclusiveInterfaceTypes []*winmd.TypeDef // true => interface requires activation, false => interface is implemented by this class activatedInterfaces := make(map[string]bool) // get all the interfaces this class implements interfaces, err := typeDef.GetImplementedInterfaces() if err != nil { return nil, err } implInterfaces := make([]*genInterface, 0, len(interfaces)) for _, iface := range interfaces { // the interface needs to be implemented by this class requiredImports = append(requiredImports, &genImport{iface.Namespace, iface.Name}) ifaceTypeDef, err := g.mdStore.TypeDefByName(iface.Namespace + "." + iface.Name) if err != nil { return nil, err } itf, err := g.createGenInterface(ifaceTypeDef, false) if err != nil { return nil, err } pkg := "" if typeDef.TypeNamespace != ifaceTypeDef.TypeNamespace { pkg = typePackage(iface.Namespace, iface.Name) } for _, f := range itf.Funcs { f.InheritedFrom = winmd.QualifiedID{ Namespace: pkg, Name: typeDefGoName(ifaceTypeDef.TypeName, ifaceTypeDef.Flags.Public()), } } implInterfaces = append(implInterfaces, itf) // The interface we implement may be exclusive to this class, in which case we need to generate it. // An exclusive (private) interface should always belong to the same winmd file. So even if this is // a TypeRef, the class should be found using its name. if td, err := g.mdStore.TypeDefByName(iface.Namespace + "." + iface.Name); err == nil { if _, ok := g.interfaceIsExclusiveTo(td); ok { exclusiveInterfaceTypes = append(exclusiveInterfaceTypes, td) activatedInterfaces[td.TypeNamespace+"."+td.TypeName] = false // implemented interfaces do not require activation } } } // Runtime classes have zero or more StaticAttribute custom attributes // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#static-interfaces staticAttributeBlobs := typeDef.GetTypeDefAttributesWithType(winmd.AttributeTypeStaticAttribute) for _, blob := range staticAttributeBlobs { class := extractClassFromBlob(blob) _ = level.Debug(g.logger).Log("msg", "found static interface", "class", class) staticClass, err := g.mdStore.TypeDefByName(class) if err != nil { _ = level.Error(g.logger).Log("msg", "static class defined in StaticAttribute not found", "class", class, "err", err) return nil, err } exclusiveInterfaceTypes = append(exclusiveInterfaceTypes, staticClass) activatedInterfaces[staticClass.TypeNamespace+"."+staticClass.TypeName] = true // static interfaces require activation } // Runtime classes have zero or more ActivatableAttribute custom attributes // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#activation activatableAttributeBlobs := typeDef.GetTypeDefAttributesWithType(winmd.AttributeTypeActivatableAttribute) hasEmptyConstructor := false for _, blob := range activatableAttributeBlobs { // check for empty constructor if activatableAttrIsEmpty(blob) { // this activatable attribute is empty, so the class has an empty constructor hasEmptyConstructor = true continue } // check for an activation interface class := extractClassFromBlob(blob) _ = level.Debug(g.logger).Log("msg", "found activatable interface", "class", class) activatableClass, err := g.mdStore.TypeDefByName(class) if err != nil { // the activatable class may be empty in some cases, example: // https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/winrt/windows.devices.bluetooth.advertisement.idl#L518 _ = level.Error(g.logger).Log("msg", "activatable class defined in ActivatableAttribute not found", "class", class, "err", err) // so do not fail continue } exclusiveInterfaceTypes = append(exclusiveInterfaceTypes, activatableClass) activatedInterfaces[activatableClass.TypeNamespace+"."+activatableClass.TypeName] = true // activatable interfaces require activation } // generate exclusive interfaces var exclusiveGenInterfaces []*genInterface for _, iface := range exclusiveInterfaceTypes { requiresActivation := activatedInterfaces[iface.TypeNamespace+"."+iface.TypeName] isExtendedInterface := !requiresActivation ifaceGen, err := g.createGenInterface(iface, requiresActivation) if err != nil { return nil, err } // if all methods from the exclusive interface have been filtered, and the interface // is an activated interface, then we can skip it. impl := false for _, m := range ifaceGen.Funcs { if m.Implement { impl = true } } // Extended interfaces always need to be generated (even if they have no method). if isExtendedInterface || impl { exclusiveGenInterfaces = append(exclusiveGenInterfaces, ifaceGen) } } typeSig, err := g.Signature(typeDef) if err != nil { return nil, err } return &genClass{ Name: typeDefGoName(typeDef.TypeName, typeDef.Flags.Public()), Signature: typeSig, RequiresImports: requiredImports, FullyQualifiedName: typeDef.TypeNamespace + "." + typeDef.TypeName, ImplInterfaces: implInterfaces, ExclusiveInterfaces: exclusiveGenInterfaces, HasEmptyConstructor: hasEmptyConstructor, IsAbstract: typeDef.Flags.Abstract(), }, nil } // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#enums func (g *generator) createGenEnum(typeDef *winmd.TypeDef) (*genEnum, error) { fields, err := typeDef.ResolveFieldList(typeDef.Ctx()) if err != nil { return nil, err } // An enum has a single instance field that specifies the underlying integer type for the enum, // as well as zero or more static fields; one for each enum value defined by the enum type. if len(fields) == 0 { return nil, fmt.Errorf("enum %s has no fields", typeDef.TypeName) } // the first row should be the underlying integer type of the enum. It must have the following flags: if !(fields[0].Flags.Private() && fields[0].Flags.SpecialName() && fields[0].Flags.RTSpecialName()) { return nil, fmt.Errorf("enum %s has more than one instance field, expected 1", typeDef.TypeNamespace+"."+typeDef.TypeName) } fieldSig, err := fields[0].Signature.Reader().Field(typeDef.Ctx()) if err != nil { return nil, err } elType, err := g.elementType(typeDef.Ctx(), fieldSig.Field) if err != nil { return nil, err } // this will always be a primitive type, so we can just use the name enumType := elType.name // After the enum value definition comes a field definition for each of the values in the enumeration. var enumValues []*genEnumValue for i, field := range fields[1:] { if !(field.Flags.Public() && field.Flags.Static() && field.Flags.Literal() && field.Flags.HasDefault()) { return nil, fmt.Errorf( "enum %s field value does not comply with the spec. Checkout https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#enums", typeDef.TypeNamespace+"."+typeDef.TypeName, ) } var fieldIndex uint32 = typeDef.FieldList.Start() + 1 + uint32(i) enumRawValue, err := typeDef.GetValueForEnumField(fieldIndex) if err != nil { return nil, err } enumValues = append(enumValues, &genEnumValue{ Name: enumName(typeDef.TypeName, field.Name), Value: enumRawValue, }) } typeSig, err := g.Signature(typeDef) if err != nil { return nil, err } return &genEnum{ Name: typeDefGoName(typeDef.TypeName, typeDef.Flags.Public()), Type: enumType, Signature: typeSig, Values: enumValues, }, nil } // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#structs func (g *generator) createGenStruct(typeDef *winmd.TypeDef) (*genStruct, error) { // structs do not have methods, only fields fields, err := typeDef.ResolveFieldList(typeDef.Ctx()) if err != nil { return nil, err } curPkg := typePackage(typeDef.TypeNamespace, typeDef.TypeName) var genFields []*genParam for _, f := range fields { fSig, err := f.Signature.Reader().Field(typeDef.Ctx()) if err != nil { return nil, err } fieldType, err := g.elementType(typeDef.Ctx(), fSig.Field) if err != nil { return nil, err } // Struct fields must be fundamental types, enums, or other structs genFields = append(genFields, &genParam{ callerPackage: curPkg, varName: cleanReservedWords(f.Name), IsOut: false, Type: fieldType, }) } typeSig, err := g.Signature(typeDef) if err != nil { return nil, err } return &genStruct{ Name: typeDefGoName(typeDef.TypeName, typeDef.Flags.Public()), Signature: typeSig, Fields: genFields, }, nil } // https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#delegates func (g *generator) createGenDelegate(typeDef *winmd.TypeDef) (*genDelegate, error) { // FieldList: must be empty // MethodList: An index into the MethodDef table (ECMA II.22.26), marking the first of a contiguous run of methods owned by this type. // Delegates' TypeDef rows must have a GuidAttribute guid, err := typeDef.GUID() if err != nil { return nil, err } // Delegates have exactly two MethodDef table entries. The first defines a constructor. methods, err := typeDef.ResolveMethodList(typeDef.Ctx()) if err != nil { return nil, err } if len(methods) != 2 { return nil, fmt.Errorf("delegate %s has more than two methods", typeDef.TypeNamespace+"."+typeDef.TypeName) } // This constructor is a compatibility marker. WinRT Delegates have no such constructor method. // We only care about the invoke method invokeMethod := methods[1] if invokeMethod.Name != invokeMethodName { return nil, fmt.Errorf("found method '%s' on delegate %s but expected '%s'", invokeMethod.Name, typeDef.TypeNamespace+"."+typeDef.TypeName, invokeMethodName, ) } // this is going to be used to define the callback type. We don't // really need the whole function, only its input parameters, // so we can reuse the logic used for getting them. f, err := g.genFuncFromMethod(typeDef, &invokeMethod, "", false) if err != nil { return nil, err } typeSig, err := g.Signature(typeDef) if err != nil { return nil, err } return &genDelegate{ Name: typeDefGoName(typeDef.TypeName, true), GUID: guid, Signature: typeSig, InParams: f.InParams, }, nil } func (g *generator) interfaceIsExclusiveTo(typeDef *winmd.TypeDef) (string, bool) { exclusiveToBlob, err := typeDef.GetAttributeWithType(winmd.AttributeTypeExclusiveTo) // an error here is fine, we just won't have the ExclusiveTo attribute if err != nil { return "", false } exclusiveToClass := extractClassFromBlob(exclusiveToBlob) return exclusiveToClass, true } func activatableAttrIsEmpty(blob []byte) bool { // the activatable attribute is empty if the size is 0 // 01 00 - header // 00 - size return len(blob) >= 3 && blob[0] == 0x01 && blob[1] == 0x00 && blob[2] == 0x00 } func extractClassFromBlob(blob []byte) string { // the blob contains a two byte header // 01 00 // followed by a byte with the size // XX // followed by the type name // so we need at least 4 bytes if len(blob) < 4 { return "" } size := blob[2] class := blob[3 : 3+size] return string(class) } func (g *generator) getGenFuncs(typeDef *winmd.TypeDef, requiresActivation bool) ([]*genFunc, error) { var genFuncs []*genFunc methods, err := typeDef.ResolveMethodList(typeDef.Ctx()) if err != nil { return nil, err } var exclusiveToType string if ex, ok := g.interfaceIsExclusiveTo(typeDef); ok { exclusiveToType = ex } for _, m := range methods { methodDef := m generatedFunc, err := g.genFuncFromMethod(typeDef, &methodDef, exclusiveToType, requiresActivation) if err != nil { return nil, err } genFuncs = append(genFuncs, generatedFunc) } return genFuncs, nil } func (g *generator) genFuncFromMethod(typeDef *winmd.TypeDef, methodDef *types.MethodDef, exclusiveTo string, requiresActivation bool) (*genFunc, error) { // add the type imports to the top of the file // only if the method is going to be implemented overloadName := winmd.GetMethodOverloadName(typeDef.Ctx(), methodDef) implement := g.shouldImplementMethod(overloadName) if !implement { // if we don't implement the method, we don't need to gather // all the information, just the name of it is enough return &genFunc{ Name: overloadName, RequiresImports: nil, Implement: implement, InParams: nil, ReturnParams: nil, FuncOwner: typeDefGoName(typeDef.TypeName, typeDef.Flags.Public()), ExclusiveTo: exclusiveTo, RequiresActivation: requiresActivation, }, nil } curPackage := typePackage(typeDef.TypeNamespace, typeDef.TypeName) params, err := g.getInParameters(curPackage, typeDef, methodDef) if err != nil { return nil, err } retParams, err := g.getReturnParameters(curPackage, typeDef, methodDef) if err != nil { return nil, err } // iterate over all parameters (in or out) to gather the required imports // and calculate if we need the package name when referencing a type // based on the current package var allImplementedParams []*genParam allImplementedParams = append(allImplementedParams, params...) allImplementedParams = append(allImplementedParams, retParams...) var requiredImports []*genImport for _, p := range allImplementedParams { p.callerPackage = curPackage if !p.Type.IsPrimitive { requiredImports = append(requiredImports, &genImport{p.Type.namespace, p.Type.name}) } } return &genFunc{ Name: overloadName, RequiresImports: requiredImports, Implement: implement, InParams: params, ReturnParams: retParams, FuncOwner: typeDefGoName(typeDef.TypeName, typeDef.Flags.Public()), ExclusiveTo: exclusiveTo, RequiresActivation: requiresActivation, }, nil } func (g *generator) shouldImplementMethod(methodName string) bool { return g.methodFilter.Filter(methodName) } func (g *generator) getInParameters(curPackage string, typeDef *winmd.TypeDef, methodDef *types.MethodDef) ([]*genParam, error) { params, err := methodDef.ResolveParamList(typeDef.Ctx()) if err != nil { return nil, err } // the signature contains the parameter // types and return type of the method r := methodDef.Signature.Reader() mr, err := r.Method(typeDef.Ctx()) if err != nil { return nil, err } var genParams []*genParam for i, e := range mr.Params { param := getParamByIndex(params, uint16(i+1)) if param == nil { _ = level.Error(g.logger).Log("msg", "Parameter with index not found", "index", i+1) continue // do not fail } // When encoding an Array parameter for any interface member type, the array length // parameter that immediately precedes the array parameter is omitted from both the // MethodDefSig blob as well from as the params table. => so we need to add it manually. // Do not trust e.IsArray variable, it's only true for the ELEMENT_TYPE_ARRAY, it if e.Type.Kind == types.ELEMENT_TYPE_SZARRAY || e.Type.Kind == types.ELEMENT_TYPE_ARRAY { // The direction of the array parameter is directly encoded in metadata.The direction of // the array length parameter may be inferred as follows. // - If the array parameter is an in parameter, the array length parameter must also // be an IN PARAMETER. // - If the array parameter is an out parameter and is not carrying the BYREF // marker, the array length is an IN PARAMETER. // - If the array parameter is an out parameter and carries the BYREF marker, the // array length is an OUT PARAMETER. sizeIsOutParam := param.Flags.Out() && e.ByRef genParams = append(genParams, &genParam{ callerPackage: curPackage, // Do not change this without also changing the code in the templates varName: cleanReservedWords(param.Name + "Size"), IsOut: sizeIsOutParam, Type: &genParamType{ namespace: "", name: "uint32", defaultValue: genDefaultValue{"0", true}, IsPrimitive: true, IsPointer: false, IsArray: false, }, }) } elType, err := g.elementType(typeDef.Ctx(), e) if err != nil { return nil, err } genParams = append(genParams, &genParam{ callerPackage: curPackage, varName: cleanReservedWords(getParamName(params, uint16(i+1))), IsOut: param.Flags.Out(), Type: elType, }) } return genParams, nil } func (g *generator) getReturnParameters(curPackage string, typeDef *winmd.TypeDef, methodDef *types.MethodDef) ([]*genParam, error) { // the signature contains the parameter // types and return type of the method r := methodDef.Signature.Reader() methodSignature, err := r.Method(typeDef.Ctx()) if err != nil { return nil, err } var genParams []*genParam // ignore void types if methodSignature.Return.Type.Kind == types.ELEMENT_TYPE_VOID { return genParams, nil } elType, err := g.elementType(typeDef.Ctx(), methodSignature.Return) if err != nil { return nil, err } genParams = append(genParams, &genParam{ // return param always has an index of zero callerPackage: curPackage, varName: "out", IsOut: true, Type: elType, }) return genParams, nil } func getParamName(params []types.Param, i uint16) string { for _, p := range params { if p.Sequence == i { return p.Name } } return fmt.Sprintf("__ERROR_PARAM_%d_NOT_FOUND__", i) } func getParamByIndex(params []types.Param, i uint16) *types.Param { for _, p := range params { if p.Sequence == i { return &p } } return nil } func (g *generator) elementType(ctx *types.Context, e types.Element) (*genParamType, error) { switch e.Type.Kind { case types.ELEMENT_TYPE_BOOLEAN: return &genParamType{ namespace: "", name: "bool", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_CHAR: return &genParamType{ namespace: "", name: "byte", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_I1: return &genParamType{ namespace: "", name: "int8", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_U1: return &genParamType{ namespace: "", name: "uint8", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_I2: return &genParamType{ namespace: "", name: "int16", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_U2: return &genParamType{ namespace: "", name: "uint16", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_I4: return &genParamType{ namespace: "", name: "int32", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_U4: return &genParamType{ namespace: "", name: "uint32", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_I8: return &genParamType{ namespace: "", name: "int64", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_U8: return &genParamType{ namespace: "", name: "uint64", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_R4: return &genParamType{ namespace: "", name: "float32", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_R8: return &genParamType{ namespace: "", name: "float64", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_STRING: return &genParamType{ namespace: "", name: "string", IsPointer: false, IsPrimitive: true, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_GENERICINST: fallthrough case types.ELEMENT_TYPE_CLASS: // return class name namespace, name, err := ctx.ResolveTypeDefOrRefName(e.Type.TypeDef.Index) if err != nil { return nil, err } return &genParamType{ namespace: namespace, name: name, IsPointer: true, IsPrimitive: false, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_VALUETYPE: namespace, name, err := ctx.ResolveTypeDefOrRefName(e.Type.TypeDef.Index) if err != nil { return nil, err } // Check for system types if t, ok := isSystemType(namespace, name); ok { return t, nil } elementTypeDef, err := g.mdStore.TypeDefByName(namespace + "." + name) if err != nil { return nil, err } // if its an enum, we will need the underlying type isEnum := false enumType := "" if elementTypeDef.IsEnum() { enumData, err := g.createGenEnum(elementTypeDef) if err != nil { return nil, err } // Treat the enum as a primitive isEnum = true enumType = enumData.Type } return &genParamType{ namespace: namespace, name: name, IsPointer: false, IsPrimitive: false, IsArray: false, IsEnum: isEnum, UnderlyingEnumType: enumType, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_VAR: // Generic types are not fully supported yet, // so we will just pass the raw unsafe.Pointer up to the user. return &genParamType{ namespace: "unsafe", name: "Pointer", IsGeneric: true, IsPointer: false, IsPrimitive: false, IsArray: false, defaultValue: g.elementDefaultValue(ctx, e), }, nil case types.ELEMENT_TYPE_SZARRAY: //A single-dimensional, zero lower-bound array type modifier // e.Type.SZArray.Elem should be non-nil param, err := g.elementType(ctx, *e.Type.SZArray.Elem) if err != nil { return nil, err } param.IsArray = true // override default val param.defaultValue = genDefaultValue{"nil", true} return param, err case types.ELEMENT_TYPE_OBJECT: // This represents System.Object, so just use a pointer return &genParamType{ namespace: "unsafe", name: "Pointer", IsPointer: false, IsPrimitive: false, IsArray: false, defaultValue: genDefaultValue{"nil", true}, }, nil default: return nil, fmt.Errorf("unsupported element type: %v", e.Type.Kind) } } func isSystemType(namespace, name string) (*genParamType, bool) { if namespace != "System" { return nil, false } switch name { case "Guid": // System.Guid is a struct (value type), so we can just // use syscall.GUID which has the same structure return &genParamType{ namespace: "syscall", name: "GUID", IsPointer: false, IsPrimitive: false, IsArray: false, IsEnum: false, defaultValue: genDefaultValue{value: "GUID{}", isPrimitive: false}, }, true } return nil, false } func (g *generator) elementDefaultValue(ctx *types.Context, e types.Element) genDefaultValue { switch e.Type.Kind { case types.ELEMENT_TYPE_BOOLEAN: return genDefaultValue{"false", true} case types.ELEMENT_TYPE_CHAR, types.ELEMENT_TYPE_I1, types.ELEMENT_TYPE_U1, types.ELEMENT_TYPE_I2, types.ELEMENT_TYPE_U2, types.ELEMENT_TYPE_I4, types.ELEMENT_TYPE_U4, types.ELEMENT_TYPE_I8, types.ELEMENT_TYPE_U8: return genDefaultValue{"0", true} case types.ELEMENT_TYPE_R4, types.ELEMENT_TYPE_R8: return genDefaultValue{"0.0", true} case types.ELEMENT_TYPE_STRING: return genDefaultValue{"\"\"", true} case types.ELEMENT_TYPE_CLASS, types.ELEMENT_TYPE_GENERICINST, types.ELEMENT_TYPE_SZARRAY: return genDefaultValue{"nil", true} case types.ELEMENT_TYPE_VALUETYPE: // we need to get the underlying type (enum, struct, etc...) namespace, name, err := ctx.ResolveTypeDefOrRefName(e.Type.TypeDef.Index) if err != nil { return genDefaultValue{"__ERROR_" + err.Error(), true} } elementTypeDef, err := g.mdStore.TypeDefByName(namespace + "." + name) if err != nil { return genDefaultValue{"__ERROR_" + err.Error(), true} } if elementTypeDef.IsEnum() { // return the first enum value fields, err := elementTypeDef.ResolveFieldList(ctx) if err != nil { return genDefaultValue{"__ERROR_" + err.Error(), true} } // the first field defines the enum type, the second is the first value if len(fields) < 2 { return genDefaultValue{"__ERROR_" + fmt.Errorf("enum %v has no fields", namespace+"."+name).Error(), true} } return genDefaultValue{enumName(elementTypeDef.TypeName, fields[1].Name), false} } else if elementTypeDef.IsStruct() { return genDefaultValue{elementTypeDef.TypeName + "{}", false} } return genDefaultValue{"nil", true} case types.ELEMENT_TYPE_VAR: return genDefaultValue{"nil", true} case types.ELEMENT_TYPE_OBJECT: return genDefaultValue{"nil", true} default: return genDefaultValue{"__ERROR_" + fmt.Errorf("unsupported element type: %v", e.Type.Kind).Error(), true} } } func (g *generator) Signature(typeDef *winmd.TypeDef) (string, error) { // Signature generation defined in // https://docs.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types // type_signature => (only relevant types included) // - interface_signature // - delegate_signature // - interface_group_signature // - runtime_class_signature // - struct_signature // - enum_signature // # these instance signatures cannot be generated until instantiated // - pinterface_instance_signature // - pdelegate_instance_signature // // Where: // - interface_signature => guid // - delegate_signature => "delegate(" guid ")" // - interface_group_signature => "ig(" interface_group_name ";" default_interface ")" // - runtime_class_signature => "rc(" runtime_class_name ";" default_interface ")" // - struct_signature => "struct(" struct_name ";" args ")" // - enum_signature => "enum(" enum_name ";" enum_underlying_type ")" switch { case typeDef.IsInterface(): // interface_signature => guid guid, err := typeDef.GUID() if err != nil { return "", err } return fmt.Sprintf("{%s}", guid), nil case typeDef.IsEnum(): // enum_signature => "enum(" enum_name ";" enum_underlying_type ")" // the first field should be the underlying integer type of the enum. It must have the following flags: fields, err := typeDef.ResolveFieldList(typeDef.Ctx()) if err != nil { return "", err } fieldSig, err := fields[0].Signature.Reader().Field(typeDef.Ctx()) if err != nil { return "", err } enumType := primitiveTypeSignature(fieldSig.Field.Type.Kind) return fmt.Sprintf(`enum(%s;%s)`, typeDef.TypeNamespace+"."+typeDef.TypeName, enumType), nil case typeDef.IsStruct(): // struct_signature => "struct(" struct_name ";" args ")" fields, err := typeDef.ResolveFieldList(typeDef.Ctx()) if err != nil { return "", err } structArgs := []string{} for _, f := range fields { fSig, err := f.Signature.Reader().Field(typeDef.Ctx()) if err != nil { return "", err } // Struct fields must be fundamental types, enums, or other structs if fSig.Field.Type.Kind == types.ELEMENT_TYPE_VALUETYPE { // this is an struct or an enum fieldType, err := g.elementType(typeDef.Ctx(), fSig.Field) if err != nil { return "", err } t, err := g.mdStore.TypeDefByName(fieldType.namespace + "." + fieldType.name) if err != nil { return "", err } sig, err := g.Signature(t) if err != nil { return "", err } structArgs = append(structArgs, sig) } else { // Assume everything else is a fundamental type structArgs = append(structArgs, primitiveTypeSignature(fSig.Field.Type.Kind)) } } return fmt.Sprintf(`struct(%s;%s)`, typeDef.TypeNamespace+"."+typeDef.TypeName, strings.Join(structArgs, ";")), nil case typeDef.IsDelegate(): //delegate_signature => "delegate(" guid ")" guid, err := typeDef.GUID() if err != nil { return "", err } return fmt.Sprintf(`delegate({%s})`, guid), nil case typeDef.IsRuntimeClass(): // Static only classes carry the abstract flag. // These cannot be instantiated so no signature needed. if typeDef.Flags.Abstract() { return "", nil } // runtime_class_signature => "rc(" runtime_class_name ";" default_interface ")" // Runtime classes must specify the DefaultAttribute on exactly one of their InterfaceImpl rows. defaultInterface, err := typeDef.GetAttributeWithType(winmd.AttributeTypeDefaultAttribute) if err != nil { // Some classes (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher) do not // define a runtime class. I'm not sure if this is an error in the IDL or the documentation. // But we are not going to fail here. Just default to the first implemented interface ifs, ifserr := typeDef.GetImplementedInterfaces() if ifserr != nil { return "", err } if len(ifs) == 0 { return "", err } defaultInterface = []byte(ifs[0].Namespace + "." + ifs[0].Name) } td, err := g.mdStore.TypeDefByName(string(defaultInterface)) if err != nil { return "", err } defaultInterfaceSignature, err := g.Signature(td) if err != nil { return "", err } return fmt.Sprintf(`rc(%s;%s)`, typeDef.TypeNamespace+"."+typeDef.TypeName, defaultInterfaceSignature), nil default: return "", fmt.Errorf("unsupported type: %v", typeDef.TypeName) } } func primitiveTypeSignature(kind types.ElementTypeKind) string { switch kind { // Fundamental types case types.ELEMENT_TYPE_U1: return winrt.SignatureUInt8 case types.ELEMENT_TYPE_U2: return winrt.SignatureUInt16 case types.ELEMENT_TYPE_U4: return winrt.SignatureUInt32 case types.ELEMENT_TYPE_U8: return winrt.SignatureUInt64 case types.ELEMENT_TYPE_I1: return winrt.SignatureInt8 case types.ELEMENT_TYPE_I2: return winrt.SignatureInt16 case types.ELEMENT_TYPE_I4: return winrt.SignatureInt32 case types.ELEMENT_TYPE_I8: return winrt.SignatureInt64 case types.ELEMENT_TYPE_R4: return winrt.SignatureFloat32 case types.ELEMENT_TYPE_R8: return winrt.SignatureFloat64 case types.ELEMENT_TYPE_BOOLEAN: return winrt.SignatureBool case types.ELEMENT_TYPE_CHAR: return winrt.SignatureChar case types.ELEMENT_TYPE_STRING: return winrt.SignatureString } return "" }