Interop: BinaryCompatibilty for VB6 Migrations

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

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

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

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

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

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

For a vb class with two methods:

Sub Method1()

End Sub

Sub Method2()

End Sub

It will produce a typelib like:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>
[
  uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
  version(1.0)
]
library Project1
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _Class1;

    [
      odl,
      uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B),
      version(1.0),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _Class1 : IDispatch {
        [id(0x60030000)]
        HRESULT Method1();
        [id(0x60030001)]
        HRESULT Method2();
    };

    [
      uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
      version(1.0)
    ]
    coclass Class1 {
        [default] interface _Class1;
    };
};

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

 

Sub Method3()

End Sub

Sub Method4()

End Sub

Sub Method1()

End Sub

Sub Method2()

End Sub

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

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

[
  uuid(FE5C56C2-E03A-4DC0-994D-B68543C72A46),
  version(1.0)
]
library Project1
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _Class1;

    [
      odl,
      uuid(A3032E1E-52FE-42E0-98FF-84A9DD4FD8C3),
      version(1.0),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _Class1 : IDispatch {
        [id(0x60030000)]
        HRESULT Method3();
        [id(0x60030001)]
        HRESULT Method4();
        [id(0x60030002)]
        HRESULT Method1();
        [id(0x60030003)]
        HRESULT Method2();
    };

    [
      uuid(72721504-CC56-4BB9-9447-C7193FE8C02D),
      version(1.0)
    ]
    coclass Class1 {
        [default] interface _Class1;
    };
};

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

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

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>
[
  uuid(8ABA2C0C-7CCA-40CD-A944-56707566634A),
  version(1.1)
]
library Project1
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    interface _Class1;
    [
      odl,
      uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF),
      version(1.1),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _Class1 : IDispatch {
        [id(0x60030002)]
        HRESULT Method3();
        [id(0x60030003)]
        HRESULT Method4();
        [id(0x60030000)]
        HRESULT Method1();
        [id(0x60030001)]
        HRESULT Method2();
    };

    [
      uuid(C71C7AB0-552A-4D5D-A9FB-AF33830A697E),
      version(1.1)
    ]
    coclass Class1 {
        [default] interface _Class1;
    };

    typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
    _Class1 Class1___v0;
};

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

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


    typedef [uuid(6B86684C-B3DD-4680-BF95-8DEE2C17AF5B), version(1.0), public]
    _Class1 Class1___v0;

    typedef [uuid(6E9C59C3-82D7-444C-92FB-01B49D91A2FF), version(1.1), public]
    _Class1 Class1___v1;

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

Binary Compatibility in .NET

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

Lets take our previous example:

using System;
using System.Runtime.InteropServices;

namespace InteropExamples
{
    public class Class1
    {
        public void Method3()
        {
        }
        public void Method4()
        {
        }
        public void Method1()
        {
        }
        public void Method2()
        {
        }
        public void Method5()
        {
        }
    }
}

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

  public partial class Class1
    {
        public void Method3()
        {
        }
        public void Method4()
        {
        }
        public void Method1()
        {
        }
        public void Method2()
        {
        }
    }

    [ComVisible(true)]
    public interface _Class1
    {
        void Method3();
        void Method4();
        void Method1();
        void Method2();

    }
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(_Class1))]
    partial class Class1 : _Class1
    {
        #region _Class1 Members

        void _Class1.Method3()
        {
            Method3();
        }

        void _Class1.Method4()
        {
            Method4();
        }

        void _Class1.Method1()
        {
            Method1();
        }

        void _Class1.Method2()
        {
            Method2();
        }

        #endregion
    }

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

using System;
using System.Runtime.InteropServices;

namespace InteropExamples
{
    public partial class Class1
    {

        public void Method3()
        {
            System.Windows.Forms.MessageBox.Show("3 N");
        }

        public void Method4()
        {
            System.Windows.Forms.MessageBox.Show("4 N");
        }

        public void Method5()
        {
            System.Windows.Forms.MessageBox.Show("5 N");
        }


        public void Method1()
        {
            System.Windows.Forms.MessageBox.Show("1 N");
        }

        public void Method2()
        {
            System.Windows.Forms.MessageBox.Show("2 N");
        }
    }

