VB6 Migration of Property Pages

9. June 2009 08:36 by Mrojas in General  //  Tags: , , , , , , ,   //   Comments (0)

How can I migrate property pages? Well that is a common question when migrating VB6 Activex controls.

Property Pages where commonly used in VB6 to provide a mechanism for your user controls to edit values.

.NET provides even more mechanisms for editing your control properties.  You can provide an editor for each one of your component properties or you can provide a ComponentEditor for all the component, this is very similar to the VB6 concept.

In .NET the ComponentEditor can be actived in the designer selecting Properties from the context menu when you right click over the control.

This is from the MSDN documentation:

“A component editor is used to edit a component as a whole and can be used to implement a
user interface similar to that of the property pages. You associate a component editor with a
component by using the
EditorAttribute attribute.” From: ComponentEditor Class

The VBUC does not process out of the box, your PropertyPages, but I developed a tool that can be
used so the VBUC can help you migrate those property pages. This tool will modify your VB6 project,
and VB6 PropertyPages source code to make those VB6 PropertyPages look like VB6 UserControls.
This will allow  the VBUC migration tool to recover some of the VB6 PropertyPages code and appearance
and with some manual changes you can get your property pages to work again.

Use the following link to downlaod the tool: DOWNLOAD TOOL

So these are the steps to migrate a VB6 Project that has Property Pages with the VB6.

1) Make a backup copy of your source code.

2) Run the TOOL with your project file. For example if your project file is Project1.vbp then run the tool like this:

FixPropertyPages Project1.vbp

This will generate a new VB6 Project file called ModifiedProject1.vbp

3) Open the VBUC, and migrate the new project file ModifiedProject1.vbp

4) Open the migrated solution in Visual Studio.

5) All your property pages will be migrated to .NET UserControls. You might need to go thru some changes to make them completely functional. Remeber to add the [ToolboxItem(false)] to these property pages because they do not need to be visible in your toolbox.

6) Now, to associate those property pages with your UserControl do this:

6.1) Add a new code file to your migrated solution. We are going to create a ComponentEditor, that will hold all the pages and associate that to the migrated control. Lets say the control is named Control1 and the property pages are PropertyPage1 and PropertyPage2.
We will call the ComponentEditor ComponentEditorToAssociatePagesForMyControl.
In this ComponentEditor we will add an internal class for each PropertyPage. This class will inherit from ComponentEditorPage. We will call this internal classes Page1, and Page2. And we will associate those classes with the ComponentEditorToAssociatePagesForMyControl in the GetComponentEditorPages().
 

The resulting code will be like:

C#
using System.Windows.Forms.Design;
using WindowsFormsApplication1;
using System.Drawing;
using System.ComponentModel;
[ToolboxItem(false)] 
public class ComponentEditorToAssociatePagesForMyControl : WindowsFormsComponentEditor
{
    // Methods
    public override bool EditComponent(ITypeDescriptorContext context, object component)
    {
        return false;
    }

    class Page1 : ComponentEditorPage
    {
        // Methods
        public Page1()
        {
            PropertyPage1ForControl1 page1 = new PropertyPage1ForControl1();
            Size mysize = new Size(400, 250);
            this.Size = mysize;
            this.Text = "Page 1 for Control1";
            this.Controls.Add(page1);
        }
        protected override void LoadComponent() { }
        protected override void SaveComponent() { }
    }

    class Page2 : ComponentEditorPage
    {
        // Methods
        public Page2()
        {
            PropertyPage2ForControl1 page2 = new PropertyPage2ForControl1();
            Size mysize = new Size(400, 250);
            this.Size = mysize;
            this.Text = "Page 2 for Control1";
            this.Controls.Add(page2);
        }
        protected override void LoadComponent() { }
        protected override void SaveComponent() { }
    }

    protected override System.Type[] GetComponentEditorPages()
    {
        return new System.Type[] { typeof(Page1),typeof(Page2) };
    }

    protected override int GetInitialComponentEditorPageIndex()
    {
        return 0;
    }
}

VB.NET

<ToolboxItem(False)> _
Public Class ComponentEditorToAssociatePagesForMyControl
    Inherits WindowsFormsComponentEditor
    ' Methods
    Public Overrides Function EditComponent(ByVal context As ITypeDescriptorContext, ByVal component As Object) As Boolean
        Return False
    End Function

    Protected Overrides Function GetComponentEditorPages() As Type()
        Return New Type() { GetType(Page1), GetType(Page2) }
    End Function

    Protected Overrides Function GetInitialComponentEditorPageIndex() As Integer
        Return 0
    End Function


    ' Nested Types
    Private Class Page1
        Inherits ComponentEditorPage
        ' Methods
        Public Sub New()
            Dim page1 As New PropertyPage1ForControl1
            Dim mysize As New Size(400, 250)
            MyBase.Size = mysize
            Me.Text = "Page 1 for Control1"
            MyBase.Controls.Add(page1)
        End Sub

        Protected Overrides Sub LoadComponent()
        End Sub

        Protected Overrides Sub SaveComponent()
        End Sub

    End Class

    Private Class Page2
        Inherits ComponentEditorPage
        ' Methods
        Public Sub New()
            Dim page2 As New PropertyPage2ForControl1
            Dim mysize As New Size(400, 250)
            MyBase.Size = mysize
            Me.Text = "Page 2 for Control1"
            MyBase.Controls.Add(page2)
        End Sub

        Protected Overrides Sub LoadComponent()
        End Sub

        Protected Overrides Sub SaveComponent()
        End Sub

    End Class
End Class

 

7) After creating the ComponentEditor you must associate the component Editor to your new component editors. This can be done with something like:

C# 

[Editor(typeof(ComponentEditorToAssociatePagesForMyControl), typeof(ComponentEditor))]
public class Control1 : UserControl

VB.NET

<Editor(GetType(ComponentEditorToAssociatePagesForMyControl), GetType(ComponentEditor))> _
Public Class Control1
8)  Now to use this property pages, go to the designer screen and open the context menu and select properties. And editor with your properties pages will appear :)

9) You still need to write some code for saving the property values that is something you have to add to the LoadComponent and SaveComponent methods of the internal classes in your ComponentEditor (ComponentEditorToAssociatePagesForMyControl in our previous example).

I hope this helps to get your code faster in .NET. I'm attaching a C# sample if you want to try it out.

VB6 TabIndex and C#

5. June 2009 05:30 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

Some time ago Jose Aguilar had blogged about the Interesting Behavior of TabIndex in Migrated Applications. As he explained at the time there are functional differences between the TabIndex behaviour in VB6

 

If you look at Figure1.

image

Figure 1. This image show a VB6 form, the TabIndex values and the way the form navigates when you press Tab.

