Search code examples
c#c++xsdcomvb6

XSD Schema to COM interfaces


I have to support a legacy Visual Basic 6.0 client, which needs to parse XML files. These are described by a fairly large and complex XSD schema. In order to ease the parsing process, I created C# classes via the Windows SDK xsd.exe tool, added these to a C# library project, and set the "Make assembly COM-Visible" attribute. Unfortunately, the resulting type library is of no value, since it merely exposes 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 could create the required COM interfaces manually in order to expose all nested properties. However, due to the complex XSD schema, the generated C# class file is over 3000 lines long and it would take me forever to create an interface for every partial class.

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


Solution

  • You probably used the Project > Properties > Application > Assembly Information button and ticked the "Make assembly COM visible" option. A very quick way to make all public classes with a default constructor in an assembly visible to COM client apps. That uses the default value for the [ClassInterface] attribute, since it isn't otherwise explicitly applied to the classes, it is ClassInterfaceType.AutoDispatch.

    Which is a very safe setting, it helps the client code to be a bit more resilient to changes in the exposed classes. The runtime errors you'll get when the classes change but the client app isn't recompiled are friendlier to interpret. Early binding has much nastier failure modes, including using the completely wrong property or the client app falling over on an AccessViolation exception.

    Considering you are exposing data, apt to change frequently, that's not exactly a bad idea.

    But not what you are asking for. Changing the default [ClassInterface] is very simple. Open the Properties > AssemblyInfo.cs source code file and make it look 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 was added. Rebuild your project and you'll now see the interfaces are no longer empty and auto-completion works in the VB6 IDE.