    [ComVisible(true)] //This to make the interface Visible for COM
    [TypeLibType((TypeLibTypeFlags)((short)TypeLibTypeFlags.FHidden |
        (short)TypeLibTypeFlags.FDual |
        (short)TypeLibTypeFlags.FNonExtensible |
        (short)TypeLibTypeFlags.FOleAutomation))] //This to use the same flags as in previous tlb
    [Guid("9BAFD76D-8E6B-439C-8B6D-37260BFA3317")] //This is to make the class have the guid
    public interface _Class1
    {
        [DispId(0x60030000)]
        void Method1();
        [DispId(0x60030001)]
        void Method2();
        [DispId(0x60030002)]
        void Method3();
        [DispId(0x60030003)]
        void Method4();
        [DispId(0x60030004)]
        void Method5();


    }

    [ComVisible(true)] //This to make the class Visible for COM
    [ClassInterface(ClassInterfaceType.None)] //This is to make sure that we have control on interface generation
    [ComDefaultInterface(typeof(_Class1))] //To set default interface
    [ProgId("Project1.Class1")] //To set ProgId 
    [Guid("C71C7AB0-552A-4D5D-A9FB-AF33830A697E")] //Maintain same Guid.
    partial class Class1 : _Class1, Class1___v0, Class1___v1
    {
        #region _Class1 Members

        void _Class1.Method3()
        {
            Method3();
        }

        void _Class1.Method4()
        {
            Method4();
        }

        void _Class1.Method1()
        {
            Method1();
        }

        void _Class1.Method2()
        {
            Method2();
        }

        #endregion


        #region Class1___v0 Members

        void Class1___v0.Method1()
        {
            Method1();
        }

        void Class1___v0.Method2()
        {
            Method2();
        }

        void Class1___v0.Method3()
        {
            Method3();
        }

        void Class1___v0.Method4()
        {
            Method4();
        }

        void Class1___v0.Method5()
        {
            Method5();
        }

        #endregion

        #region Class1___v1 Members

        void Class1___v1.Method1()
        {
            Method1();
        }

        void Class1___v1.Method2()
        {
            Method2();
        }

        void Class1___v1.Method3()
        {
            Method3();
        }

        void Class1___v1.Method4()
        {
            Method4();
        }

        void Class1___v1.Method5()
        {
            Method5();
        }

        #endregion
    }

    //This is to keep compatibility with old versions
    //we cannot generate a typedef so we will need to add all of the versions
    //for BinaryCompatibility
    [ComVisible(true)]
    [Guid("6B86684C-B3DD-4680-BF95-8DEE2C17AF5B")]
    [TypeLibType(TypeLibTypeFlags.FHidden)]
    public interface Class1___v0
    {
        [DispId(0x60030000)]
        void Method1();
        [DispId(0x60030001)]
        void Method2();
        [DispId(0x60030002)]
        void Method3();
        [DispId(0x60030003)]
        void Method4();
        [DispId(0x60030004)]
        void Method5();
    }

    //This is to keep compatibility with old versions
    //we cannot generate a typedef so we will need to add all of the versions
    //for BinaryCompatibility
    [ComVisible(true)]
    [Guid("4A7A3317-BF13-443E-9DB0-2C5EA21F00CA")]
    [TypeLibType(TypeLibTypeFlags.FHidden)]
    public interface Class1___v1
    {
        [DispId(0x60030000)]
        void Method1();
        [DispId(0x60030001)]
        void Method2();
        [DispId(0x60030002)]
        void Method3();
        [DispId(0x60030003)]
        void Method4();
        [DispId(0x60030004)]
        void Method5();
    }

}

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

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

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

Enjoy.

AutoCAD VBA Migration to VB.NET or C#

AutoCAD 2010 will not be supporting VBA.

Quoting

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

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

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

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

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

ActiveX Server Migration to .NET

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

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

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

In that is your case what are your options then?

1. Convert those ActiveX-Exes to Windows Services.

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

a) Start Microsoft Visual Studio 2005\2008

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

That will generated code like:

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

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

d) Modify the previous code:

Add two using statements like:

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

Add a simple event log for tracing:

 

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

 And modify the OnStart and OnStop methods to look like:

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

}

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

Also make sure that MyClass extends MarshalByRefClass

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

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

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

The code will then be changed from:

Dim myInstance As New MyProject.MyClass

To the following Helper method:

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

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

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

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

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

 

 

 

 