If you migrate that form with the VBUC and activate the TabOrder option in View\TabOrder you will see something like:

image

As you can see by the 0.1 and 0.3 and 5.4 and 5.2 values. TabOrder in .NET is hierarquical. When you press tab you will navigate to the next control in the container, and when you get to the last in that container then you will switch to the next one in the following container. This is different from the VB6 world when you would have switched from 0.1 to 5.2.

How can we fix this without a lot of manual corrections. Well you can override the ProcessTabKey method to navigate controls following the tabIndex without taking into account the containers.

The code you will need to add is:

        /// <summary>
/// holds a list of controls for tab navigation
/// </summary>
List<Control> controls = new List<Control>();
/// <summary>
/// Populates the list used for tab navigation
/// </summary>
/// <param name="c">Control to use to populate list</param>
protected void BuildOrder(Control c)
{
if (c.TabStop)
controls.Add(c);
if (c.Controls.Count > 0)
{
foreach (Control child in c.Controls)
BuildOrder(child);
}
}
/// <summary>
/// Transversers all form controls to populate a list ordered by TabIndex
/// that will be used to follow tabindex ignoring containers
/// </summary>
protected void BuildOrder()
{
if (controls.Count == 0)
{

foreach (Control c in this.Controls)
{
BuildOrder(c);
}
controls.Sort(
delegate(Control c1, Control c2) { return c1.TabIndex.CompareTo(c2.TabIndex); });
}
}
/// <summary>
/// Overrides default tabIndex behaviour
/// </summary>
/// <param name="forward"></param>
/// <returns></returns>
protected override bool ProcessTabKey(bool forward)
{
BuildOrder();
if (ActiveControl != null)
{
int index = controls.IndexOf(ActiveControl);
if (index != -1)
{
if (forward)
controls[(index + 1) % controls.Count].Select();
else
controls[index==0?controls.Count-1:index-1].Select();

return true;
}

else
return false;
}
else
return base.ProcessTabKey(forward);
}

After adding this code just run your project and it will fix the tabIndex issues.

A Better Visual Studio!

3. June 2009 04:21 by Mrojas in General  //  Tags: , , ,   //   Comments (0)

Recently I discovered in MSDN a great addition, a must to for all C# developers. CodeRush Express.

This product was build by DevExpress and it just make it perfect your experience with Visual Studio.

 

For example finding symbols or files, tabbing between references, and more than 20 differente refactorings!!!!

Take at look at this new extension! It’s a absolutely a must.

Extended WebBrowser Control Series:And the WebBrowser keeps going…

Well recently Kingsley has point me to a lot of useful links to improve the ExtendedWebBrowser. However he found another detail. When in Javascript you do something like a:

window.open(‘url’,’window’,’width=200;height=300’);

Those width and height settings were not being considered in the new window. I researched for I while until I found this great link:

HOW TO: Get Width and Height from window.open() Inside a WebBrowser Host by Using Visual Basic .NET

So basicly I follow the sugested code and added logic in my EventSink class:

        public void WindowSetLeft(int Left)
        {
            ///Should I calculate any diff?
            _Browser.Parent.Left = Left;

        }

        public void WindowSetTop(int Top)
        {
            _Browser.Parent.Top = Top;

        }

        public void WindowSetWidth(int Width)
        {
            int diff = 0;
            diff = _Browser.Parent.Width - _Browser.Width;
            _Browser.Parent.Width = diff + Width;

        }
        public void WindowSetHeight(int Height)
        {
            int diff = 0;
            diff = _Browser.Parent.Height - _Browser.Height;
            _Browser.Parent.Height = diff + Height;

        }
So now when the window opens it takes the specified width, heigth, left and top.

As always

HERE IS THE UPDATED CODE

Extended WebBrowser Control Series: WebBrowser Control and window.Close()

I had previously posted an extended version of the WebBrowser Control. This code posted in Opening Popup in a NewWindow and NewWindow2 Events in the C# WebBrowserControl, dealt with some issues when you want to have a form with a WebBrowser and in the enclosed page you have a Javascript code like:

window.open(“ <some url to a page”)

But recently another problem arised. What if you have a Javascript snippet like:

window.close()

OMG!!! Why haven’t I thought about it. Well Kelder wrote me about this problem and he also sent me some of his\her research results:

Solution (Add WebBrowser as unmanaged code):  blogs.msdn.com/jpsanders/archive/2008/04/23/window-close-freezes-net-2-0-webbrowser-control-in-windows-form-application.aspx

Solution (Add WebBrowser using WM_NOTIFYPARENT override):blogs.msdn.com/jpsanders/archive/2007/05/25/how-to-close-the-form-hosting-the-webbrowser-control-when-scripting-calls-window-close-in-the-net-framework-version-2-0.aspx

http://blogs.msdn.com/jpsanders/archive/2007/05/25/how-to-close-the-form-hosting-the-webbrowser-control-when-scripting-calls-window-close-in-the-net-framework-version-2-0.aspx

Solution (Implementation not detailed): social.msdn.microsoft.com/forums/en-US/winforms/thread/1199c004-9eb2-400d-a118-6e06bca9f1f0/

Proposes changing pop-up links to WebBrowser navigate: dotnetninja.wordpress.com/2008/02/26/prevent-opening-new-window-from-webbrowser-control/Close

problem observed (no solution):www.codeproject.com/KB/cpp/ExtendedWebBrowser.aspx

It seams to me that the better solution is to use jpsanders solution, so I created an ExtendWebBrowser_v2 (the following is the modified fragment):

//Extend the WebBrowser control
public class ExtendedWebBrowser : WebBrowser
{
    
    // Define constants from winuser.h
    private const int WM_PARENTNOTIFY = 0x210;
    private const int WM_DESTROY = 2;
    
    AxHost.ConnectionPointCookie cookie;
    WebBrowserExtendedEvents events;

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_PARENTNOTIFY:
             if (!DesignMode) 
             {
                if (m.WParam.ToInt32() == WM_DESTROY) 
                {
                    Message newMsg = new Message();
                    newMsg.Msg = WM_DESTROY;
                    // Tell whoever cares we are closing
                    Form parent = this.Parent as Form;
                    if (parent!=null)
                        parent.Close();
                }
             }
            DefWndProc(ref m);
            break;
          default:
            base.WndProc(ref m);
            break;
        }
    }

The problem that might arise with this solution is that the parent might not be a Form but an user control, etc. For a more general aproach I think I should send a WM_DESTROY directly to the parent, but for most cases it works. I’m attaching the code and a sample page called test0.htm. I hope this helps and rembember you can always donate to programming geeks jejejejeje just kidding

HERE IS THE CODE

Extended WebBrowser Control Series: Opening Popup in a NewWindow

