Tom’s Blog

October 7, 2009

Creating a Simple Hotkey Component using RegisterHotKey

Filed under: .NET,C#,P/Invoke,Visual Basic — Tom Shelton @ 12:47 pm

A while ago, I needed to create a simple tray application that had to be able be brought into focus using a global hotkey.  I did a bit of research and came across the RegisterHotKey API function.  On my first attempt at using it, I created a simple class that I used to subclass any window handle that was passed into the constructor.  It  worked well – until you wanted to have more then one hotkey and it seemed to me it might be more user friendly as a component then as a class.  So, I rewrote the code as a windows forms component – and in the process I even created a custom type editor for the HotKey.Modifiers property.

Anyway, I’m posting two projects – the first is the HotKeyLib project that contains the Hotkey component.  The second is a project (both C# and VB.NET) that make use of the component.  You will need to compile the HotKeyLib project and then add it to your IDE’s toolbox to compile the second project.  The reason I did it this way was to show that the custom icon of the hotkey component will indeed show up in the IDE toolbox.

Anyway, hope this code proves helpful.

August 17, 2009

ByVal vs ByRef – What is the Difference?

Filed under: General,Visual Basic — Tom Shelton @ 10:44 pm

Over the years, I’ve noticed that there seems to be a lot of confusion about the difference between passing a parameter ByVal vs. ByRef.    So, I thought I’d spend some time writing something on this topic.  It should be noted, that while I will be discussing this from the view point of VB.NET, the same basic concepts also apply to VB.CLASSIC – it’s just that some of the terminology is different.

First, let’s cover the definition of these two keywords as they are presented in the Visual Basic documentation.

ByVal

Specifies that an argument is passed in such a way that the called procedure or property cannot change the value of a variable underlying the argument in the calling code.

ByRef

Specifies that an argument is passed in such a way that the called procedure can change the value of a variable underlying the argument in the calling code.

On the surface, these two statements seem simple enough.  ByVal (short for By Value) means that the called procedure can not alter the value that was passed.  In essence, when you pass ByVal – a copy of the original value is made and passed to the procedure.  ByRef (short for By Reference) on the other hand is just the opposite.  When, a value is passed ByRef, then a reference – aka pointer (in fact it is more accurate to say that it is a C++ reference then a pointer) – to the original value is passed, so any changes made to this value alter the callers state.  Just to test this out, lets create a little test code and see what happens:

 

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim i As Integer = 0
   8:  
   9:         ' Pass an integer ByVal
  10:         Console.WriteLine("Before ByVal Call: i = {0}", i)
  11:         PassIntByVal(i)
  12:         Console.WriteLine("After ByVal Call: i = {0}", i)
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' Pass an integer ByRef
  17:         Console.WriteLine("Before ByRef Call: i = {0}", i)
  18:         PassIntByRef(i)
  19:         Console.WriteLine("After ByRef Call: i = {0}", i)
  20:  
  21:     End Sub
  22:  
  23:     Sub PassIntByVal(ByVal i As Integer)
  24:         i = 5
  25:     End Sub
  26:  
  27:     Sub PassIntByRef(ByRef i As Integer)
  28:         i = 5
  29:     End Sub
  30:  
  31: End Module

And our output:

output001

So far so good.  It looks exactly like we should expect given the definitions of ByVal and ByRef.  So, if things work according to the definition, why the confusion?   Well, lets expand this simple example a bit and see if things continue to hold up – this time by creating a structure and putting it to the same test as above:

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim i As Integer = 0
   8:  
   9:         ' Pass an integer ByVal
  10:         Console.WriteLine("Before ByVal Call: i = {0}", i)
  11:         PassIntByVal(i)
  12:         Console.WriteLine("After ByVal Call: i = {0}", i)
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' Pass an integer ByRef
  17:         Console.WriteLine("Before ByRef Call: i = {0}", i)
  18:         PassIntByRef(i)
  19:         Console.WriteLine("After ByRef Call: i = {0}", i)
  20:  
  21:         Console.WriteLine()
  22:  
  23:         Dim ts As TestStructure
  24:  
  25:         ' Pass a structure ByVal
  26:         Console.WriteLine("Before ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  27:         PassStructureByVal(ts)
  28:         Console.WriteLine("After ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  29:  
  30:         Console.WriteLine()
  31:  
  32:         ' Pass a strucutre ByRef
  33:         Console.WriteLine("Before ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  34:         PassStructureByRef(ts)
  35:         Console.WriteLine("After ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  36:  
  37:     End Sub
  38:  
  39:     Sub PassIntByVal(ByVal i As Integer)
  40:         i = 5
  41:     End Sub
  42:  
  43:     Sub PassIntByRef(ByRef i As Integer)
  44:         i = 5
  45:     End Sub
  46:  
  47:     Sub PassStructureByVal(ByVal s As TestStructure)
  48:         s.i = 5
  49:         s.j = 10
  50:     End Sub
  51:  
  52:     Sub PassStructureByRef(ByRef s As TestStructure)
  53:         s.i = 5
  54:         s.j = 10
  55:     End Sub
  56:  
  57:     Structure TestStructure
  58:         Public i As Integer
  59:         Public j As Integer
  60:     End Structure
  61: End Module

And our output:

output002

The output should be consistent with our expectations.  Again, what’s the problem?  I’m going to do one more expansion of our code – lets add a custom class into the mix:

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim i As Integer = 0
   8:  
   9:         ' Pass an integer ByVal
  10:         Console.WriteLine("Before ByVal Call: i = {0}", i)
  11:         PassIntByVal(i)
  12:         Console.WriteLine("After ByVal Call: i = {0}", i)
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' Pass an integer ByRef
  17:         Console.WriteLine("Before ByRef Call: i = {0}", i)
  18:         PassIntByRef(i)
  19:         Console.WriteLine("After ByRef Call: i = {0}", i)
  20:  
  21:         Console.WriteLine()
  22:  
  23:         Dim ts As TestStructure
  24:  
  25:         ' Pass a structure ByVal
  26:         Console.WriteLine("Before ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  27:         PassStructureByVal(ts)
  28:         Console.WriteLine("After ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  29:  
  30:         Console.WriteLine()
  31:  
  32:         ' Pass a strucutre ByRef
  33:         Console.WriteLine("Before ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  34:         PassStructureByRef(ts)
  35:         Console.WriteLine("After ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  36:  
  37:         Console.WriteLine()
  38:  
  39:         Dim tc As New TestClass
  40:  
  41:         ' Pass a class ByVal
  42:         Console.WriteLine("Before ByVal Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  43:         PassClassByVal(tc)
  44:         Console.WriteLine("After ByVal Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  45:  
  46:         Console.WriteLine()
  47:  
  48:         ' Pass a class ByRef
  49:         Console.WriteLine("Before ByRef Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  50:         PassClassByRef(tc)
  51:         Console.WriteLine("After ByRef Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  52:  
  53:     End Sub
  54:  
  55:     Sub PassIntByVal(ByVal i As Integer)
  56:         i = 5
  57:     End Sub
  58:  
  59:     Sub PassIntByRef(ByRef i As Integer)
  60:         i = 5
  61:     End Sub
  62:  
  63:     Sub PassStructureByVal(ByVal s As TestStructure)
  64:         s.i = 5
  65:         s.j = 10
  66:     End Sub
  67:  
  68:     Sub PassStructureByRef(ByRef s As TestStructure)
  69:         s.i = 5
  70:         s.j = 10
  71:     End Sub
  72:  
  73:     Sub PassClassByVal(ByVal c As TestClass)
  74:         c.i = 5
  75:         c.j = 10
  76:     End Sub
  77:  
  78:     Sub PassClassByRef(ByRef c As TestClass)
  79:         c.i = 6
  80:         c.j = 11
  81:     End Sub
  82:  
  83:     Structure TestStructure
  84:         Public i As Integer
  85:         Public j As Integer
  86:     End Structure
  87:  
  88:     Class TestClass
  89:         Public i As Integer
  90:         Public j As Integer
  91:     End Class
  92:  
  93: End Module

And the output:

output003

Take a look at the lines of output for the tc variable.  Have we been mislead by the documentation?   Because, on the surface at least, it sure appears as if tc was modified when passed ByVal.   The answer to this question is no – ByVal still worked exactly as it was supposed to. 

To understand why these results are in fact correct, requires a bit of understanding of the .NET type system.  The .NET CTS – or Common Type System – defines two basic type categories (each of which is further subdivided).  These categories are Value types and Reference types.  Here is what the documentation has to say regarding these two categories:

    • Value types

      Value types directly contain their data, and instances of value types are either allocated on the stack or allocated inline in a structure. Value types can be built-in (implemented by the runtime), user-defined, or enumerations. For a list of built-in value types, see the .NET Framework Class Library.

    • Reference types

      Reference types store a reference to the value’s memory address, and are allocated on the heap. Reference types can be self-describing types, pointer types, or interface types. The type of a reference type can be determined from values of self-describing types. Self-describing types are further split into arrays and class types. The class types are user-defined classes, boxed value types, and delegates.

Summary:  A value type variable directly contains it’s data, a reference type variable does not.   In practical terms, this means that the behavior demonstrated by the example code is in fact completely consistent with the documented behavior of ByVal and ByRef.  When passing ByVal, the copy that is made is the contents of the variable – in the case of a reference type, the value that the variable contains is not the instance, but the memory address of the instance on the heap.  Maybe a small diagram will help clarify:

heap

What this means in practical terms is that, if you pass an object ByVal – the objects properties can be modified, but the reference can not:

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim tc As TestClass = Nothing
   8:  
   9:         ' pass by value
  10:         Console.WriteLine("Passing tc ByVal")
  11:         CreateTCByVal(tc)
  12:         Console.WriteLine(If(tc IsNot Nothing, String.Format("tc.i = {0}, tc.j={1}", tc.i, tc.j), "tc is Nothing"))
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' pass by reference
  17:         Console.WriteLine("Passing tc ByRef")
  18:         CreateTCByRef(tc)
  19:         Console.WriteLine(If(tc IsNot Nothing, String.Format("tc.i = {0}, tc.j={1}", tc.i, tc.j), "tc is Nothing"))
  20:     End Sub
  21:  
  22:     Sub CreateTCByVal(ByVal c As TestClass)
  23:         c = New TestClass()
  24:         c.i = 5
  25:         c.j = 6
  26:     End Sub
  27:  
  28:     Sub CreateTCByRef(ByRef c As TestClass)
  29:         c = New TestClass()
  30:         c.i = 5
  31:         c.j = 6
  32:     End Sub
  33:  
  34:     Class TestClass
  35:         Public i As Integer
  36:         Public j As Integer
  37:     End Class
  38:  
  39: End Module

And the output:

output004

I hope this brief explanation of ByVal and ByRef can help clear up some of the confusion that seems to exist regarding these to argument passing methods.

May 11, 2009

A P/Invoke Primer for the Experienced VB6 Programmer

Filed under: .NET,P/Invoke,Visual Basic — Tom Shelton @ 10:52 pm

"In VB6 I used to use SomeSuchAPI to perform task X – I have tried to use SomeSuchAPI in my new SuperVelocityStrumCruncher app written in VB.NET, but it won’t work!  Help!"  And on goes the refrain. 

When I see these types of questions, nine times out of ten, I know the answer before I even read the question.  Or, that is to say, I can pretty much surmise that the answer will fall into one of a couple of typical traps that the experienced VB6 user faces when deciding to use P/Invoke in VB.NET (which, is a pretty relevant question because many times the task you were using the API for in VB6, may just be taken care of by the .NET framework).

So, I’m going to cover a few tips that should help the experienced VB6 user successfully make use of Windows or other API calls from VB.NET.


Know Your Data Types

That’s right – know your data types.  For better or for worse, many of the integral data types that you used to know and love in VB6 have changed sizes in VB.NET.  For example, Long is no longer a 32-bit Integer – it’s 64-bit.  So, forget what you knew about integer types and their sizes in VB6 and learn them a new in VB.NET.

Here is a quick conversion table that might help speed along this task:

Size/Sign VB6 Data Type VB.NET Data Type
8 bits/unsigned Byte Byte
8 bits/signed N/A SByte
16 bits/unsigned N/A UShort
16 bits/signed Integer Short
32 bits/unsigned N/A UInteger
32 bits/signed Long Integer
64 bits/unsigned N/A ULong
64 bits/signed N/A Long
This table is based on the information found here.

So…  What does all of this mean in the actual context of a Windows API call?  Well, lets look at the GetWindowsDirectory API call.  A typical declaration for this in VB6 might be:

   1: Private Declare Function GetWindowsDirectory Lib "KERNEL32" Alias "GetWindowsDirectoryA" _
   2:     (ByVal lpBuffer As String, ByVal nSize As Long) As Long

This call is incorrect in VB.NET – we can see that we are passing a Long value to the nSize parameter, which is a 32-bit integer in VB6, but is 64-bit in VB.NET.  This can cause problems as it can unbalance the functions call stack.  To correct this we can declare the function in VB.NET like so:

   1: Private Declare Function GetWindowsDirectory Lib "KERNEL32" Alias "GetWindowsDirectoryA" _
   2:     (ByVal lpBuffer As String, ByVal nSize As Integer) As Integer

If you were to use this declaration in VB.NET, it would work – but, there are a couple of other levels of enlightenment that can be achieved here on our way to API Nirvana! 

Drop The Alias

Any one else remember the bad old days when a seemingly simple call to an API call from VB could cause gyrations such as these to occur on NT based OS’s?

funny_conversion_flow_chart

Good Times!  Good Times!

Well, fortunately, the VB.NET marshaller has improved significantly when it comes to handling of strings.  For instance, it can call the W functions directly without the need for hacks using StrPtr or creating custom API typelibs.  And, as if that wasn’t good news, it actually gets better!  You don’t even have to alias your functions anymore. 

The venerable old Declare statement has a new option in VB.NET that lets you declare the character set that should be used when calling the function.  There are 3 choices – Ansi, Unicode, and Auto.  When dealing with the A/W function set of the Windows API, Auto is often a good choice because you can just declare the function and let the .NET runtime worry about if it should call the A or the W version.

Here’s our function declaration again with a bit more .NET goodness:

   1: Private Declare Auto Function GetWindowsDirectory Lib "KERNEL32" _
   2:     (ByVal lpBuffer As String, ByVal nSize As Integer) As Integer

Isn’t that nice!

Mind Your Buffers

Ok, I want to point out one more minor adjustment that probably should be made to our Declare statement.  In the name of correctness, since the buffer we are passing in to this function is intended to be filled by the API call, we should not be passing the lpBuffer parameter as String.  See in a nod to compatibility with it’s predecessor, the VB.NET marshaller – unlike C#’s – does not respect the immutability of strings and will go through the extra gyrations necessary to make sure you get the result you have come to expect in your long association with VB.CLASSIC.  With out getting into arguments about computability vs correctness – I’m simply going to show you the recommended way of declaring string buffers that are intended to be filled by called API’s and you are free to make your own decision as to which way to go with this:

   1: Private Declare Auto Function GetWindowsDirectory Lib "KERNEL32" _
   2:     (ByVal lpBuffer As System.Text.StringBuilder, ByVal nSize As Integer) As Integer

Keep in mind, the use of StringBuilder is only recommended when the string is intended to be modified by the called function – if it’s a simple constant string buffer, then String is definitely the appropriate type.  Here is a complete sample using our declare statement:

   1: Option Strict On
   2: Option Explicit On
   3:  
   4: Imports System
   5: Imports System.Text
   6:  
   7: Module Module1
   8:     Private Const MAX_PATH As Integer = 260
   9:     Private Declare Auto Function GetWindowsDirectory Lib "KERNEL32" _
  10:         (ByVal lpBuffer As StringBuilder, ByVal nSize As Integer) As Integer
  11:  
  12:     Sub Main()
  13:         Console.WriteLine(GetWindowsDirectory())
  14:     End Sub
  15:  
  16:     Private Function GetWindowsDirectory() As String
  17:         Dim buffer As New StringBuilder(MAX_PATH)
  18:         GetWindowsDirectory(buffer, buffer.Capacity)
  19:         Return buffer.ToString()
  20:     End Function
  21: End Module

Handle Your Handles

The last bit of advice that I’m going to render in this article is about handles.  In the Windows API the concept of handles is very important, and handles are used all over the place.  When ever you are dealing with a handle type (such as HWND), then it is recommended that you use System.IntPtr as the type for that parameter over Integer.  The main reason for this is that the IntPtr is a wrapper over an unsigned system sized integer – so that means that it is 32-bit on 32-bit systems and 64-bit on 64-bit systems.  See how that works?  Anyway, it might make your porting life in the future easier if you remember that little tip.

October 1, 2008

Convert a Long File Name to a Short File Name

Filed under: P/Invoke,Visual Basic — Tom Shelton @ 4:26 am

Recently, on microsoft.public.dotnet.languages.vb group, help was asked in getting the code snip-it found here to work in VB2008.  Here is a conversion of the step-by-step for use in VB.NET (this should work in all versions of VB.NET)

  1. Start a new Windows Forms project
  2. Add a button control to the default Form1
  3. Add a OpenFileDialog to the default Form1
  4. From the Project Menu, select Add Module
  5. Add the following code to the new module:
    Option Explicit On
    Option Strict On
     
    Imports System.Text
     
    Module Module1
        Private Declare Auto Function GetShortPathName Lib "kernel32" ( _
            ByVal lpszLongPath As String, _
            ByVal lpszShortPath As StringBuilder, _
            ByVal cchBuffer As Integer) As Integer
     
        Public Function GetShortPath(ByVal longPath As String) As String
            Dim requiredSize As Integer = GetShortPathName(longPath, Nothing, 0)
            Dim buffer As New StringBuilder(requiredSize)
     
            GetShortPathName(longPath, buffer, buffer.Capacity)
            Return buffer.ToString()
        End Function
    End Module
  6. Add the following code to Form1:
    Option Strict On
    Option Explicit On
     
    Imports System
    Imports System.Text
     
    Public Class Form1
     
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            If OpenFileDialog1.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                Dim msg As New StringBuilder()
                msg.AppendFormat("Long File Name: {0}", OpenFileDialog1.FileName)
                msg.AppendLine()
                msg.AppendFormat("Short File Name: {0}", GetShortPath(OpenFileDialog1.FileName))
                MessageBox.Show(msg.ToString())
            End If
        End Sub
    End Class
  7. Run the project using Ctrl+F5 and click the button.
  8. Navigate to a file with  a long path and select ok.
  9. The message box with both the long and short versions of the file name should appear.

Powered by WordPress