Code compiles but System.IO.FileNotFound Exception is thrown

2. October 2009 10:04 by Mrojas in General  //  Tags: , , , , , , ,   //   Comments (0)

We have seen a situation that can be a little annoying.
I usually find that situation in ASP.NET projects, but it can also happen in Winforms.

The issue is like this:

  • You have a VStudio Solution
  • You have added all the needed references
  • All your code compiles

 

BUT

When you run the application it terminates at a certain point with a
FileNotFound exception indicating that the assembly blablabla  or one of its
dependencies could not be loaded.

 

Whats hapenning!!!

Do this:

1) Look at the bin\debug or bin\release and make sure that the assembly is in those directories.

2) If the assembly is not there, then go to the project references in the solution explorer, right click and select properties and set the Copy Local Setting

 

image

Mapping VBRUN.RasterOpConstants

1. October 2009 05:44 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

 

vb Constant Decimal Value Hex Value .NET
vbDstInvert 5570569 H550009
System.Drawing.CopyPixelOperation.DestinationInvert
vbMergeCopy 12583114 HC000CA
System.Drawing.CopyPixelOperation.MergeCopy
vbMergePaint 12255782 HBB0226
System.Drawing.CopyPixelOperation.MergePaint
vbNotSrcCopy 3342344 H330008
System.Drawing.CopyPixelOperation.NotSourceCopy
vbNotSrcErase 1114278 H1100A6
System.Drawing.CopyPixelOperation.NotSourceErase
vbPatCopy 15728673 HF00021
System.Drawing.CopyPixelOperation.PatCopy
vbPatInvert 5898313 H5A0049
System.Drawing.CopyPixelOperation.PatInvert
vbPatPaint 16452105 HFB0A09
System.Drawing.CopyPixelOperation.PatPaint
vbSrcAnd 8913094 H8800C6
System.Drawing.CopyPixelOperation.SourceAnd
vbSrcCopy 13369376 HCC0020
System.Drawing.CopyPixelOperation.SourceCopy
vbSrcErase 4457256 H440328
System.Drawing.CopyPixelOperation.SourceErase
vbSrcInvert 6684742 H660046
System.Drawing.CopyPixelOperation.SourceInvert
vbSrcPaint 15597702 HEE0086
System.Drawing.CopyPixelOperation.SourcePaint

IE Explorer and Favorites folder or Special Folders in general

29. September 2009 04:05 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

I found this email in my inbox today:

Hi Mauricio,I came across a reference to your blog at :http://stackoverflow.com/questions/1286746/c-open-link-in-new-tab-webbrowser-control

I have been studying your writings on extending the WebBrowser control, and verified that the extended web code you wrote for C# compiles and works fine in VS 2010 beta, against FrameWork 4.0.

Many thanks for the valuble code and writing !

I am "stuck" on how to read the contents of an IE browser page when the page is displaying a local file, like the contents of the Favorites folder.

All my attempts to get at the Document or DomDocument by casting it to the usual mshtml.dll interfaces fail.

I am NOT asking you to answer my question, or respond, but if you ever get interested in blogging about this aspect of use of IE, I think many people would be interested.

I have done a lot of research on the net, and posted my own question on StackOverFlow : so far not one real pointer, and, possibly, this is not "doable" (?) : maybe what you are seeing when IE shows a file contents is a kind of "virtual explorer" view that is not parseable.

best, Bill xxxxxx”

And I decided to take at look at it to see if I could be of any help and I found out that it is easy and doable.

So I find an useful link by Andreas M. if you want to look at it.

In general My Favorites, Desktop, etc are special folder. So they need a trick to be able to access them.

 

image

Take the code from my ExtendedWebBrowser sample published in http://blogs.artinsoft.net/mrojas/archive/2009/05/01/opening-popup-in-a-newwindow.aspx

and http://blogs.artinsoft.net/mrojas/archive/2009/08/07/newwindow3.aspx and

1. Add a reference to %windir%\system32\shell32.dll

2. Add a new property to the ExtendedWebBrowser like:

    /// <summary>
    /// Returns the shell folderview object displayed in the webbrowser control.
    /// </summary>
    public Shell32.IShellFolderViewDual2 FolderView
    {
        get
        {
            return ((SHDocVw.WebBrowser)base.ActiveXInstance).Document
                     as Shell32.IShellFolderViewDual2;
        }
    }