In a previous post, i had published an “Extended Version” of the WebBrowser control
that gave you access to events like the NewWindow2.
This event that is not public in the common WebBrowser control allows you to intercept
the NewWindow event but gives you the posibility to setup the the ppDisp property witch sets
a pointer to the WebBrowser where the new window will be open.

So i setup a small example using this “ExtendedBrowser”.

I created a simple page (well really it was my wife, I know about transport-layer, C++, bits etc, but I never remember HTML syntax):

<html>
<body>
<H1> This is sample page to test opening a pop up in a new form </H1>
<input type="button" onclick="window.open('test0.htm')"/>
</body>
</html>

And created a simple form like in the following picture:

image

 

Instead of using a WebBrowser control i just used an ExtendedWebBrowser from my previous post.

And added code like:

        private void extendedWebBrowser1_NewWindow2(object sender, NewWindow2EventArgs e)
        {
            //Intercepting this event will allow us to create a new form in which
            //we will open the new webpage, to do that we must set the ppDisp property
            //of the NewWindow2EventArgs
            FormWithExtendedBrowser form1 = new FormWithExtendedBrowser();
            form1.Show();
            e.PPDisp = form1.extendedWebBrowser1.Application;
        }

When I run the code , it now opens the pop up in my form:

image

But test if for yourself! :) HERE IS THE CODE

Get the Week Number in C#

30. April 2009 10:56 by Mrojas in General  //  Tags: , , ,   //   Comments (0)

Here is some examples of how to determine the WeekNumber of a given Date

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {

        object index = DateTime.Now;
        int res = 0;
        //0    First day of year
        res = System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
        Convert.ToDateTime(index), System.Globalization.CalendarWeekRule.FirstDay, System.Globalization.DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek);

        //1    (Default) First four day week from Sunday
        res = System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
        Convert.ToDateTime(index), System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Sunday);

        //2    First four day week from StartOfWeek
        res = System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                Convert.ToDateTime(index), System.Globalization.CalendarWeekRule.FirstFourDayWeek, System.Globalization.DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek);

        //3    First full week from Sunday
        res = System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                Convert.ToDateTime(index), System.Globalization.CalendarWeekRule.FirstFullWeek, DayOfWeek.Sunday);
        
        //4    First full week from StartOfWeek
        res = System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                Convert.ToDateTime(index), System.Globalization.CalendarWeekRule.FirstFullWeek, System.Globalization.DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek);


        }
    }
}

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

20. March 2009 06:32 by Mrojas in General  //  Tags: , , ,   //   Comments (0)
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)

Debug XBAP using WinForms Host

26. January 2009 06:13 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

Recently I had to deal with targeting an XBAP application that had some Windows Forms controls.

The problem is that those controls can only be used in a trusted environment. If you try to debug an XBAP with some Windows Forms Controls you will get an exception like:

Message: Cannot create instance of 'Page1' defined in assembly 'XBAPWithWinForms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Exception has been thrown by the target of an invocation.  Error in markup file 'Page1.xaml' Line 1 Position 7.

It took me a while to found a solution, and it was thru Scott Landford Blog that I found a way around.

In short what he recommends to do is:

Change your settings to:

Start Action->Start external program = %windir%system32\PresentationHost.exe

  In my case (and the case of most people that is: c:\windows\system32\PresentationHost.exe)


Start Options->Command line arguments = -debug "c:\projects\myproject\bin\debug\MyProject.xbap" -debugSecurityZoneUrl "http://localhost:2022"

Copy the value from the Start URL after the –debug argument

Very import for using WinForms components you must run in FULL TRUST

fullTrust

 

Here is some XBAP code using a WinForms WebBrowser. They designer does not like it a lot but it works:

XBAPWithWinforms.zip

Use C++ in C#

19. December 2008 07:00 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

COM


The idea is to make a class or several classes available thru COM. Then the compiled dll or the TLB is used to generate and Interop Assembly and call the desired functions.

With this solution the current C++ code base line can be kept or might require just subtle changes. 

Calling a function thru com is involved in a lot of marshalling and can add an additional layer that is not really needed in the architecture of the solution.

Creating a Managed Wrapper with Managed C++

The idea with this scenario is to provide a class in Managed C++ that will be available in C#. This class is just a thin proxy that redirects calls to the Managed object. 

Let’s see the following example: 

If we have a couple of unmanaged classes like: 

class Shape {

public:

  Shape() {

    nshapes++;

  }

  virtual ~Shape() {

    nshapes--;

  };

  double  x, y;  

  void    move(double dx, double dy);

  virtual double area(void) = 0;

  virtual double perimeter(void) = 0;

  static  int nshapes;

}; 
 

class Circle : public Shape { 

private:

  double radius;

public:

  Circle(double r) : radius(r) { };

  virtual double area(void);

  virtual double perimeter(void);

}; 
 

The first thing we can try, to expose our classes to .NET it to set the setting for managed compilation: 

Es posible que tu navegador no permita visualizar esta imagen.Es posible que tu navegador no permita visualizar esta imagen. 

If your project compiles then you are just very close, and what you need is to add some managed classes to your C++ project to expose your native classes: 

Let’s see the Shape class: 

//We can use another namespace, to avoid name collition.

//In this way we can replicate the structure of our C++ classes. 

namespace exposedToNET

{

      //Shape is an abstract class so the better thing

    // to do is to generate an interface 

      public interface class Shape : IDisposable

      {

      public:  

            //public variables must be exposed as properties

            property double x

            {

                  double get();

                  void set(double value);

            }

            property double y

            {

                  double get();

                  void set(double value);

            }

            //method do not expose any problems

            void move(double dx, double dy);

            double area();

            double perimeter();

            //public static variables must

          //be exposed as static properties

          //However we might need to create a new class

          //for all public static variables and methods

          //because C# does not accept methods in an interface

            static property int nshapes;

      };

      //Static methods or variables of abstract class are added here

      public ref class Shape_Methods

      {

                        //public static variables must be exposed as static properties

      public:

            static property int nshapes

            {

                  int get()

                  {

                        return ::Shape::nshapes;

                  }

                  void set(int value)

                  {

                        ::Shape::nshapes = value;

                  }

            } 

      };

} 

And for the Circle class we will have something like this: 

namespace exposedToNET

{ 
 

      public ref class Circle : Shape

