ArtinSoft's Blogs

Software Migration Experts
Welcome to ArtinSoft's Blogs Sign in | Join | Help
in Search

Mauricio Rojas Blog

  • Interop: BinaryCompatibilty for VB6 Migrations

    In VB6 when you have an ActiveX Library it was very important to use
    the BinaryCompatibility setting to make sure that your applications did not break after a change.

    So let’s first introduce what is binary compatibility and how to accomplish that in .NET.

    Binary Compatibility allows to make changes to your components or COM classes without recompiling
    every application you've made that uses the component.
    And why do you need it. Why compatibility breaks.
    On lets see.

    An ActiveX Control or DLL expose Public interfaces.
    Those interfaces have all of the properties, methods, events, etc. that you've marked as Public.
    In other words, everything you've added that shows in Intellisense while working outside of your component.

    Now let's say you have create a class, with two Methods Method1 and Method2

    When you compile, VB generates all the COM infraestructure you need for your component.
    It defines a CoClass and an interface and an entry for each method.

    For a vb class with two methods:

    Sub Method1()
    
    End Sub
    
    Sub Method2()
    
    End Sub

    It will produce a typelib like:

    // Generated .IDL file (by the OLE/COM Object Viewer)
    // 
    // typelib filename: <could not determine filename>
    [
      uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
      version(1.0)
    ]
    library Project1
    {
        // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
        importlib("stdole2.tlb");
    
        // Forward declare all types defined in this typelib
        interface _Class1;
    
        [
          odl,
          uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B),
          version(1.0),
          hidden,
          dual,
          nonextensible,
          oleautomation
        ]
        interface _Class1 : IDispatch {
            [id(0x60030000)]
            HRESULT Method1();
            [id(0x60030001)]
            HRESULT Method2();
        };
    
        [
          uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
          version(1.0)
        ]
        coclass Class1 {
            [default] interface _Class1;
        };
    };

    As you can see in the typelib there are IDs associated to each coclass, interface and
    methods. Those IDs are the ones use when you generate the .exe file for your application.
    Now if you modify your Class to:

     

    Sub Method3()
    
    End Sub
    
    Sub Method4()
    
    End Sub
    
    Sub Method1()
    
    End Sub
    
    Sub Method2()
    
    End Sub

    and you use No Compatibility the typelib after your changes will be:

    // Generated .IDL file (by the OLE/COM Object Viewer)
    // 
    // typelib filename: <could not determine filename>
    
    [
      uuid(FE5C56C2-E03A-4DC0-994D-B68543C72A46),
      version(1.0)
    ]
    library Project1
    {
        // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
        importlib("stdole2.tlb");
    
        // Forward declare all types defined in this typelib
        interface _Class1;
    
        [
          odl,
          uuid(A3032E1E-52FE-42E0-98FF-84A9DD4FD8C3),
          version(1.0),
          hidden,
          dual,
          nonextensible,
          oleautomation
        ]
        interface _Class1 : IDispatch {
            [id(0x60030000)]
            HRESULT Method3();
            [id(0x60030001)]
            HRESULT Method4();
            [id(0x60030002)]
            HRESULT Method1();
            [id(0x60030003)]
            HRESULT Method2();
        };
    
        [
          uuid(72721504-CC56-4BB9-9447-C7193FE8C02D),
          version(1.0)
        ]
        coclass Class1 {
            [default] interface _Class1;
        };
    };

    As you can see, now the ids for the methods, CoClass are different, so your applications will return errors like: Error 430 (Automation error, the component dies horribly) or Error 429 (can't create the object at all)

    But if you instead used BinaryCompatibility then the typelib for your class will be:

    // Generated .IDL file (by the OLE/COM Object Viewer)
    // 
    // typelib filename: <could not determine filename>
    [
      uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
      version(1.1)
    ]
    library Project1
    {
        // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
        importlib("stdole2.tlb");
    
        // Forward declare all types defined in this typelib
        interface _Class1;
        [
          odl,
          uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF),
          version(1.1),
          hidden,
          dual,
          nonextensible,
          oleautomation
        ]
        interface _Class1 : IDispatch {
            [id(0x60030002)]
            HRESULT Method3();
            [id(0x60030003)]
            HRESULT Method4();
            [id(0x60030000)]
            HRESULT Method1();
            [id(0x60030001)]
            HRESULT Method2();
        };
    
        [
          uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
          version(1.1)
        ]
        coclass Class1 {
            [default] interface _Class1;
        };
    
        typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
        _Class1 Class1___v0;
    };

    If you compare now the two typelibs you can see the Method1 and Method2 keep the same ids.

    For each version a typedef is generated that will point to the last version. For example adding a Method5 will add new entry like:


        typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
        _Class1 Class1___v0;
    
        typedef [uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF), version(1.1), public]
        _Class1 Class1___v1;

    Well that is what binary compatibility does. Now how to achieve binary compatibility in .NET

    Binary Compatibility in .NET

    Achieving binary compatibility in .NET is really easy. You just need to give more information to
    make explicit how your typelib information will be. I will follow an approach as the one I already explained in this post:
    http://blogs.artinsoft.net/mrojas/archive/2010/06/23/exposing-c-classes-thru-interop.aspx

    Lets take our previous example:

    using System;
    using System.Runtime.InteropServices;
    
    namespace InteropExamples
    {
        public class Class1
        {
            public void Method3()
            {
            }
            public void Method4()
            {
            }
            public void Method1()
            {
            }
            public void Method2()
            {
            }
            public void Method5()
            {
            }
        }
    }

    In previous posts I had recommended using partial classes and using interfaces to explicitly specify what you what to be seen in COM. This means you start up with something like:

      public partial class Class1
        {
            public void Method3()
            {
            }
            public void Method4()
            {
            }
            public void Method1()
            {
            }
            public void Method2()
            {
            }
        }
    
        [ComVisible(true)]
        public interface _Class1
        {
            void Method3();
            void Method4();
            void Method1();
            void Method2();
    
        }
        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.None)]
        [ComDefaultInterface(typeof(_Class1))]
        partial class Class1 : _Class1
        {
            #region _Class1 Members
    
            void _Class1.Method3()
            {
                Method3();
            }
    
            void _Class1.Method4()
            {
                Method4();
            }
    
            void _Class1.Method1()
            {
                Method1();
            }
    
            void _Class1.Method2()
            {
                Method2();
            }
    
            #endregion
        }

    Now to make this code binary compatible then you have to make sure that the tlb file generated for your class is almost identical to that generated before. To acomplish that we must make sure that we your methods, interfaces and classes have the same guids and ids. Lets see how:

    using System;
    using System.Runtime.InteropServices;
    
    namespace InteropExamples
    {
        public partial class Class1
        {
    
            public void Method3()
            {
                System.Windows.Forms.MessageBox.Show("3 N");
            }
    
            public void Method4()
            {
                System.Windows.Forms.MessageBox.Show("4 N");
            }
    
            public void Method5()
            {
                System.Windows.Forms.MessageBox.Show("5 N");
            }
    
    
            public void Method1()
            {
                System.Windows.Forms.MessageBox.Show("1 N");
            }
    
            public void Method2()
            {
                System.Windows.Forms.MessageBox.Show("2 N");
            }
        }
    
        [ComVisible(true)] //This to make the interface Visible for COM
        [TypeLibType((TypeLibTypeFlags)((short)TypeLibTypeFlags.FHidden |
            (short)TypeLibTypeFlags.FDual |
            (short)TypeLibTypeFlags.FNonExtensible |
            (short)TypeLibTypeFlags.FOleAutomation))] //This to use the same flags as in previous tlb
        [Guid("9BAFD76D-8E6B-439C-8B6D-37260BFA3317")] //This is to make the class have the guid
        public interface _Class1
        {
            [DispId(0x60030000)]
            void Method1();
            [DispId(0x60030001)]
            void Method2();
            [DispId(0x60030002)]
            void Method3();
            [DispId(0x60030003)]
            void Method4();
            [DispId(0x60030004)]
            void Method5();
    
    
        }
    
        [ComVisible(true)] //This to make the class Visible for COM
        [ClassInterface(ClassInterfaceType.None)] //This is to make sure that we have control on interface generation
        [ComDefaultInterface(typeof(_Class1))] //To set default interface
        [ProgId("Project1.Class1")] //To set ProgId 
        [Guid("C71C7AB0-552A-4D5D-A9FB-AF33830A697E")] //Maintain same Guid.
        partial class Class1 : _Class1, Class1___v0, Class1___v1
        {
            #region _Class1 Members
    
            void _Class1.Method3()
            {
                Method3();
            }
    
            void _Class1.Method4()
            {
                Method4();
            }
    
            void _Class1.Method1()
            {
                Method1();
            }
    
            void _Class1.Method2()
            {
                Method2();
            }
    
            #endregion
    
    
            #region Class1___v0 Members
    
            void Class1___v0.Method1()
            {
                Method1();
            }
    
            void Class1___v0.Method2()
            {
                Method2();
            }
    
            void Class1___v0.Method3()
            {
                Method3();
            }
    
            void Class1___v0.Method4()
            {
                Method4();
            }
    
            void Class1___v0.Method5()
            {
                Method5();
            }
    
            #endregion
    
            #region Class1___v1 Members
    
            void Class1___v1.Method1()
            {
                Method1();
            }
    
            void Class1___v1.Method2()
            {
                Method2();
            }
    
            void Class1___v1.Method3()
            {
                Method3();
            }
    
            void Class1___v1.Method4()
            {
                Method4();
            }
    
            void Class1___v1.Method5()
            {
                Method5();
            }
    
            #endregion
        }
    
        //This is to keep compatibility with old versions
        //we cannot generate a typedef so we will need to add all of the versions
        //for BinaryCompatibility
        [ComVisible(true)]
        [Guid("6B86684C-B3DD-4680-BF95-8DEE2C17AF5B")]
        [TypeLibType(TypeLibTypeFlags.FHidden)]
        public interface Class1___v0
        {
            [DispId(0x60030000)]
            void Method1();
            [DispId(0x60030001)]
            void Method2();
            [DispId(0x60030002)]
            void Method3();
            [DispId(0x60030003)]
            void Method4();
            [DispId(0x60030004)]
            void Method5();
        }
    
        //This is to keep compatibility with old versions
        //we cannot generate a typedef so we will need to add all of the versions
        //for BinaryCompatibility
        [ComVisible(true)]
        [Guid("4A7A3317-BF13-443E-9DB0-2C5EA21F00CA")]
        [TypeLibType(TypeLibTypeFlags.FHidden)]
        public interface Class1___v1
        {
            [DispId(0x60030000)]
            void Method1();
            [DispId(0x60030001)]
            void Method2();
            [DispId(0x60030002)]
            void Method3();
            [DispId(0x60030003)]
            void Method4();
            [DispId(0x60030004)]
            void Method5();
        }
    
    }

    Sadly in .NET you cannot use Interface Inheritance in COM. If there is interface inheritance YOU HAVE TO IMPLEMENT each interface. In the case of code that comes from VB6. VB6 just uses typedefs, so you really don’t know which methods belong to each version. So in the end all versions have all methods.

    The other alternative to this method, is just to implement last version. And after generating the tlb, decompile it to an .IDL file add the typedefs and recompiled it. I explained something similar in this post:http://blogs.artinsoft.net/mrojas/archive/2010/05/17/interop-remove-prefix-from-c-enums-for-com.aspx

    Ok. I hope this helps you to have an more clear idea of what Binary Compatibility is and how to do it in .NET. I am attaching some sample code. It show an ActiveX library that uses BinaryCompatibility and three version on an aplications that uses the different versions. And also a .NET class library that is equivalent to the VB6 one. HERE

    Enjoy.

  • Exposing C# Classes thru Interop

    Either if you migrate your application from VB6 to C# or if you develop a new application in C# something you end up with cases where you need to use your classes in legacy apps. Some of them could have been written in VB6 or could even be VBA macros in Excel applications.

    Exposing your .NET classes can be sometimes very easy (you can think is just a matter of putting a ComVisible tag) but in other occasions is not that simple. Specially if your legacy application is using a lot of Late Bound calls like in VBA, so you must make sure that the COM information that you are exposing for your class is exactly what you really want and need.

    OK. So I will provide some guidelines or some steps you should follow to provide a consistent COM interface for your .NET Code.

    1. First you have to add the [ComVisible(true)]  attribute. Don’t think that’s all. Even if in some cases that is enough is better if you take an strict control of want is being generated for your class. Ok Let’s use the following class as an example:

    using System;
    using System.Runtime.InteropServices;
    
    namespace InteropExamples
    {
        [ComVisible(true)]
        public class MyVerySimpleClass 
        {
            public Class2 CreateANewClass()
            {  return new Class2()     }

            public int GetMyLuckyNumber() { return 15; }
        }
    public class Class2 { 
      }
    }

    // Generated .IDL file (by the OLE/COM Object Viewer)
    //
    // typelib filename: <could not determine filename>
    [
      uuid(370E4AD4-073B-4984-8C7D-5ED027F7B1CA),
      version(1.0)
    ]
    library ClassLibrary1
    {
        // 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 _MyVerySimpleClass;

        [
          uuid(E03CCE68-2D55-3576-9DB6-019AAA667A5D),
          version(1.0),
            custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")
        ]
        coclass MyVerySimpleClass {
            [default] interface _MyVerySimpleClass;
            interface _Object;
        };

        [
          odl,
          uuid(D18BEEE1-4425-3AC7-891E-807EC2283731),
          hidden,
          dual,
          oleautomation,
            custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")   

        ]
        interface _MyVerySimpleClass : IDispatch {
        };
    };

    In this case your class will be expose using all defaults. That is, a progId that will be the <AssemblyName>.ClassName an interface _<ClassName> is generated and the class is exposed only for IDispatch, which would not provide class information if you add the tlb reference to a VB6 or VBA project.

    And if you run this code in VB6 you will have a problem like type mismatch when you try to use the method x.CreateAClass because it is returning an object that is not exposed thru COM.

    Private Sub Command1_Click()
        Dim x As Object
        Set x = CreateObject("InteropExamples.MyVerySimpleClass")
        MsgBox x.GetMyLuckyNumber
        MsgBox x.CreateAClass
    End Sub

    So my recommendation is to make explicit what you want to expose. Maybe you only need some of the methods to be exposed. Well that is step two.

    2. Define a public, ComVisible(true) interface that will define the methods that you want to be exposed thru COM. Sometimes it is better to implement the interface explicitly. I even recommend using partial classes so you isolate the COM stuff from your normal class. If you class is very simple you can leave all COM stuff there.

        //It is better to have an interface, because
        //you are completely sure what you are exposing or not
        [ComVisible(true)]
        public interface _MyVerySimpleClass
        {
            int GetMyLuckyNumber();
        }

    3. (Recommedation) This is not an obligatory step but I recommend using partial classes.

        //Using partial classes allow you to separate all the
        //COM plumbing and leave your .NET implementation simple
        public partial class MyVerySimpleClass 
        {
            public Class2 CreateAClass()
            {
                return new Class2();
            }
    
            public int GetMyLuckyNumber() { return 15; }
        }

    3. Make sure your partial class has the following attributes:

    [ComVisible(true)] <—This is obvious because you want to use your class in COM

    [ClassInterface(ClassInterfaceType.None)] <—This is because your want to take charge or what will be generated in your Typelib (tlb)

    [ComDefaultInterface(typeof(_MyVerySimpleClass))] <—This is to indicate the interface that holds your COM visible methods.

    [ProgId("InteropExamples.MyVerySimpleClass")] <—To establish which will be the progId not have a generated one
    [Guid("{029D468C-8BE6-498f-8A57-3B4B0306BA41}")] <—this is important specially if you are trying to accomplish binary compatibility

    Optionally add this attribute [IDispatchImpl(IDispatchImplType.CompatibleImpl)] this is currently marked as an obsolete attribute but it still works and I have found scenarios, specially in some VBA applications where you need this attribute in order to make some late bound calls.

    4. And Explicitly implement the interface methods. This is important because some of the return values or arguments might need convertions. For example what can you do if your method returns a DataSet and your Excel VBA script is expecting something like a Recordset (more on this on other posts).

    So now you will have a class like:

        //Using partial classes allow you to separate all the
        //COM plumbing and leave your .NET implementation simple
        public partial class MyVerySimpleClass
        {
            public Class2 CreateAClass()
            {
                return new Class2();
            }
    
            public int GetMyLuckyNumber() { return 15; }
        }
    
        //It is better to have an interface, because
        //you are completely sure what you are exposing or not
        [ComVisible(true)]
        public interface _MyVerySimpleClass
        {
            int GetMyLuckyNumber();
        }
    
        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.None)] //This is to make sure that no automatic generation of COM methods is done
        [ComDefaultInterface(typeof(_MyVerySimpleClass))] //This to explicitly establish which is the default interface
        [ProgId("InteropExamples.MyVerySimpleClass")]
        [Guid("{029D468C-8BE6-498f-8A57-3B4B0306BA41}")]
        [IDispatchImpl(IDispatchImplType.CompatibleImpl)]
        partial class MyVerySimpleClass : _MyVerySimpleClass
        {
        
            #region _MyVerySimpleClass Members
            //Explicit implementation is better because it avoids messing your .NET
            //class specification. Sometimes when you expose thru COM you can have problem with
            //methods overloads. For example you have to have the same method name but differente 
            //return type. Or you have a collition with an existing member.
            int _MyVerySimpleClass.GetMyLuckyNumber()
            {
                return GetMyLuckyNumber();
            }
    
            #endregion
        }

    And your TLB is now explicit and exposes ONLY what you really really want.

    // Generated .IDL file (by the OLE/COM Object Viewer)
    //
    // typelib filename: <could not determine filename>

    [
      uuid(370E4AD4-073B-4984-8C7D-5ED027F7B1CA),
      version(1.0)
    ]

    library ClassLibrary1
    {
       // 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 _MyVerySimpleClass;

        [
          odl,
          uuid(80D00C45-EE10-3D65-A5FF-42AB7D8F8A71),
          version(1.0),
          dual,
          oleautomation,
            custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples._MyVerySimpleClass")   

        ]
        interface _MyVerySimpleClass : IDispatch {
            [id(0x60020000)]
            HRESULT GetMyLuckyNumber([out, retval] long* pRetVal);
        };

        [
          uuid(029D468C-8BE6-498F-8A57-3B4B0306BA41),
          version(1.0),
            custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "InteropExamples.MyVerySimpleClass")
        ]

        coclass MyVerySimpleClass {
            interface _Object;
            [default] interface _MyVerySimpleClass;
        };
    };

    For more info about BinaryCompatibility see my other posts on Interop.

  • Repair Crashing Open Document in Sharepoint

    I love sharepoint and it is very nice and cool. We use it a lot to share information with our clients. But every know and then when I try to open or check out a document IE just crashes.

    So I got tired of it and decided to put an end to that annoyance. I pointed my browser to google and found the: http://www.groovypost.com/howto/microsoft/ie/fix-ie-crash-when-opening-documents-in-sharepoint/ This post recommends running the Microsoft Office Diagnostics Tools and it really works.

    If you cannot find your Diagnostics Tools use this page: http://office.microsoft.com/en-us/help/HA012340761033.aspx

     

     

     

     

  • Interop: Remove prefix from C# Enums for COM

    Sometimes when you do a migration, you still need to “Interop” with legacy applications.

    Normally this interop is done thru COM and .NET assemblies can be easily registered with COM.
    However the devil is in the details, and there are always subtle details that can be difficult to tackle with
    COM Interop.

    One of those subtle details is that the tlbexp tool (this is the tool that generates the .tlb for a .NET assembly)
    generates a prefix for enum elements.

    So if you have something in C# like:

    using System;
    using System.Runtime.InteropServices;
    
    namespace ClassLibrary1
    {
        [ComVisible(true)]
        public enum simpleEnum
        {
         Field1 = 1,
         Field2 = 2,
         Field3 = 3,
         Field4 = 4,
         Field5 = 5
        } ;
    
    }

    Sadly that would generate a COM Interface like:

    // Generated .IDL file (by the OLE/COM Object Viewer)
    // 
    // typelib filename: <could not determine filename>
    
    [
      uuid(D3DB73ED-08B3-4E2F-AD20-61E44E3FDF17),
      version(1.0)
    ]
    library ClassLibrary1
    {
        // TLib :     // Forward declare all types defined in this typelib
    
        typedef [uuid(9D2C80FF-C124-33D2-9991-676A18DA7CAF), version(1.0)    ,
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.simpleEnum")    
    ]
        enum {
            simpleEnum_Field1 = 1,
            simpleEnum_Field2 = 2,
            simpleEnum_Field3 = 3,
            simpleEnum_Field4 = 4,
            simpleEnum_Field5 = 5,
        } simpleEnum;
    };

    And the problem with that is that your legacy programs are expecting something like:

    enum { Field1 = 1, Field2 = 2, Field3 = 3, Field4 = 4, Field5 = 5, } simpleEnum;

    Is there a way to solve this?
    Well yes there is but it is not a nice one. You have to:

    • take the tlb that the tlbexp tool generates,
    • generate an .idl file from it
    • modify the .idl to correct its syntax
    • change the name of the enum fields
    • compile the .idl file to tlb with the midl compiler
    • use the generated tlb with the legacy application.

    Lets see an example. Suppose we have something like:

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace ClassLibrary1
    {
        [ComVisible(true)]
        
        public enum simpleEnum
        {
         Field1 = 1,
         Field2 = 2,
         Field3 = 3,
         Field4 = 4,
         Field5 = 5
        } ;
    
        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.AutoDual)] 
        public class Class1
        {
            public void foo(simpleEnum val)
            {
                switch(val)
                {
                    case simpleEnum.Field1:
                        MessageBox.Show("FieldValue1");
                        break;
                    case simpleEnum.Field2:
                        MessageBox.Show("FieldValue2");
                        break;
                    case simpleEnum.Field3:
                        MessageBox.Show("FieldValue3");
                        break;
                    case simpleEnum.Field4:
                        MessageBox.Show("FieldValue4");
                        break;
                    case simpleEnum.Field5:
                        MessageBox.Show("FieldValue5");
                        break;
                }
            }
        };
    
    }

    When you open that generated IDL with the OLE2VIEW tool:

    // Generated .IDL file (by the OLE/COM Object Viewer)
    // 
    // typelib filename: <could not determine filename>
    
    [
      uuid(D3DB73ED-08B3-4E2F-AD20-61E44E3FDF17),
      version(1.0)
    ]
    library ClassLibrary1
    {
        // 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 _Class1;
    
        typedef [uuid(4087FBAF-9633-30D1-8A64-533E37B784B6), version(1.0)    ,
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.simpleEnum")    
    ]
        enum {
            simpleEnum_Field1 = 1,
            simpleEnum_Field2 = 2,
            simpleEnum_Field3 = 3,
            simpleEnum_Field4 = 4,
            simpleEnum_Field5 = 5
        } simpleEnum;
    
        [
          uuid(0D39F056-DF63-3860-9E79-B57F6358FD4D),
          version(1.0),
            custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.Class1")
        ]
        coclass Class1 {
            [default] interface _Class1;
            interface _Object;
        };
    
        [
          odl,
          uuid(1A87868B-7CE6-3C75-B2FA-71A86F77FC7D),
          hidden,
          dual,
          nonextensible,
          oleautomation,
            custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.Class1")    
    
        ]
        interface _Class1 : IDispatch {
            [id(00000000), propget,
                custom({54FC8F55-38DE-4703-9C4E-250351302B1C}, "1")]
            HRESULT ToString([out, retval] BSTR* pRetVal);
            [id(0x60020001)]
            HRESULT Equals(
                            [in] VARIANT obj, 
                            [out, retval] VARIANT_BOOL* pRetVal);
            [id(0x60020002)]
            HRESULT GetHashCode([out, retval] long* pRetVal);
            [id(0x60020003)]
            HRESULT GetType([out, retval] _Type** pRetVal);
            [id(0x60020004)]
            HRESULT foo([in] simpleEnum val);
        };
    };
     
    When you use this tlb for example in VB6 look at the member names for the enum:
     
    image 

    If you want to be able to use your enum fields unchanged then this is the modified IDL you will have to use. The OLE2View Tool adds an extra “{“ to the IDL custom attribute and the enum name must be put after the enum keyword.

    // Generated .IDL file (by the OLE/COM Object Viewer)
    // 
    // typelib filename: <could not determine filename>
    
    [
      uuid(D3DB73ED-08B3-4E2F-AD20-61E44E3FDF17),
      version(1.0)
    ]
    library ClassLibrary1
    {
        // 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 _Class1;
    
        typedef [uuid(4087FBAF-9633-30D1-8A64-533E37B784B6), version(1.0)    ,
        custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary1.simpleEnum")    
    ]
        enum simpleEnum {
            Field1 = 1,
            Field2 = 2,
            Field3 = 3,
            Field4 = 4,
            Field5 = 5
        } simpleEnum;
    
        [
          uuid(0D39F056-DF63-3860-9E79-B57F6358FD4D),
          version(1.0),
            custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary1.Class1")
        ]
        coclass Class1 {
            [default] interface _Class1;
            interface _Object;
        };
    
        [
          odl,
          uuid(1A87868B-7CE6-3C75-B2FA-71A86F77FC7D),
          hidden,
          dual,
          nonextensible,
          oleautomation,
            custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary1.Class1")    
    
        ]
        interface _Class1 : IDispatch {
            [id(00000000), propget,
                custom(54FC8F55-38DE-4703-9C4E-250351302B1C, "1")]
            HRESULT ToString([out, retval] BSTR* pRetVal);
            [id(0x60020001)]
            HRESULT Equals(
                            [in] VARIANT obj, 
                            [out, retval] VARIANT_BOOL* pRetVal);
            [id(0x60020002)]
            HRESULT GetHashCode([out, retval] long* pRetVal);
            [id(0x60020003)]
            HRESULT GetType([out, retval] _Type** pRetVal);
            [id(0x60020004)]
            HRESULT foo([in] simpleEnum val);
        };
    };

    Save the modified .idl file as ClassLibrary1.dll and run a command like:

    midl ClassLibrary1.idl /tlb ClassLibrary1_new.tlb

    and then register the new tlb with:

    regtlib ClassLibrary1_new.tlb

    NOTE: I you cannot find the regtlib tool you can look for it at: %windir%\Microsoft.NET\Framework\v2.0.50727 it will be there with the name regtlibv12.zip. If you still cannot find it, download it from HERE

  • .HLP files in Windows Vista

    Microsoft has decided that .hlp files are not the best option for your help files.

    And you will probably receive an error message like: http://support.microsoft.com/kb/917607

    If you still want to run your .hlp help files you still can look for a WinHelp viewer for Vista from Microsoft.

    But as someone that has been in the application migration/upgrade bussiness for several year I think automatic migration is a very good option.

     

    For example take a look at the following links:

     

     

    http://www.herdsoft.com/linux/themen/hlp_to_chm.html 

    Some guidance and scripts to help in migrating your files
    http://www.helpscribble.com/vista.html Tools to automatically upgrade your .hlp files and projects

    Good luck! And If you had any other suggestions just leave a comment.

  • Which App.Config file will my ServiceComponent load?

    When a VB6 COM+ Component is migrated to a ServiceComponent,
    you might want to take advantage of the Configuration files of .NET to specify your
    connection strings and other important information.

    So where should your App.Config go.

    There is a slight diference with a ServiceComponent.
    Remember that for a ServicedComponent the hosting process is ‘dllhost.exe’.
    So your programs will look for config files in %windir%\System32, which is not a very nice solution.

    You can instead set the ‘Application Base Directory’ of the COM+ Application.

    Follow these steps:

    1) Create an application.manifest file and copy it to the directory
    that will be used as the base directory for the COM+ application. The file can be like:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"/>

     

    2) Create an app.config file and copy that file to the same :

    <?xml version="1.0" encoding="utf-8" ?> 
    <configuration>
    <appSettings>
    <add key="ConfigData" value="My Custom AppSetting!" />
    </appSettings>
    </configuration>
    3) Configure the COM+ Application:
    3.1) Open the Component Services MMC
    3.2) Find the COM+ Application
    3.3) Right Click the Application and go to Properties and Activation Tab
    3.4) Find option: ‘Application Root Directory’
    3.5) Write the path where the other two files where created.
     

    This blog post was created from an original Blog Post from HeikkiRi.

  • AutoCAD VBA Migration to VB.NET or C#

    AutoCAD 2010 will not be supporting VBA.

    Quoting

    “If you utilize VBA macros in your work environment, they will no longer work unless the VBA module is installed on your system. “
    “When you run a command that requires VBA, a message dialog box will be displayed stating that VBA is no longer installed with AutoCAD and directing you to a website where you can download the VBA module. “

    And also you can see that Autodesk states: “Autodesk is evaluating how long VBA will be supported in Autodesk products in the future. Though supported in the AutoCAD 2010-based products, it may or may not be supported in future releases. Therefore, it is strongly recommended that VB developers develop all new code using VB .NET.

    VBA does not support 64bit systems in a native way.

    But If you want some advice from the VB migration experts or help on your migration project from VBA to VB.NET or C# you can us contact Artinsoft Migration Services.

    We build the VB Upgrade Wizard that shipped with Visual Studio and have been doing VB migrations for years.

  • DataGridView does not show Horizontal scrollbar

    During a migration from a FlexGrid to a DataGridView, we encountered a situation where the HorizontalScrollBar did not show.

    I found many suggestions like setting a MinimumColWidth value for all columns, etc.

    But it wasn’t until my friend Jesus added a line like:

     

    mygrid.DockStyle = DockStyle.Fill

    that the HorizontalScrollBar appear.

    It might just be that the grid was too big for form but just for the record this is a possible solution.

  • Escape characters for SQLLoader

     

    The LINC/EAE migration tool can automatically generate reports that can be used to extract your data from DMSII to your target database, for example Oracle.
    In this scenarios the Oracle SQL Loader tool is used. However you might problems loading the data because the string values can contain the same characters you are using to enclose them.

    Let’s see an example, taken from an oracle forum:

    C:\ora>type buyer.ctl
    LOAD DATA
    INFILE 'buyer.data'
    truncate into table BUYER
    FIELDS TERMINATED BY ',' optionally enclosed by '"' TRAILING NULLCOLS
    (
    buyer_code,
    BUYER_NAME
    )
     

    And suppose you have data like:

    1,"XYZ IND"
    2,"ABC"
    3,"XYZ ABC"
    4,"Your "offspring""
    5,"ATUL"

    How can you “escape” the enclosing characters. Well I found the answer in another forum:

    If two delimiter characters are encountered next to each other, a single occurrence of the delimiter character is used in the data value. For example, 'DON''T' is stored as DON'T. However, if the field consists of just two delimiter characters, its value is null.

    So just use something like:

     

    1,"XYZ IND"
    2,"ABC"
    3,"XYZ ABC"
    4,"Your ""offspring"""
    5,"ATUL"

  • Easy way to see the Explain Plan in Oracle

     

    Linc\EAE used profiles for their queries. Well the profile information is used by our migration tool to generate indexes.
    In Java is easy to intercept all SQL statements used by the translated application and analyze them.

    To analyse how a query is executed you have to study its explain plan. For go here an excellent guide on EXPLAIN PLAN.

    After you read that page, you will find useful the following function, that will shorten the lines that you have to type to see the explain plan:

    create OR REPLACE function  ShowPlan return sys_refcursor
      as
          c_test sys_refcursor;
    BEGIN
      open c_test for select 
      substr (lpad(' ', level-1) || operation || ' (' || options || ')',1,30 ) "Operation", 
      object_name "Object"
      from 
      sys.plan_table$ start with id = 0 connect by prior id=parent_id;
      return c_test;
    END;
    SQL> 
    explain plan for select * from MY_TABLE
    SQL> variable rc refcursor SQL> exec :rc := testfunc() PL/SQL procedure successfully completed. SQL> print rc
    Operation                      Object
    ------------------------------ ------------------------------
    SELECT STATEMENT ()
     TABLE ACCESS (FULL)           MY_TABLE
  • Get Table Owner in Oracle

     

    When we migrate from LINC/EAE to Oracle, the migration tool generates an schema an tables form the original ISPECS.
    I came across with the problem that I had been playing around with a test database and I didn’t know who was the owner of the table.

    Well just as a reminder this is what is needed:

    select owner, table_name, tablespace_name   from dba_tables   where table_name='YOUR_TABLE';
    This will return something as:
     

    OWNER                    TABLE_NAME  TABLESPACE_NAME
    ------------------------------ ------------------------ ------------------------------
    THE_OWNER               MY_TABLE       USERS

  • Get Java Version for Oracle Stored Procedures

    If you have to write stored procedures for oracle is important
    to notice which Java version is supported by your Oracle Database,

    A common technique is create a JAVA stored procedure for that:

    1. Create a function with an ORACLE SQL statement like:

    CREATE OR REPLACE FUNCTION getJavaProperty(myprop IN VARCHAR2)
    RETURN VARCHAR2 IS LANGUAGE JAVA
    name ‘java.lang.System.getProperty(java.lang.String) return java.lang.String’;

     

    2. Once you created the function you can use it to get the version:

    SELECT getJavaProperty(‘java.version’) from dual;

    You can see in the attached version that for my Oracle Database 10.1.0.4.2 the Java version is 1.4.2_04 :)

    image

  • File Previewers for Outlook

    As I developer I usually receive emails with .zip attachments and .xml attachments. When I’m looking for an old email I hate that I have to open the attachment just to see if it has the files I’m looking.

    image

    Why isn’t there a built-in preview functionality for .xml and .ZIP files?

    So I thought, I’m a developer I can build one. And I found an excellent article about the File Previewers in Outlook 2007 and Windows 7 by Stephen Toub.

    I just updated the project files to VS 2008 and removed the dependencies to VJ# replacing them by the SharpZipLib library.

    image 

    And it works well and does not require you to install anything else!

     

    Below you can see an example of Zip File preview

    image

    And and example of XML File Preview

    image

    I think is an excellent article and I can know write my own previewers every time I need them.

     

    Download the code from CODE HERE

    Download the installer from INSTALLER HERE

  • Get Exact Text Width C#

     I was recently trying to get the exact width of a string. And I found that the Graphics.MeasureString does not give an exact result.

    I finally found Pierre Arnaud

    post in Code Project, which gave me a good explaination and solution of what was happening.

    You can see in the image Pierre put in his post:

     That Graphics.measurestring will return a size that might be bigger that the actual drawn size, this is due some GDI+ details that he explains in that post.

    I really like the second proposed solution:

    static public int MeasureDisplayStringWidth(Graphics graphics, string text,Font font)
    {
        System.Drawing.StringFormat format  = new System.Drawing.StringFormat ();
        System.Drawing.RectangleF   rect    = new System.Drawing.RectangleF(0, 0,1000, 1000);
        System.Drawing.CharacterRange[] ranges  = { new System.Drawing.CharacterRange(0, text.Length) };
        System.Drawing.Region[]         regions = new System.Drawing.Region[1];

        format.SetMeasurableCharacterRanges (ranges);
        regions = graphics.MeasureCharacterRanges (text, font, rect, format);
        rect    = regions[0].GetBounds (graphics);

        return (int)(rect.Right + 1.0f);
    }

     

     

  • ActiveX Server Migration to .NET

     In VB6 ActiveX-EXEs or ActiveX OLE Server where used for several reasons. Sometimes it was performance (because it allowed you to run your code in another process) and sometimes as way to share resources between several applications, like connection information, database connections, mainframe info, etc.

    During migration some of this ActiveX-Exes can be migrated as simple Assembly DLLs, but other require more attention. Specially if they have global variables that hold state shared by several programs.

    In that is your case what are your options then?

    1. Convert those ActiveX-Exes to Windows Services.

    This option is simple. You modify your migrated assembly to work as a Windows Service. The easier way to do that is:

    a) Start Microsoft Visual Studio 2005\2008

    b) Go to File\New…\Project… and Select Windows Service

    That will generated code like:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.Linq;
    using System.ServiceProcess;
    using System.Text;
    namespace WindowsService1
    {
       public partial class Service1 : ServiceBase
       {
          public Service1()    InitializeComponent();    }
         
    protected override void OnStart(string[] args)    {   }
         
    protected override void OnStop()    {   }
       }
    }

    c) Add a reference to the Remoting Assemblies: System.Runtime.Remoting;

    d) Modify the previous code:

    Add two using statements like:

    using System.Runtime.Remoting.Channels.Http;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting;

    Add a simple event log for tracing:

     

    private static EventLog evt = new EventLog(“Application”);
    private static string SVC_NAME = “ActiveX Server Example Svc”;

     And modify the OnStart and OnStop methods to look like:

      protected override void OnStart(string[] args)
    {
        HttpChannel chnl = new HttpChannel(1234);
       
    ChannelServices.RegisterChannel(chnl,true );
       
    RemotingConfiguration.RegisterWellKnownServiceType(typeof(MyClass), “MyClass.soap”, WellKnownObjectMode.Singleton);
       evt.WriteEntry(SVC_NAME + ” Started”);

    }

    protected override void OnStop() { evt.WriteEntry(SVC_NAME +” Stoppped”); }

    Also make sure that MyClass extends MarshalByRefClass

    2. Convert those ActiveX-Exes using the Artinsoft ActiveX migration helpers.

     Sometimes, you need your migrated application to replicate some of the original ActiveX EXE \OLE DLL VB6 characteristics. For example you need your ActiveX-EXE to start just when the first instance is created and to resemble the VB6 logic for Process creation\destruction.

    For that purpose Artinsoft has created some helpers that our migration tool is able to automatically use in the generated code if it detects that this functionality is needed.

    The code will then be changed from:

    Dim myInstance As New MyProject.MyClass

    To the following Helper method:

    myInstance = MyProjectFactory.Create< MyProject.MyClass>(myInstance);

     And destroy calls can be changed to the following Helper method:

     myInstance= MyProjectFactory.Dispose<MyProject.MyClass >( myInstance); 

    The migration tool will modify your ActiveX-EXEs or OLE Servers to be Windows EXE and the helper will then locate the assembly that contains the desired Class, create an instance and initilize a Remoting channel to the desired classes. Settings as SingleUse and MultiUse are also taken care by the helpers.

    3. Other possible alternatives are using WFC and COM+ that I will comment in another post.

     

     

     

     

More Posts Next page »

This Blog

Syndication

Powered by Community Server (Non-Commercial Edition), by Telligent Systems