Interop: Remove prefix from C# Enums for COM

17. May 2010 09:44 by Mrojas in General  //  Tags:   //   Comments (0)

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

13. May 2010 04:07 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

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?

30. April 2010 04:51 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

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

16. February 2010 10:25 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

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

9. February 2010 18:49 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

 

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"

Get Table Owner in Oracle

3. February 2010 12:40 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

 

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

1. February 2010 06:17 by Mrojas in General  //  Tags: , , ,   //   Comments (0)

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

26. January 2010 09:56 by Mrojas in General  //  Tags:   //   Comments (0)

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

ActiveX Server Migration to .NET

14. December 2009 08:01 by Mrojas in General  //  Tags: , , , , , , , , , ,   //   Comments (0)

 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.