      {

      private:

            ::Circle* c;

      public:

            Circle(double radius)

            {

                  c = new ::Circle(radius);

            }

            ~Circle()

            {

                  delete c;

            }

            //public variables must be exposed as properties

            property double x

            {

                  virtual double get()

                  {

                        return c->x;

                  }

                  virtual void set(double value)

                  {

                        c->x = value;

                  }

            }

            property double y

            {

                  virtual double get()

                  {

                        return c->y;

                  }

                  virtual void set(double value)

                  {

                        c->y = value;

                  }

            }

            //method do not expose any problems

            virtual void move(double dx, double dy)

            {

                  return c->move(dx,dy);

            }

            virtual double area()

            {

                  return c->area();

            }

            virtual double perimeter()

            {

                  return c->perimeter();

            }

            //public static variables must be exposed as static properties

            static property int nshapes

            {

                  int get()

                  {

                        return ::Shape::nshapes;

                  }

                  void set(int value)

                  {

                        ::Shape::nshapes = value;

                  }

            } 
 

      }; 

}

DOWNLOAD EXAMPLE CODE

SWIG

 

SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages.

This is a great tool used for several languages like Python, Perl, Ruby, Scheme, and even in different platforms.

The exposure mechanism used in this scheme is platform invoke, the issues here are similar to those of COM because there is some marshaling going on. This scheme might be more efficient than the COM one but I haven’t really test it to be completely sure that it is better. 

I have reviewed the SWIG code and it might also be possible to modify its code to generate wrappers using managed C++, but this is an interesting exercise that I have to leave for my readers. Sorry I just don’t have enough time. 

But how is SWIG used? 

In SWIG what you do is that you add a .i file to your project. This file provides directives for some code generation that specify exactly what you want to expose and how. 

This can very helpful if you just want to expose some methods. 

If you are lazy like me you can just add something like: 

/* File : example.i */

%module example 

%{

#include "example.h"  ß you put here includes with the definitions for your classes

%} 

/* Let's just grab the original header file here */

%include "example.h" ß add alse the include here 

And SWIG will add a file like example_wrap.cxx that you have to compile with the rest of your C++ code. 

It will also generate a set of C# classes that you use in your C# application, so it seams to your program that all the code is just C#.  

SWIG is a great tool and has been testing in a lot of platforms.

Migration to 64-bit: ODBC

5. December 2008 06:20 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

Most people migrating their application want to move ahead and take advantage of new technologies and new operating systems.

So if you had a VB6 application and you migrated it with us to .NET we will recommend and automate the process to use ADO.NET.

Why?

You can still use ODBC but i will list some compelling reasons:

* There a very fast ADO.NET drivers available. Using ODBC implies addind an interop overhead that can affect performance.
* Some vendors do not support and/or certify the use of ODBC drivers for .NET. So in those cases if you use ODBC your are on your own.
During my consulting experience I have seen several problems using ODBC drivers ranging from just poor performance, problems with some SQL statements, stored procedures calls, database specific features or complete system inestability.
* and also problems running in 64-bit.

This last one is very concerning. If you made all the effort to migrate an application to .NET and run it on for example on a Windows 2003 64 bit server it wont be able to use your 32-bit ODBC drivers unless you go to the the Build tab, and set Platform Target to "x86".

This is very sad because your application cannot take advantage of all the 64 bit resources.

If you are lucky enough you might find a 64 bit version of your ODBC driver but I will really recommend going straigth to 64-bit and use ADO.NET. And that's exactly what we can really help you to do specially in our version 2.2 of the VBUC.

 

Migration of ActiveX UserDocuments to C# or .NET

This post describes an an interesting workaround that you can use to support the migration of ActiveX Documents with the Artinsoft Visual Basic Upgrade Companion which is one of the Artinsoft \ Mobilize.NET tools you can use to modernize your Visual Basic, Windows Forms and PowerBuilder applications.

Currently the Visual Basic Upgrade Companion does not allow you to process ActiveX Document directly, but there is a workaround: in general ActiveX Document are something really close to an User Control which is a element that is migrated automatically by the Visual Basic Upgrade Companion.

This post provides a link to a tool (DOWNLOAD TOOL) that can fix your VB6 projects, so the Visual Basic Upgrade Companion processes them. To run the tool:

1) Open the command prompt

2) Go to the Folder where the .vbp file is located

3) Execute a command line command like:

 FixUserDocuments Project1.vbp

This will generate a new project called Project1_modified.vbp. Migrate this new project and now UserDocuments will be supported.

  

First Some History
 

VB6 allows you to create UserDocuments, which can be embedded inside an ActiveX container. The most common one is Internet Explorer. After compilation, the document is contained in a Visual Basic Document file (.VBD) and the server is contained in either an .EXE or .DLL file. During development, the project is in a .DOB file, which is a plain text file containing the definitions of the project’s controls, source code, and so on.

If an ActiveX document project contains graphical elements that cannot be stored in text format, they will be kept in a .DOX file. The .DOB and .DOX files in an ActiveX document project are parallel to the .FRM and .FRX files of a regular Visual Basic executable project. 

The trick to support ActiveX documents is that in general they are very similar to UserControls, and .NET UserControls can also be hosted in a WebBrowser. The following command line tool can be used to update your VB6 projects. It will generate a new solution where UserDocuments will be defined as UserControls.

 

If you have an ActiveX document like the following: 

 

Then after running the tool you will have an Project like the following:

 

So after you have upgraded the projet with the Fixing tool, open the Visual Basic Upgrade Companion  and migrate your project.

After migration you will get something like this:

 

 

To use your migrated code embedded in a Web Browser copy the generated assemblies and .pdb to the directory you will publish:

Next create an .HTM page. For example UserDocument1.htm

The contents of that page should be something like the following:

 

<html>

<body>

<p>ActiveX Demo<br> <br></body>

<object id="UserDocument1"

classid="http:<AssemblyFileName>#<QualifiedName of Object>"

height="500" width="500" VIEWASTEXT>   

</object>

<br><br>

</html>

 

For example:

<html>

<body>

<p>ActiveX Demo<br> <br></body>

<object id="UserDocument1"

classid="http:Project1.dll#Project1.UserDocument1"

height="500" width="500" VIEWASTEXT>   

</object>

<br><br>

</html>

  

 Now all that is left is to publish the output directory.
To publish your WinForms user control follow these steps.

  1. Create a Virtual Directory:

  1. A Wizard to create a Virtual Directory will appear.

 

 Click Next

 Name the directory as you want. For example Project1. Click Next

 Select the location of your files. Click the Browse button to open a dialog box where you can select your files location. Click Next

 Check the read and run scripts checks and click next

 Now Click Finish

  1. Properties for the Virtual Directory will look like this:

 

NOTE: to see this dialog right click over the virtual directory

 

  1. Now just browse to the address lets say http:\\localhost\Project1\UserDocument1.htm

 And that should be all! :)

 

 

 

The colors are different because of the Host configuration however a simple CSS like:

 

<style>

 body {background-color: gray;}

</style>

 

Can make the desired change:

 

 

 

Notice that there will be security limitations, for example for thinks like MessageBoxes.

You can allow restricted operations by setting your site as a restricted site:

 

For example:

 

 

Restrictions

The constraints for this solution include:

 

