Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSD Schema to COM interfaces

Tags:

c++

c#

com

xsd

vb6

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++?

like image 728
Aurora Avatar asked Apr 14 '26 14:04

Aurora


2 Answers

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.

like image 195
Hans Passant Avatar answered Apr 16 '26 05:04

Hans Passant


You can "fix" the xsd.exe tool to make it generate what you need. Basically, the xsd.exe is a relatively small tool which is based on CodeDom. See more here: writing your own xsd.exe

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

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 is what you get as an output:

/// <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;
};
like image 28
Nikolay Avatar answered Apr 16 '26 04:04

Nikolay



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!