And now you can access the special folder from your code. As Bill mentioned, that “page” or “special page” is not real HTML and not parseable but you can examine its contents for example you can do something like:

        /// <summary>
        /// Button 1_ click
        /// </summary>
        private void button1_Click(object sender, EventArgs e)
        {
            Shell32.IShellFolderViewDual2 specialFolder = this.extendedWebBrowser1.FolderView;
            string folderName = specialFolder.Folder.Title;
            string parentFolder = specialFolder.Folder.ParentFolder.Title;
            foreach (Shell32.ShellFolderItem f in specialFolder.Folder.Items())
            {
                if (f.IsFolder)
                    System.Diagnostics.Debug.WriteLine("Folder:" + f.Name);
                else
                    System.Diagnostics.Debug.WriteLine("File:" + f.Name);
            } // foreach
        } // button1_Click(sender, e)

C# PInvoke out or ref??

17. August 2009 09:40 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

If I have a PInvoke call like the following:

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename, 
   out UInt32 status);

What is the difference between

 

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)] static extern Boolean FileEncryptionStatus(String filename, out UInt32 status);

and

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)] static extern Boolean FileEncryptionStatus(String filename, ref UInt32 status);

Well, as long as I have tested it, they exactly the same. From the MSDN you can even read

“I could have selected the ref keyword here as well, and in fact both result in the same machine code at run time. The out keyword is simply a specialization of a by-ref parameter that indicates to the C# compiler that the data being passed is only being passed out of the called function. In contrast, with the ref keyword the compiler assumes that data may flow both in and out of the called function.”

“When marshaling pointers through P/Invoke, ref and out are only used with value types in managed code. You can tell a parameter is a value type when its CLR type is defined using the struct keyword. Out and ref are used to marshal pointers to these data types”

So what should you use? Well using the out keyword for PInvoke will just add some information or documentation to your method, but because these functions are implemented in C or C++ they might treat an out parameter as an IN parameter so I really prefere to use ref when I’m calling functions with PInvoke.

Extended WebBrowser Control Series: NewWindow3

Recently an user of the ExtendedBrowser v2 commented that he needed access to the NewWindow3 event.

The NewWindow3 event is raised when a new window is to be created. It extends NewWindow2 with additional information about the new window. 

Syntax

Private Sub object_NewWindow3( _
	ByRef ppDisp As Object, _
	ByRef Cancel As Boolean, _
	ByVal dwFlags As Long, _
	ByVal bstrUrlContext As String, _
	ByVal bstrUrl As String)

Parameters

object Object expression that resolves to the objects in the Applies To list. ppDisp Object expression that, optionally, receives a new, hidden WebBrowser or InternetExplorer object with no URL loaded. Cancel A Boolean value that determines whether the current navigation should be canceled. true Cancel the navigation. false Do not cancel the navigation. dwFlags The flags from the NWMFenumeration that pertain to the new window.

typedef enum NWMF 
{     
    NWMF_UNLOADING = 0x00000001,
     NWMF_USERINITED = 0x00000002,
     NWMF_FIRST = 0x00000004,
     NWMF_OVERRIDEKEY = 0x00000008,
     NWMF_SHOWHELP = 0x00000010,
     NWMF_HTMLDIALOG = 0x00000020,
    NWMF_FROMDIALOGCHILD = 0x00000040,
     NWMF_USERREQUESTED = 0x00000080,
     NWMF_USERALLOWED = 0x00000100,
     NWMF_FORCEWINDOW = 0x00010000,
     NWMF_FORCETAB = 0x00020000,
     NWMF_SUGGESTWINDOW = 0x00040000,
     NWMF_SUGGESTTAB = 0x00080000,
     NWMF_INACTIVETAB = 0x00100000
} NWMF;

bstrUrlContext The URL of the page that is opening the new window. bstrUrlThe URL that is opened in the new window.

Please notice:

Note   The NewWindow3 event is only fired when a new instance of Internet Explorer is about to be created. Calling showModalDialog or showModelessDialog does not trigger an event because they are not new instances of Internet Explorer. They are implemented as MSHTML host windows, which allows them to render and display HTML content but not hyperlinks between documents.

You can download from here

DOWNLOAD CODE HERE v3_1

ExtendedBrowserExampleVBNET.zip (92.56 kb)

CapsLock, NumLock in C# and VB.NET