* This solutions requires Windows operating system on the client side

* Internet Explorer 6.0 is the only browser that provides support for this type of hosting

* It requires .NET runtime to be installed on the client machine.

* It also requires Windows 2000 and IIS 5.0 or above on the server side

 

Due to all of the above constraints, it might be beneficial to detect the capabilities of the client machine and then deliver content that is appropriate to them. For example, since forms controls hosted in IE require the presence of the .NET runtime on the client machine, we can write code to check if the client machine has the .NET runtime installed. You can do this by checking the value of the Request.Browser.ClrVersion property. If the client machine has .NET installed, this property will return the version number; otherwise it will return 0.0.

 

Adding a script like:

 

<script>

 if ((navigator.userAgent.indexOf(".NET CLR")>-1))

 {

      //alert ("CLR available " +navigator.userAgent);

 }

  else

      alert(".NET SDK/Runtime is not available for us from within " +            "your web browser or your web browser is not supported." +            " Please check with http://msdn.microsoft.com/net/ for " +            "appropriate .NET runtime for your machine.");

 

</script>

Will help with that.

 References:

 

ActiveX Documents Definitions:

http://www.aivosto.com/visdev/vdmbvis58.html

 

 

Hosting .NET Controls in IE

http://www.15seconds.com/issue/030610.htm

 

 

Scripting your applications in .NET Part 2

14. October 2008 03:10 by Mrojas in General  //  Tags: , , ,   //   Comments (0)

Just more details about scripting

Using the MS Scripting Object

 

The MS Scripting Object can be used in .NET applications. But it has several limitations.

The main limitation it has is that all scripted objects must be exposed thru pure COM. The scripting object is a COM component that know nothing about .NET

 

In general you could do something like the following to expose a component thru COM:

 
   [System.Runtime.InteropServices.ComVisible(true)]
    public partial class frmTestVBScript  : Form
    {
        //Rest of code
    }
 
NOTE: you can use that code to do a simple exposure of the form to COM Interop. However to provide a full exposure of a graphical component like a form or user control you should use the Interop Form ToolKit from Microsoft http://msdn.microsoft.com/en-us/vbasic/bb419144.aspx
 

To expose an object in COM. But most of the properties and methods in a System.Windows.Forms.Form class, use native types instead of COM types.

 

As you could see in the Backcolor property example:

 
public int MyBackColor
{
            get { return System.Drawing.ColorTranslator.ToOle(this.BackColor); }
            set { this.BackColor = System.Drawing.ColorTranslator.FromOle(value); }
}
   Issues:
  • The problem with properties such as those is that System.Drawing.Color is not COM exposable.
  • Your script will expect an object exposing COM-compatible properties.
  • Another problem with that is that there might be some name collision.
 

Using Forms

 

In general to use your scripts without a lot of modification to your scripts you should do something like this:

 
  • Your forms must mimic the interfaces exposed by VB6 forms. To do that you can use a tool like OLE2View and take a look at the interfaces in VB6.OLB
  • Using those interfaces create an interface in C#
  • Make your forms implement that interface.
  • If your customers have forms that they expose thru com then if those forms add new functionality do this:
    • Create a new interface, that extends the basic one you have and
 

I’m attaching an application showing how to to this.

Performing a CreateObject and Connecting to the Database

 

The CreateObject command can still be used. To allow compatibility the .NET components must expose the same ProgIds that the used.

 

ADODB can still be used, and probably RDO and ADO (these last two I haven’t tried a lot)

 

So I tried a simple script like the following to illustrate this:

Sub ConnectToDB
'declare the variable that will hold new connection object
Dim Connection 
'create an ADO connection object
Set Connection=CreateObject("ADODB.Connection")
'declare the variable that will hold the connection string
Dim ConnectionString 
'define connection string, specify database driver and location of the database
ConnectionString = "Driver={SQL Server};Server=MROJAS\SQLEXPRESS;Database=database1;TrustedConnection=YES"
'open the connection to the database
Connection.Open ConnectionString
MsgBox "Success Connect. Now lets try to get data"
'declare the variable that will hold our new object
Dim Recordset   
'create an ADO recordset object
Set Recordset=CreateObject("ADODB.Recordset")
'declare the variable that will hold the SQL statement
Dim SQL    
SQL="SELECT * FROM Employees"
'Open the recordset object executing the SQL statement and return records
Recordset.Open SQL, Connection
'first of all determine whether there are any records
If Recordset.EOF Then
    MsgBox "No records returned."
Else
'if there are records then loop through the fields
Do While NOT Recordset.Eof   
 MsgBox Recordset("EmployeeName") & " -- " & Recordset("Salary")
  Recordset.MoveNext    
Loop
End If
MsgBox "This is the END!"

End Sub
 
 

I tested this code with the sample application I’m attaching. Just paste the code, press Add Code, then type ConnectToDB and executeStatement

 

I’m attaching an application showing how to do this. Look at extended form. Your users will have to make their forms extend the VBForm interface to expose their methods.

 

Using Events

 

Event handling has some issues.

All events have to be renamed (at least this is my current experience, I have to investigate further, but the .NET support for COM Events does a binding with the class names I think there’s a workaround for this but I still have not the time to test it).

In general you must create an interface with all events, rename then (in my sample I just renamed them to <Event>2) and then you can use this events.

You must also add handlers for .NET events to raise the COM events.

 
     #region "Events"

        public delegate void Click2EventHandler();
        public delegate void DblClick2EventHandler();
        public delegate void GotFocus2EventHandler();

        public event Click2EventHandler    Click2;
        public event DblClick2EventHandler DblClick2;
        public event GotFocus2EventHandler GotFocus2;

        public void HookEvents()
        {
            this.Click += new EventHandler(SimpleForm_Click);
            this.DoubleClick += new EventHandler(SimpleForm_DoubleClick);
            this.GotFocus += new EventHandler(SimpleForm_GotFocus);
        }

        void SimpleForm_Click(object sender, EventArgs e)
        {
            if (this.Click2 != null)
            {
                try
                {
                    Click2();
                }
                catch { }
            }
        }

        void SimpleForm_DoubleClick(object sender, EventArgs e)
        {
            if (this.DblClick2 != null)
            {
                try
                {
                    DblClick2();
                }
                catch { }
            }

        }


        void SimpleForm_GotFocus(object sender, EventArgs e)
        {
            if (this.GotFocus2 != null)
            {
                try
                {
                    GotFocus2();
                }
                catch { }
            }
        }



        #endregion
 Alternative solutions

Sadly there isn’t currently a nice solution for scripting in .NET.  Some people have done some work to implement something like VBScript in .NET (including myself as a personal project but not mature enough I would like your feedback there to know if you will be interesting in a managed version of VBScript)  but currently the most mature solution I have seen is Script.NET. This implementation is a true interpreter. http://www.codeplex.com/scriptdotnet Also microsoft is working in a DLR (Dynamic Languages Runtime, this is the runtime that I’m using for my pet project of VBScript)

 

