XSD schema for COM interfaces

I need to support a legacy Visual Basic 6.0 client that needs to parse XML files. They are described by a rather large and complex XSD. To make the parsing process easier, I created the C # classes using the xsd.exe Windows SDK tool, added them to the C # library project, and set the Make Assembly COM Visible attribute. Unfortunately, the resulting type library is irrelevant as it simply provides empty interfaces for all complex types.

To illustrate this behavior, consider the following XSD schema:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:customers" xmlns:c="urn:customers">
  <xsd:element name="catalog" type="c:CatalogData"/>   
    <xsd:complexType name="AddressData">
        <xsd:sequence>
            <xsd:element name="no" type="xsd:integer"/>
            <xsd:element name="road" type="xsd:string"/>
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="CustomerData">
      <xsd:sequence>
        <xsd:element name="name" type="xsd:string"/>
        <xsd:element name="address" type="c:AddressData"/>
        <xsd:element name="order_date" type="xsd:date"/>
      </xsd:sequence>
      <xsd:attribute name="id" type="xsd:string"/>
    </xsd:complexType>
    <xsd:complexType name="CatalogData">
        <xsd:sequence>
            <xsd:element name="customer" type="c:CustomerData" minOccurs="0" maxOccurs="unbounded"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

      

The xsd tool creates the following source file:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.34209
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System.Xml.Serialization;

// 
// This source code was auto-generated by xsd, Version=4.0.30319.33440.
// 


/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:customers")]
[System.Xml.Serialization.XmlRootAttribute("catalog", Namespace="urn:customers", IsNullable=false)]
public partial class CatalogData {

