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:
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
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:
Good luck! And If you had any other suggestions just leave a comment.
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 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.
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.
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"
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
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 :)
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.
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.
And it works well and does not require you to install anything else!
Below you can see an example of Zip File preview
And and example of XML File Preview
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
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.