The problem with some of the other solutions is that they allow you to use a .NET language like CSharp or VB.NET or Jscript.NET and compile it. But the problem with that is that this process generates a new assembly that is then loaded in the running application domain of the .NET Virtual machine. Once an assembly is loaded it cannot be unloaded. So if you compile and load a lot of script you will consume your memory. There are some solutions for this memory consumption issues but they require other changes to your code.

 

Using other alternatives (unless you used a .NET implementation of VBScript which currently there isn’t a mature one) will require updating all your user scripts. Most of the new scripts are variants of the Javascript language.

  Migration tools for VBScript

No. There aren’t a lot of tools for this task. But you can use http://slingfive.com/pages/code/scriptConverter/

  Download the code from: http://blogs.artinsoft.net/public_img/ScriptingIssues.zip


 

OutOfProcess In C#

2. October 2008 11:49 by Mrojas in General  //  Tags: , , ,   //   Comments (0)

In VB6 you could create an OutOfProcess instance to execute some actions. But there is not a direct equivalent for that. However you can run a class in an another application domain to produce a similar effect that can be helpful in a couple of scenarios.

This example consists of two projects. One is a console application, and the other is a Class Library that holds a Class that we want to run like an "OutOfProcess" instance. In this scenario. The console application does not necessary know the type of the object before hand. This technique can be used for example for a Plugin or Addin implementation.

Code for Console Application

using System;
using System.Text;
using System.IO;
using System.Reflection;

namespace OutOfProcess
{
    /// <summary>
    /// This example shows how to create an object in an
    /// OutOfProcess similar way.
    /// In VB6 you were able to create an ActiveX-EXE, so you could create
    /// objects that execute in their own process space.
    /// In some scenarios this can be achieved in .NET by creating 
    /// instances that run in their own
    /// 'ApplicationDomain'.
    /// This simple class shows how to do that.
    /// Disclaimer: This is a quick and dirty implementation.
    /// The idea is get some comments about it.
    /// </summary>
    class Program
    {
        delegate void ReportFunction(String message);

        class RemoteTextWriter : TextWriter
        {
            ReportFunction report;
            public RemoteTextWriter(ReportFunction report)
            {
                this.report = report;
            }
            public override Encoding Encoding
            {
                get
                {
                    return new UnicodeEncoding(false, false);
                }
            }

            public override void Flush()
            {
                //Nothing to do here
            }


            public override void Write(char value)
            {
                //ignore
            }

            public override void Write(string value)
            {

                report(value);
            }

            public override void WriteLine(string value)
            {

                report(value);
            }

            //This is very important. Specially if you have a long running process
            // Remoting has a concept called Lifetime Management.
            //This method makes your remoting objects Inmmortals
            public override object InitializeLifetimeService()
            {
                return null;
            }

        }

        static void ReportOut(String message)
        {
            Console.WriteLine("[stdout] " + message);
        }

        static void ReportError(String message)
        {
            ConsoleColor oldColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("[stderr] " + message);
            Console.ForegroundColor = oldColor;
        }

        static void ExecuteAsOutOfProcess(String assemblyFilePath,String typeName)
        {
            RemoteTextWriter outWriter = new RemoteTextWriter(ReportOut);
            RemoteTextWriter errorWriter = new RemoteTextWriter(ReportError);

            //<-- This is my path, change it for your app


            //Type superProcessType = AspUpgradeAssembly.GetType("OutOfProcessClass.SuperProcess");
            AppDomain outofProcessDomain = 
                AppDomain.CreateDomain("outofprocess_test1", 
                AppDomain.CurrentDomain.Evidence, 
                AppDomain.CurrentDomain.BaseDirectory, 
                AppDomain.CurrentDomain.RelativeSearchPath, 
                AppDomain.CurrentDomain.ShadowCopyFiles);
            //When the invoke member is called this event must return the assembly

            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(outofProcessDomain_AssemblyResolve);
            Object outofProcessObject = 
                outofProcessDomain.CreateInstanceFromAndUnwrap(
                assemblyFilePath, typeName);
            assemblyPath = assemblyFilePath;
            outofProcessObject.
                GetType().InvokeMember("SetOut", 
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, 
                null, outofProcessObject, new object[] { outWriter });
            outofProcessObject.
                GetType().InvokeMember("SetError", 
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, 
                null, outofProcessObject, new object[] { errorWriter });
            outofProcessObject.
                GetType().InvokeMember("Execute", 
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, 
                null, outofProcessObject, null);
            Console.ReadLine();

        }

        static void Main(string[] args)
        {
            string testAssemblyPath = 
                @"B:\OutOfProcess\OutOfProcess\OutOfProcessClasss\bin\Debug\OutOfProcessClasss.dll";
            ExecuteAsOutOfProcess(testAssemblyPath, "OutOfProcessClass.SuperProcess");

        }

        static String assemblyPath = "";
        static Assembly outofProcessDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {

            try
            {
                //We must load it to have the metadata and do reflection
                return Assembly.LoadFrom(assemblyPath);
            }
            catch
            {
                return null;
            }
        }

    }


}

 

Code for OutOfProcess Class

 

 

using System;
using System.Collections.Generic;
using System.Text;

namespace OutOfProcessClass
{
    public class SuperProcess : MarshalByRefObject
    {
        public void SetOut(System.IO.TextWriter newOut)
        {
            Console.SetOut(newOut);
        }
        public void SetError(System.IO.TextWriter newError)
        {
            Console.SetError(newError);
        }

        public void Execute()
        {
            for (int i = 1; i < 5000; i++)
            {
                Console.WriteLine("running running running ");
                if (i%100 == 0) Console.Error.Write("an error happened");
                
            }

        }
    }
}

TLBIMP SourceCode

30. September 2008 09:50 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

 

Have you ever wished to modify the way Visual Studio imported a COM Class. Well finally you can.

The Managed, Native, and COM Interop Team (wow what a name). It looks like the name of that goverment office in the Ironman movie.

Well this fine group of men, have release the source code of the TLBIMP tool. I'm more that happy for this.

I can know finally get why are some things imported the way they are.

http://www.codeplex.com/clrinterop

You can dowload also the P/Invoke assistant. This assistant has a library of signatures so you can invoke any Windows API.

 

Extended WebBrowser Control Series:NewWindow2 Events in the C# WebBrowserControl

The WebBrowser control for .NET is just a wrapper for the IE ActiveX control. However this wrapper does not expose all the events that the IE ActiveX control exposes.