    private CustomerData[] customerField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("customer", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public CustomerData[] customer {
        get {
            return this.customerField;
        }
        set {
            this.customerField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:customers")]
public partial class CustomerData {

    private string nameField;

    private AddressData addressField;

    private System.DateTime order_dateField;

    private string idField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string name {
        get {
            return this.nameField;
        }
        set {
            this.nameField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public AddressData address {
        get {
            return this.addressField;
        }
        set {
            this.addressField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="date")]
    public System.DateTime order_date {
        get {
            return this.order_dateField;
        }
        set {
            this.order_dateField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string id {
        get {
            return this.idField;
        }
        set {
            this.idField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:customers")]
public partial class AddressData {

    private string noField;

    private string roadField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="integer")]
    public string no {
        get {
            return this.noField;
        }
        set {
            this.noField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string road {
        get {
            return this.roadField;
        }
        set {
            this.roadField = value;
        }
    }
}

      

The generated type library looks like this:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: xsd.tlb

[
]
library xsd
{

    importlib("mscorlib.tlb");

    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _CatalogData;
    interface _CustomerData;
    interface _AddressData;

    [      
    ]
    coclass CatalogData {
        [default] interface _CatalogData;
        interface _Object;
    };

    [      
    ]
    coclass CustomerData {
        [default] interface _CustomerData;
        interface _Object;
    };

    [      
    ]
    coclass AddressData {
        [default] interface _AddressData;
        interface _Object;
    };

    [
    ]
    interface _CatalogData : IDispatch {
    };

    [
    ]
    interface _CustomerData : IDispatch {
    };

    [
    ]
    interface _AddressData : IDispatch {
    };
};

      

I am aware that I can create the required COM interfaces manually to expose all attached properties. However, due to the complex XSD schema, the generated C # class file is over 3000 lines in length, and would forever require creating an interface for each partial class.

Is there an alternative that will speed up the process? Or does anyone know of another tool that can generate COM interfaces / classes from an XSD schema, preferably via ATL or C ++?

+3


source to share


2 answers


You probably used the Project> Properties> Application> Assembly Info button and checked the Make COM Assembly Visible option. A very quick way to make all public classes with a default constructor in an assembly visible to COM client applications. This uses the default for the [ClassInterface] attribute as it does not explicitly apply explicitly to classes, it is ClassInterfaceType.AutoDispatch

.

This is a very safe setting and helps the client code to be a little more resilient to changes in public classes. Run-time errors that you get when classes change but the client application is not recompiled are easier to interpret. Early binding has many more annoying failure modes, including using a completely wrong property or a client application that depends on an AccessViolation exception.

Given that you post data tends to change frequently, this isn't exactly a bad idea.



But not what you are asking for. Changing the default [ClassInterface] is easy. Open Properties Source> AssemblyInfo.cs and make it like this:

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]
[assembly: ClassInterface(ClassInterfaceType.AutoDual)]

      

The last line has been added. Rebuild your project and you will now see that the interfaces are no longer empty and the autocomplete works in VB6 environment.

+2


source


You can "fix" the xsd.exe tool to make it generate what you want. Basically, xsd.exe is a relatively small tool based on CodeDom. More details here: Writing your own xsd.exe

Here is the starting point (full source) of a "custom xsd.exe" (in addition to classes, it also creates COM interfaces and interop)

using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.CodeDom;
using System.CodeDom.Compiler;

using Microsoft.CSharp;

namespace ConsoleApplication9
{
    class Program
    {
        static void Main(string[] args)
        {
            // identify the path to the xsd
            const string xsdFileName = @"schema.xsd";
            var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var xsdPath = Path.Combine(path, xsdFileName);

            // load the xsd
            XmlSchema xsd;
            using (var stream = new FileStream(xsdPath, FileMode.Open, FileAccess.Read))
            {
                xsd = XmlSchema.Read(stream, null);
            }

            var xsds = new XmlSchemas();
            xsds.Add(xsd);
            xsds.Compile(null, true);
            var schemaImporter = new XmlSchemaImporter(xsds);

            // create the codedom
            var codeNamespace = new CodeNamespace("Generated");
            var codeExporter = new XmlCodeExporter(codeNamespace);

            var maps = new List<XmlTypeMapping>();
            foreach (XmlSchemaType schemaType in xsd.SchemaTypes.Values)
            {
                maps.Add(schemaImporter.ImportSchemaType(schemaType.QualifiedName));
            }
            foreach (XmlSchemaElement schemaElement in xsd.Elements.Values)
            {
                maps.Add(schemaImporter.ImportTypeMapping(schemaElement.QualifiedName));
            }
            foreach (var map in maps)
            {
                codeExporter.ExportTypeMapping(map);
            }

            PostProcess(codeNamespace);

            // Check for invalid characters in identifiers
            CodeGenerator.ValidateIdentifiers(codeNamespace);

            // output the C# code
            var codeProvider = new CSharpCodeProvider();
            using (var writer = new StringWriter())
            {
                codeProvider.GenerateCodeFromNamespace(codeNamespace, writer, new CodeGeneratorOptions());
                Console.WriteLine(writer.GetStringBuilder().ToString());
            }
        }
        // For each class declaration, 
        // adds interface declaration, makes that class inherit from that interface,
        // and adds COM interop stuff
        private static void PostProcess(CodeNamespace codeNamespace)
        {
            var codeTypeDeclarations = new List<CodeTypeDeclaration>();
            foreach (CodeTypeDeclaration codeType in codeNamespace.Types)
            {
                // mark class as com visible
                AddClassInterfaceNone(codeType.CustomAttributes);
                AddComVisibleTrue(codeType.CustomAttributes);

                // create new interface
                var itf = new CodeTypeDeclaration
                {
                    Name = string.Format("I{0}", codeType.Name),
                    IsInterface = true
                };

                AddComVisibleTrue(itf.CustomAttributes);

                // make base type inherit from this interface
                codeType.BaseTypes.Add(new CodeTypeReference(itf.Name));

                // clone interface members
                foreach (CodeTypeMember m in codeType.Members)
                {
                    var itfM = CloneMember(m);
                    itfM.CustomAttributes.Clear();
                    itf.Members.Add(itfM);
                }

                codeTypeDeclarations.Add(itf);
            }

            codeNamespace.Types.AddRange(codeTypeDeclarations.ToArray());
        }

        private static CodeTypeMember CloneMember(CodeTypeMember m)
        {
            var ms = new MemoryStream();
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, m);
            ms.Seek(0, SeekOrigin.Begin);
            return formatter.Deserialize(ms) as CodeTypeMember;
        }

        private static void AddComVisibleTrue(CodeAttributeDeclarationCollection attrs)
        {
            attrs.Add(new CodeAttributeDeclaration(
                new CodeTypeReference("System.Runtime.InteropServices.ComVisibleAttribute"),
                new[] { new CodeAttributeArgument(new CodePrimitiveExpression(true)) }));
        }

        private static void AddClassInterfaceNone(CodeAttributeDeclarationCollection attrs)
        {
            attrs.Add(new CodeAttributeDeclaration(
                new CodeTypeReference("System.Runtime.InteropServices.ClassInterface"),
                new[] { new CodeAttributeArgument(new CodeFieldReferenceExpression(
                new CodeTypeReferenceExpression("System.Runtime.InteropServices.ClassInterfaceType"), 
                ClassInterfaceType.None.ToString()))
                    }));
        }
    }
}

      

Here's what you get as a result:



/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("ConsoleApplication9", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:customers")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="urn:customers", IsNullable=true)]
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.None)]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public partial class AddressData : IAddressData {

    private string noField;

    private string roadField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="integer")]
    public string no {
        get {
            return this.noField;
        }
        set {
            this.noField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string road {
        get {
            return this.roadField;
        }
        set {
            this.roadField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("ConsoleApplication9", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:customers")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="urn:customers", IsNullable=true)]
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.None)]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public partial class CustomerData : ICustomerData {

    private string nameField;

    private AddressData addressField;

    private System.DateTime order_dateField;

    private string idField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string name {
        get {
            return this.nameField;
        }
        set {
            this.nameField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public AddressData address {
        get {
            return this.addressField;
        }
        set {
            this.addressField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="date")]
    public System.DateTime order_date {
        get {
            return this.order_dateField;
        }
        set {
            this.order_dateField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string id {
        get {
            return this.idField;
        }
        set {
            this.idField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("ConsoleApplication9", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:customers")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="urn:customers", IsNullable=true)]
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.None)]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public partial class CatalogData : ICatalogData {

    private CustomerData[] customerField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("customer", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public CustomerData[] customer {
        get {
            return this.customerField;
        }
        set {
            this.customerField = value;
        }
    }
}

[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public interface IAddressData {



    /// <remarks/>
    string no {
        get;
        set;
    }

    /// <remarks/>
    string road {
        get;
        set;
    }
}

[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public interface ICustomerData {





    /// <remarks/>
    string name {
        get;
        set;
    }

    /// <remarks/>
    AddressData address {
        get;
        set;
    }

    /// <remarks/>
    System.DateTime order_date {
        get;
        set;
    }

    /// <remarks/>
    string id {
        get;
        set;
    }
}

[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public interface ICatalogData {


    /// <remarks/>
    CustomerData[] customer {
        get;
        set;
    }
}

      

And here is the TLB file:

// TLib :     // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");

// Forward declare all types defined in this typelib
interface IAddressData;
interface ICustomerData;
interface ICatalogData;

[
  odl,
  uuid(9C2EF5B0-59BA-3CBE-874A-DA690A595F26),
  version(1.0),
  dual,
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Generated.IAddressData")    

]
interface IAddressData : IDispatch {
    [id(0x60020000), propget]
    HRESULT no([out, retval] BSTR* pRetVal);
    [id(0x60020000), propput]
    HRESULT no([in] BSTR pRetVal);
    [id(0x60020002), propget]
    HRESULT road([out, retval] BSTR* pRetVal);
    [id(0x60020002), propput]
    HRESULT road([in] BSTR pRetVal);
};

[
  uuid(26736B67-A277-3E81-AAF1-653A936F209E),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Generated.AddressData")
]
coclass AddressData {
    interface _Object;
    [default] interface IAddressData;
};

[
  odl,
  uuid(8CCFC141-05C0-3A11-BD3B-C3279AB9B3C1),
  version(1.0),
  dual,
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Generated.ICustomerData")    

]
interface ICustomerData : IDispatch {
    [id(0x60020000), propget]
    HRESULT name([out, retval] BSTR* pRetVal);
    [id(0x60020000), propput]
    HRESULT name([in] BSTR pRetVal);
    [id(0x60020002), propget]
    HRESULT address([out, retval] IAddressData** pRetVal);
    [id(0x60020002), propputref]
    HRESULT address([in] IAddressData* pRetVal);
    [id(0x60020004), propget]
    HRESULT order_date([out, retval] DATE* pRetVal);
    [id(0x60020004), propput]
    HRESULT order_date([in] DATE pRetVal);
    [id(0x60020006), propget]
    HRESULT id([out, retval] BSTR* pRetVal);
    [id(0x60020006), propput]
    HRESULT id([in] BSTR pRetVal);
};

[
  uuid(9B02E545-4EF6-355E-8CD3-7EC5D2780648),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Generated.CustomerData")
]
coclass CustomerData {
    interface _Object;
    [default] interface ICustomerData;
};

[
  odl,
  uuid(8DC4A69C-31FA-311A-8668-9D646AFF1F10),
  version(1.0),
  dual,
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Generated.ICatalogData")    

]
interface ICatalogData : IDispatch {
    [id(0x60020000), propget]
    HRESULT customer([out, retval] SAFEARRAY(ICustomerData*)* pRetVal);
    [id(0x60020000), propput]
    HRESULT customer([in] SAFEARRAY(ICustomerData*) pRetVal);
};

[
  uuid(5423877C-2618-3D59-8F3A-E1443AC362CA),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Generated.CatalogData")
]
coclass CatalogData {
    interface _Object;
    [default] interface ICatalogData;
};

      

+1


source







All Articles