10. July 2009 12:49 by Mrojas in General  //  Tags: , , , , ,   //   Comments (0)

I was looking for a “.net” way of detecting the CapsLock state, but almost all the references pointed to pinvoke code like:

<DllImport("user32.dll")> _
Public Shared Function GetKeyState(VirtKey As Integer) As Integer
End Sub

And I finally found two ways:

1) You can call methods from the System.Console class:

You can use the System.Console.CapsLock property and if you want the NumLock state use: System.Console.NumberLock

or

2) You can call make an instance of Microsoft.VisualBasic.Devices.Keyboard. (For this if you are in C# you need to add a reference to Microsoft.VisualBasic.dll)

For example:

Microsoft.VisualBasic.Devices.Keyboard key = new Microsoft.VisualBasic.Devices.Keyboard();

and use properties like:

key.CapsLock

key.NumLock

key.ScrollLock

key.ShiftKeyDown

key.CtrlKeyDown

key.AltKeyDown

DDE in .NET

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

Someone recently made me remind an old technology called DDE.

“Dynamic Data Exchange (DDE) is a technology for communication between multiple applications under Microsoft Windows or OS/2

 

“The primary function of DDE is to allow Windows applications to share data. For example, a cell in Microsoft Excel could be linked to a value in another application and when the value changed, it would be automatically updated in the Excel spreadsheet. The data communication was established by a simple, three-segment model. Each program was known to DDE by its "application" name. Each application could further organize information by groups known as "topic" and each topic could serve up individual pieces of data as an "item". For example, if a user wanted to pull a value from Microsoft Excel which was contained in a spreadsheet called "Sheet1" in the cell in the first row and first column, the application would be "Excel", the topic "Sheet1" and the item "r1c1".

Note: In DDE, the application, topic and item are not case-sensitive.”

 

So in VB6 you can have something like:

 

Private Sub Form_Load()
Text1.LinkMode = 0
Text1.LinkTopic = "Excel|Sheet1"
Text1.LinkItem = "R1C1"
Text1.LinkMode = 1
End Sub

 

 

How can you do that in .NET. Is it possible in C#? Well I started looking around and found several forums explaining about all the API calls and I was just about to write my own solution when I found NDDE. This project hosted in CodePlex “provides a convenient and easy way to integrate .NET applications with legacy applications that use Dynamic Data Exchange (DDE)” :)

 

So this is a  nice example of how to do the previous lines in C#:

        //This class provides the infraestructure for DDE comunication
        NDde.Client.DdeClient ddeClient_TextBox1 = null;

        private void Form1_Load(object sender, EventArgs e)
        {
            //I initialize the DDEClient object. Application is Excel and Topic is Sheet1. I'm using the 
            //the TextBox as the syncronization object
            ddeClient_TextBox1 = new NDde.Client.DdeClient("Excel", "Sheet1", textBox1);
            //Connect to the DDE Server
            ddeClient_TextBox1.Connect();
            //Start the Advise Loop
            ddeClient_TextBox1.StartAdvise("R1C1", 1, true, 60000);
            //Setup the Advise Method
            ddeClient_TextBox1.Advise += new EventHandler<NDde.Client.DdeAdviseEventArgs>(ddeClient_TextBox1_Advise);
            //Setup a method to Poke the Server for TextBox cahnges
            textBox1.TextChanged += new EventHandler(textBox1_TextChanged);

        }
        
        void textBox1_TextChanged(object sender, EventArgs e)
        {
            //Syncronous Poking the server
            ddeClient_TextBox1.Poke("R1C1", textBox1.Text + "\0", 4000);
        }

        const string DDE_postFix = "\r\n\0";
        void ddeClient_TextBox1_Advise(object sender, NDde.Client.DdeAdviseEventArgs e)
        {
            //Advise only if needed
            if (e.Text.Length >=DDE_postFix.Length && textBox1.Text + DDE_postFix != e.Text)
                textBox1.Text = e.Text.Substring(0,e.Text.Length-3);
        }
NOTE: Remember that you need to download NDDE and add a reference to this library

This is very good library, you can also set up a lot of Async calls to even improve performance. I have even thought of making an extender as the ToolTip control to add LinkTopic, LinkMode and LinkItem properties for Winforms controls or provide extensions methods to make all the syntax easier, but that is for a future post. Good Luck.