ArtinSoft's Blogs

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

Mauricio Rojas Blog

March 2009 - Posts

  • VB6 AddressOf operator Migration

    I was looking into ways to migrate something like the AddressOf operator in VB6. I read in some forums that I could use a delegate, but I hadn’t seen a code sample, so I started googling until I found this great post.

    It provided a great example of how to pass a pointer to a function. I am attaching here the code so you can benefit from this too.

     

    Remember to put the Working directory pointing to the output directory of the C DLL. In my case it is

    image

    The idea in general is like this:

    /// <summary>
    /// Simple callback function.
    /// </summary>
    /// <param name="a">Some integer parameter.</param>
    public delegate void CBFUNC(int a);

    /// <summary>
    /// Demo of a simple callback.
    /// </summary>
    /// <param name="f">Function to call back to</param>
    /// <param name="a">Parameter which will be returned through the callback</param>
    [DllImport("c_test_lib.dll")]
    public static extern void DoCallback(CBFUNC f, int a);

    DOWNLOAD CODE

  • Bittable What????

    As vb6 migration experts in our company we deal everyday with a lot of issues around Interop and serialization.

    One important thing to note is the concept of “Bittable Types”. I’m not making up terms. Those terms actually exist. Just see this link in MSDN.

    In a few words, a bittable type is a type that has the same representation in managed and unmanaged code.

    Why in earth is that important at all?

    Because if you are calling that great C++ DLL implemented some years ago that just works ok, you won’t be able to pass a NON-Bittable type because that DLL will expect a binary representation different from that in the .NET virtual machine.

    This is also an issue in other scenarios like:

    • Serializing content to files
    • Sending messages through messaging mechanisms like named-pipes or sockets.

    Well, we have just introduced the problem so now let’s think on a nice solution for this problem.

    Well Bittable Types are:

    The following types from the System namespace are blittable types:

     

    So now let’s look at a couple of non-BITTABLE types

    DateTime

    To test this differences let’s make a small test in VB6 and write a Date value to a file:

     

    Private Sub SaveDateToFile()
        Open "C:\test1.bin" For Binary Access Write As #1
        Dim d1 As Date
        d1 = "1/1/2009"
        Put #1, , d1
        Close #1
    End Sub

    Now let’s make a quick program in Vb.NET

     

    Sub Main()
            Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
            Dim fw As New System.IO.BinaryWriter(f)
            Dim d As Date
            d = Convert.ToDateTime("1/1/2009")
            Dim val As Long = d.ToBinary()
            fw.Write(val)
            fw.Close()
            Main2()
        End Sub

     

    If we compare these files we will have:

    image

    So the values are obviously different. This is because VB6 Date are stores with the OLE Automation DateFormat

    So let’s change the C# code for something like:

     

        Sub Main2()
            Dim f As System.IO.FileStream = System.IO.File.Open("C:\test3.bin", IO.FileMode.Create, IO.FileAccess.Write)
            Dim fw As New System.IO.BinaryWriter(f)
            Dim d As Date
            d = Convert.ToDateTime("1/1/2009")
            fw.Write(d.ToOADate())
            fw.Close()
        End Sub

    And now when we compare the files we will have:

    image

     

    So to make your Date values compatible with VB6 format you must user the DateTime method .ToOADate. Now if you are calling a DLL that expects a Date value in the same format used by VB6 then you will have to do this:

     

            Dim d As Date
            d = Convert.ToDateTime("1/1/2009")
            Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d.ToOADate(), Runtime.InteropServices.GCHandleType.Pinned)
            Dim memory_address As IntPtr = handle.AddrOfPinnedObject()
            Try
                APICall(memory_address)
            Finally
                d = DateTime.FromOADate(System.Runtime.InteropServices.Marshal.ReadInt64(memory_address))
                handle.Free()
            End Try  

     

    String

    Most of the time you wont have to deal with String marshalling because adding marshaling tags to your API call solves most of the problems, but if you arent that luckyly then you might do something like:

    IntPtr ptrToStringVar = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(strVar);
    try
    {
       APICall(ptrToStringVar);
    }
    finally
    {
    strVar = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar);
    System.Runtime.InteropServices.Marshal.FreeHGlobal(ptrToStringVar);
    }

    NOTE: if you have an API that might return an string with /0 characters you must call the API with System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar,size), if you do that the Framework will take in consideration the size bytes at the ptrToStringVar memory address.

     

    Double and Singles

    At least between VB6 and VB.NET the double and single types follows the same format. Well, at least, that is the result of my tests.

    Try it yourself, the following shows a simple test for double variables:

    VB6

    Private Sub SaveDoubleToFile()
        Open "C:\test1.bin" For Binary Access Write As #1
        Dim d1 As Double
        d1 = 1.123
        Put #1, , d1
        Close #1
    End Sub
    
    Sub Main()
        SaveDoubleToFile
    End Sub

     

    .NET

    Module Module1
    
        Sub Main()
            Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
            Dim fw As New System.IO.BinaryWriter(f)
            Dim d As Double
            d = 1.123
            fw.Write(d)
            fw.Close()
        End Sub
    
    
    End Module
     

    So you could make an api call in those cases with something like:

    Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d, System.Runtime.InteropServices.GCHandleType.Pinned)
    Dim ptr As System.IntPtr = handle.AddrOfPinnedObject()
    Try
        APICall(ptr)
    Finally
        handle.Free()
    End Try
  • Change CreateObject during Migration

    One of our clients wanted to change the CreateObject function migration for a function of their own. So they wanted all cases like:

    Dim x As Object
    Set x = CreateObject("Excel.Application")

     

    To be migrated to something like:

    Excel.Application x = (Excel.Application) Utils.MyCreateObject("Excel.Application", "");

    Our migratio vb6migration tool provides a new cool feature called CustomMaps. This feature allows you to provide some simple but useful changes to the way things get migrated.

    For this case follow these steps:

    1. Open the Visual Basic Upgrade Companion.

    2. In the Tools Menu choose:

    image

    3. Create a new CustomMaps File and an an entry like the following:

     

    image

    Notice the Source name is VBA.Interaction.CreateObject. To find out this name you can look in your VB6 IDE, right click on the CreateObject and select goto Definition.
    image 
     
    image 
    and for the target name just put the implementation that you what, for example you can write a function like:
    class Utils
            {
                public static object MyCreateObject(string className,params object[] ignoreRestParams)
                {
                    return Activator.CreateInstance(Type.GetType(className));
                }
            }

    and set the SourceName to Utils.MyCreateObject (or NameSpace.Utils.MyCreateObject to use the fully qualified name). You just need to set the New Reference Name column because we will not change the definition of the function.
  • .NET Calculate week number of a date

    This post shows a way to calculate the number of weeks.
    Remember that this calculation is culture-dependant
    For example the GetWeekOfYear methods requires a criteria to determine 
    how to determine the first week and which day to consider as FirstDayOfWeek for more info see here:

    CalendarWeekRule.FirstDay

    Supported by the .NET Compact Framework.

    Indicates that the first week of the year starts on the first day of the year and ends before the following designated first day of the week. The value is 0.

    CalendarWeekRule.FirstFourDayWeek

    Indicates that the first week of the year is the first week with four or more days before the designated first day of the week. The value is 2.

    CalendarWeekRule.FirstFullWeek

    Indicates that the first week of the year begins on the first occurrence of the designated first day of the week on or after the first day of the year. The value is 1.

     

     

    Sample Code

            Dim x As Date
            Dim currentCulture As System.Globalization.CultureInfo
            currentCulture = CultureInfo.CurrentCulture
            Dim weekNum = currentCulture.Calendar.GetWeekOfYear(x, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday)
  • Get path of ASP.NET web application that is running

    If you have your ASP.NET application for example in c:\inetpub\wwwroot\WebApplication1 and you want to programmatically get that path just use something like:

     

    string AppPath = Request.PhysicalApplicationPath;

  • Resizing Forms in C#

    In the VB6 world it was very common to use “Resizer” OCXs that you just put on your forms and that they handle all the proportional resizing needed if you changed your form.

    It was very simple and easy.

    However when I started looking for something like that in .NET it was quite hard to find. So finally I got to ComponentSource and found some alternatives and I evaluated them:

     

    Component Vendor Pricing URL
    Component One Sizer For .NET Component ONE I think you have to buy the Component One Studio. That’s $800 ($1000 with more support)
    and I think is one license per developer, runtime-free
    http://www.componentone.com/SuperProducts/StudioWinForms/
    .NET Forms Resize Softgroup 380$ per developer
    from ComponentSource
    if you buy directly from them you can get the component for 180$

    Each copy is licensed on a per developer basis. A developer may use the license on multiple CPUs as long as they are not used concurrently nor used by another developer. Run-time royalty free.
    http://www.soft-group.it/net/index.php?c=3a&id=11
    Resize OCX/.NET Larcom and Young 95$-100$ per license

    one license per developer, runtime-free
    http://www.lyoung.com/

     

    All of them seem to work. So it really up to your company preferences, budget and the level of support you desire. I haven't made tests like how it behaves if you have thirdparty components or activex ??? maybe I'll review that later.

  • Calling .NET from PowerBuilder

    Most of our clients come from a medium-size to a big enterprise level. In these scenarios is very common to have different department using different technologies to solve their business needs.

    These different technologies can be on a very homogeneous platform like .NET where you can easily interact between your VB.NET and C# assemblies, or they could be on differente technologies like ASP, Classic VB, C++, or Powerbuilder.

    This post is about PowerBuilder, and in order to interact with PowerBuilder I think the easiest way is to expose your assemblies thru COM Interop.

    So if there is some .NET functionality that you want to expose to PowerBuilder you just need to expose that functionality with a class in a ClassLibrary project with COM attributes.

    Let’s begin with a simple program to show how to comunicate Powerbuilder with C#.

    NOTE:  If you don’t have Powerbuilder you can get a trial version from: http://www.sybase.com/detail?id=1052162

    1. Open Microsoft Visual Studio
    2. On the File Menu, choose the New option, and in the File submenu choose Project….

    Visual Studio File Menu

    Figure 1. Visual Studio File Menu. Choosing the option for a new project

    1. When you choose that option a dialog window will shown with the available options for new projects. In the option for C# Projects choose “Class Library”

    image

    Figure 2. New Project dialog window

    You must introduce the new project name, location and solution name. Type something like ClassLibrary1, D:\PowerBuilder, ClassLibrary1.

    1. When you finish creating your project you will have a code file called Class1.cs.

    2. Change that code for something like :

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace SimpleClass
    {
        public class Class1
        {
            public int AddTenToParameter(int param1)
            {
                return param1 + 10;
            }
    
            public void SayHi()
            {
                System.Windows.Forms.MessageBox.Show("Hello World!");
            }
    
            public String GiveMeDate()
            {
                return DateTime.Now.ToLongDateString();
            }
        }
    }

    This will allow to test things like parameter passing, using different return types like strings or integers. But Before you continue you must add a reference to System.Windows.Forms to be able to use the MessageBox.

     

    Context Menu to add a reference

    Figure 3. Adding a reference

     

    Dialog with References

    Figure 3. Reference to System.Windows.Forms

    3. Right click on the solution file and select properties:

    Project properties

    Figure 4. Option to change project properties

    4. Select the Register for COM Interop  checkbox

    Register for COM

    Figure 5. Project properties window

    5. Return to Class1.cs code file

    6. Add an using statement after the existing using lines on Class1.cs file:

    using System.Runtime.InteropServices;

    7. Add the following attributes to the class:

    [ComVisible(true)]
    
    [ClassInterface(ClassInterfaceType.AutoDual)]
    
    [ProgId("ClassLibrary1.Class1")]

    Note: the ProgId is very important, because these value will be use in PB to comunicate with the this code

    8. Now you must edit the AssemblyInfo.cs

    AssemblyInfo file

    Figure 6. AssemblyInfo.cs File

    Now make sure to establish the COM settings in this file with statements like the following:

    // 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)]

    // The following GUID is for the ID of the typelib if this project is exposed to COM

    [assembly: Guid("69efac5b-d887-40f4-a7e9-2721ac3c1598")]

     

    The Guid is also very important, because this is used to differentiate this component and it must be unique.

    To generate a new GUID you can got to the Tools Menu and choose the option Create GUID

    Create GUID menu option

    Figure 7. Option menu to create a GUID

    In the Create Guid dialog box, choose the fourth option and press Copy to put the contents on the Clipboard. Later, copy that value in the GUID attribute but remove the “{“ y “}”.

    Now we are set. You only need to build the solution and the DLL. The build process with register the COM component.

    If you will use the component on another computer you need to create an instalation program.

     

    Using your program from Powerbuilder

    Using your program from Powerbuilder is very easy. You just need code like the following:

    image

    Figure 8. PB Code to call a C# class thru COM

    When you execute this program you will have 3 messageboxes :

    • Hello World!
    • 30
    • Monday, March 02, 2009 (this message will change depending of the day, locale and regional settings)

     

    Creating an instalation program

    1. Right click the solution and in the context menu choose Add and then new project.

    Context Menu for Adding new project to solution

    Figure 9. Context Menu to add a new project

    On the dialog box for Add New projec, look for the Other Project Types section and the choose Setup Project.

    Adding a setup project

    Figure 10. Creating a setup project

    In this dialog bos indicate the name and location of the setup project. For example Setup1 and D:\Powerbuilder\ClassLibrary1.

    Later, add a project to the setup program. To do that rigth click on the setup project and select Add, and in the submenu choose Project Output.

     

    image

    Figure 11. Adding a project to the setup project.

    A dialog box will be shown with a combo that allow you to select the proyects in the solution. Choose ClassLibrary1 and press OK.

    Add Project Output Dialog

    Figure 12. Adding project output to the setup project.

    When you build this instalation program two files will be produced:

    Release

    D:\PowerBuilder\ClassLibrary1\Setup1\Release\Setup.exe

    D:\PowerBuilder\ClassLibrary1\Setup1\Release\Setup1.msi

    Debug

    D:\PowerBuilder\ClassLibrary1\Setup1\Debug\Setup.exe

    D:\PowerBuilder\ClassLibrary1\Setup1\Debug\Setup1.msi

    When you run the instalation program, this program will handle the instalation of the .NET component and the COM registration.

This Blog

Syndication

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