For example the ActiveX control has a NewWindow2 that you can use to intercept when a new window is gonna be created and you can even use the ppDisp variable to give a pointer to an IE ActiveX instance where you want the new window to be displayed.

So, our solution was to extend the WebBrowser control to make some of those events public.

In general the solution is the following:

  1. Create a new Class for your Event that extend any of the basic EventArgs classes.
  2. Add constructors and property accessor to the class
  3. Look at the IE Activex info and add the  DWebBrowserEvents2 and IWebBrowser2 COM interfaces. We need them to make our hooks.
  4. Create a WebBrowserExtendedEvents extending System.Runtime.InteropServices.StandardOleMarshalObject and DWebBrowserEvents2. We need this class to intercept the ActiveX events. Add methos for all the events that you want to intercept.
  5. Extend the WebBrowser control overriding the CreateSink and DetachSink methods, here is where the WebBrowserExtendedEvents class is used to make the conneciton.
  6. Add EventHandler for all the events.

And thats all.Here is the code. Just add it to a file like ExtendedWebBrowser.cs

using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;

    //First define a new EventArgs class to contain the newly exposed data
    public class NewWindow2EventArgs : CancelEventArgs
    {

        object ppDisp;

        public object PPDisp
        {
            get { return ppDisp; }
            set { ppDisp = value; }
        }


        public NewWindow2EventArgs(ref object ppDisp, ref bool cancel)
            : base()
        {
            this.ppDisp = ppDisp;
            this.Cancel = cancel;
        }
    }

    public class DocumentCompleteEventArgs : EventArgs
    {
        private object ppDisp;
        private object url;

        public object PPDisp
        {
            get { return ppDisp; }
            set { ppDisp = value; }
        }

        public object Url
        {
            get { return url; }
            set { url = value; }
        }

       public DocumentCompleteEventArgs(object ppDisp,object url)
        {
           this.ppDisp = ppDisp;
           this.url = url;

        }
    }

    public class CommandStateChangeEventArgs : EventArgs
    {
        private long command;
        private bool enable;
        public CommandStateChangeEventArgs(long command, ref bool enable)
        {
            this.command = command;
            this.enable = enable;
        }

        public long Command
        {
            get { return command; }
            set { command = value; }
        }

        public bool Enable
        {
            get { return enable; }
            set { enable = value; }
        }
    }


    //Extend the WebBrowser control
    public class ExtendedWebBrowser : WebBrowser
    {
        AxHost.ConnectionPointCookie cookie;
        WebBrowserExtendedEvents events;


        //This method will be called to give you a chance to create your own event sink
        protected override void CreateSink()
        {
            //MAKE SURE TO CALL THE BASE or the normal events won't fire
            base.CreateSink();
            events = new WebBrowserExtendedEvents(this);
            cookie = new AxHost.ConnectionPointCookie(this.ActiveXInstance, events, typeof(DWebBrowserEvents2));

        }

        public object Application
        {
            get
            {
                IWebBrowser2 axWebBrowser = this.ActiveXInstance as IWebBrowser2;
                if (axWebBrowser != null)
                {
                    return axWebBrowser.Application;
                }
                else
                    return null;
            }
        }

        protected override void DetachSink()
        {
            if (null != cookie)
            {
                cookie.Disconnect();
                cookie = null;
            }
            base.DetachSink();
        }

        //This new event will fire for the NewWindow2
        public event EventHandler<NewWindow2EventArgs> NewWindow2;

        protected void OnNewWindow2(ref object ppDisp, ref bool cancel)
        {
            EventHandler<NewWindow2EventArgs> h = NewWindow2;
            NewWindow2EventArgs args = new NewWindow2EventArgs(ref ppDisp, ref cancel);
            if (null != h)
            {
                h(this, args);
            }
            //Pass the cancellation chosen back out to the events
            //Pass the ppDisp chosen back out to the events
            cancel = args.Cancel;
            ppDisp = args.PPDisp;
        }


        //This new event will fire for the DocumentComplete
        public event EventHandler<DocumentCompleteEventArgs> DocumentComplete;

        protected void OnDocumentComplete(object ppDisp, object url)
        {
            EventHandler<DocumentCompleteEventArgs> h = DocumentComplete;
            DocumentCompleteEventArgs args = new DocumentCompleteEventArgs( ppDisp, url);
            if (null != h)
            {
                h(this, args);
            }
            //Pass the ppDisp chosen back out to the events
            ppDisp = args.PPDisp;
            //I think url is readonly
        }

        //This new event will fire for the DocumentComplete
        public event EventHandler<CommandStateChangeEventArgs> CommandStateChange;

        protected void OnCommandStateChange(long command, ref bool enable)
        {
            EventHandler<CommandStateChangeEventArgs> h = CommandStateChange;
            CommandStateChangeEventArgs args = new CommandStateChangeEventArgs(command, ref enable);
            if (null != h)
            {
                h(this, args);
            }
        }


        //This class will capture events from the WebBrowser
        public class WebBrowserExtendedEvents : System.Runtime.InteropServices.StandardOleMarshalObject, DWebBrowserEvents2
        {
            ExtendedWebBrowser _Browser;
            public WebBrowserExtendedEvents(ExtendedWebBrowser browser)
            { _Browser = browser; }

            //Implement whichever events you wish
            public void NewWindow2(ref object pDisp, ref bool cancel)
            {
                _Browser.OnNewWindow2(ref pDisp, ref cancel);
            }

            //Implement whichever events you wish
            public void DocumentComplete(object pDisp,ref object url)
            {
                _Browser.OnDocumentComplete( pDisp, url);
            }

            //Implement whichever events you wish
            public void CommandStateChange(long command, bool enable)
            {
                _Browser.OnCommandStateChange( command,  ref enable);
            }


        }
        [ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch), TypeLibType(TypeLibTypeFlags.FHidden)]
        public interface DWebBrowserEvents2
        {
            [DispId(0x69)]
            void CommandStateChange([In] long command, [In] bool enable);
            [DispId(0x103)]
            void DocumentComplete([In, MarshalAs(UnmanagedType.IDispatch)] object pDisp, [In] ref object URL);
            [DispId(0xfb)]
            void NewWindow2([In, Out, MarshalAs(UnmanagedType.IDispatch)] ref object pDisp, [In, Out] ref bool cancel);
        }

        [ComImport, Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E"), TypeLibType(TypeLibTypeFlags.FOleAutomation | TypeLibTypeFlags.FDual | TypeLibTypeFlags.FHidden)]
        public interface IWebBrowser2
        {
            [DispId(100)]
            void GoBack();
            [DispId(0x65)]
            void GoForward();
            [DispId(0x66)]
            void GoHome();
            [DispId(0x67)]
            void GoSearch();
            [DispId(0x68)]
            void Navigate([In] string Url, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
            [DispId(-550)]
            void Refresh();
            [DispId(0x69)]
            void Refresh2([In] ref object level);
            [DispId(0x6a)]
            void Stop();
            [DispId(200)]
            object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xc9)]
            object Parent { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xca)]
            object Container { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xcb)]
            object Document { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
            [DispId(0xcc)]
            bool TopLevelContainer { get; }
            [DispId(0xcd)]
            string Type { get; }
            [DispId(0xce)]
            int Left { get; set; }
            [DispId(0xcf)]
            int Top { get; set; }
            [DispId(0xd0)]
            int Width { get; set; }
            [DispId(0xd1)]
            int Height { get; set; }
            [DispId(210)]
            string LocationName { get; }
            [DispId(0xd3)]
            string LocationURL { get; }
            [DispId(0xd4)]
            bool Busy { get; }
            [DispId(300)]
            void Quit();
            [DispId(0x12d)]
            void ClientToWindow(out int pcx, out int pcy);
            [DispId(0x12e)]
            void PutProperty([In] string property, [In] object vtValue);
            [DispId(0x12f)]
            object GetProperty([In] string property);
            [DispId(0)]
            string Name { get; }
            [DispId(-515)]
            int HWND { get; }
            [DispId(400)]
            string FullName { get; }
            [DispId(0x191)]
            string Path { get; }
            [DispId(0x192)]
            bool Visible { get; set; }
            [DispId(0x193)]
            bool StatusBar { get; set; }
            [DispId(0x194)]
            string StatusText { get; set; }
            [DispId(0x195)]
            int ToolBar { get; set; }
            [DispId(0x196)]
            bool MenuBar { get; set; }
            [DispId(0x197)]
            bool FullScreen { get; set; }
            [DispId(500)]
            void Navigate2([In] ref object URL, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
            [DispId(0x1f7)]
            void ShowBrowserBar([In] ref object pvaClsid, [In] ref object pvarShow, [In] ref object pvarSize);
            [DispId(-525)]
            WebBrowserReadyState ReadyState { get; }
            [DispId(550)]
            bool Offline { get; set; }
            [DispId(0x227)]
            bool Silent { get; set; }
            [DispId(0x228)]
            bool RegisterAsBrowser { get; set; }
            [DispId(0x229)]
            bool RegisterAsDropTarget { get; set; }
            [DispId(0x22a)]
            bool TheaterMode { get; set; }
            [DispId(0x22b)]
            bool AddressBar { get; set; }
            [DispId(0x22c)]
            bool Resizable { get; set; }
        }
   }

Using C# or VB.NET collection in your VB6 Applications

28. August 2008 05:34 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

If you have some .NET code that you want to share with VB6, COM has always been a nice option. You just add couple of ComVisible tags and that's all.

But...

Collections can be a little tricky.

This is a simple example of how to expose your Collections To VB6.

Here I create an ArrayList descendant that you can use to expose your collections.
Just create a new C# class library project and add the code below.
Remember to check the Register for ComInterop setting.

 
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace CollectionsInterop
{
    
    [Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")]
    [ComVisible(true)]
    public interface IMyCollectionInterface 
    {

        int Add(object value);
        void Clear();
        bool Contains(object value);
        int IndexOf(object value);
        void Insert(int index, object value);
        void Remove(object value);
        void RemoveAt(int index);

        [DispId(-4)]
        System.Collections.IEnumerator GetEnumerator();
      
        [DispId(0)]
        [System.Runtime.CompilerServices.IndexerName("_Default")]
        object this[int index]
        {
            get;
        }
    }

    
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IMyCollectionInterface))]
    [ProgId("CollectionsInterop.VB6InteropArrayList")]
    public class VB6InteropArrayList : System.Collections.ArrayList, IMyCollectionInterface
    {

        #region IMyCollectionInterface Members


        // COM friendly strong typed GetEnumerator

        [DispId(-4)]
        public System.Collections.IEnumerator GetEnumerator()
        {
            return base.GetEnumerator();
        }



        #endregion
    }



    /// <summary>
    /// Simple object for example 
    /// </summary>
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ProgId("CollectionsInterop.MyObject")]
    public class MyObject
    {
        String value1 = "nulo";

        public String Value1
        {
            get { return value1; }
            set { value1 = value; }
        }
        String value2 = "nulo";

        public String Value2
        {
            get { return value2; }
            set { value2 = value; }
        }
    }


}
 
To test this code you can use this VB6 code. Remember to add a reference to this class library.
Private Sub Form_Load()
    Dim simpleCollection As New CollectionsInterop.VB6InteropArrayList
    Dim value As New CollectionsInterop.MyObject
    value.Value1 = "Mi valor1"
    value.Value2 = "Autre valeur"
    simpleCollection.Add value
    
    For Each c In simpleCollection
      MsgBox value.Value1
    Next
End Sub

Scripting your applications in .NET

14. August 2008 16:48 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

In VB6 it was very simple to add scripting capabilities to your application.
Just by using the Microsoft Script Control Library
You can still use this library in .NET just as Roy Osherove' Bloc show in
http://weblogs.asp.net/rosherove/articles/dotnetscripting.aspx

However there are some minor details that must be taken care of:

* Objects must be exposed thru COM (Add the [ComVisible(true)] attribute to the class
* Add the ComVisible(true) attribute to the AssemblyInfo file
* Make these objects public
* Recommended (put your calls to Eval or ExecuteStatement inside try-catch blocks).

And here's an example:

using System;
using System.Windows.Forms;
 
namespace ScriptingDotNetTest
{
    [System.Runtime.InteropServices.ComVisible(true)]
    public partial class frmTestVBScript  : Form
    {
        public int MyBackColor
        {
            get { return System.Drawing.ColorTranslator.ToOle(this.BackColor); }
            set { this.BackColor = System.Drawing.ColorTranslator.FromOle(value); }
        }
 
        MSScriptControl.ScriptControl sc = new MSScriptControl.ScriptControl();
        private void RunScript(Object eventSender, EventArgs eventArgs)
        {
            try
            {
                sc.Language = "VbScript";
                sc.Reset();
                sc.AddObject("myform", this, true);
                sc.ExecuteStatement("myform.MyBackColor = vbRed");
            }
            catch 
            {
                MSScriptControl.IScriptControl iscriptControl = sc as MSScriptControl.IScriptControl;
                lblError.Text = "ERROR" + iscriptControl.Error.Description + " | Line of error: " + iscriptControl.Error.Line + " | Code error: " + iscriptControl.Error.Text;
            }
        }
 
        [STAThread]
        static void Main()
        {
            Application.Run(new frmTestVBScript());
        }
    }
}


TIP: If you don find the reference in the COM tab, just browse to c:\windows\system32\msscript.ocx

 

Categories