Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades

11,533 views

Published on

This document helps users deal with most issues they will face when transitioning VB6 applications to VB.NET. Many examples of solutions are featured.

Published in: Technology, News & Politics
1 Comment
2 Likes
Statistics
Notes
No Downloads
Views
Total views
11,533
On SlideShare
0
From Embeds
0
Number of Embeds
10
Actions
Shares
0
Downloads
339
Comments
1
Likes
2
Embeds 0
No embeds

No notes for slide

Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades

  1. 1. Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades “Addressing Changes to the Visual Basic Language from VB6 to VB.NET, and how to deal with those changes.” By David Ross Goben Copyright © 2010-2014 by David Ross Goben All rights reserved. (Last updated October 25, 2015) (The above two logos are registered trademarks of Microsoft Corporation) NOTE: If you have trouble downloading a PDF copy of this document, go to Google Docs and download it freely: https://docs.google.com/open?id=0B_Dj_dKazINlMjViMGUzZTUtMWFiZS00ZGNhLWE1NjEtMDQ4NjcwNmNiOTFm
  2. 2. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –2– TABLE OF CONTENTS Introduction.................................................................................................................................................. 4 1. Dealing with structures passed to P/Invokes containing strings................................................................................. 7 2. Dealing with structures passed to P/Invokes not containing strings........................................................................... 7 3. Dealing with the loss of the VB6 BackStyle property................................................................................................. 7 4. Dealing with passing strings directly to P/Invokes..................................................................................................... 8 5. Dealing with VarPtr, ObjPtr, StrPtr, VarPtrArray, and StrPtrArray.............................................................................. 8 6. More on StrPtr........................................................................................................................................................ 8 7. Dealing with passing strings ByRef to P/Invokes ...................................................................................................... 9 8. Dealing with fixed-length strings ............................................................................................................................ 10 9. Dealing with fixed-length arrays Within Structures.................................................................................................. 13 10. Dealing with RichTextBox Property Renaming....................................................................................................... 14 11. Dealing with recovering the LenB() function........................................................................................................... 15 12. Dealing with passing parameters to P/Invokes “As Any”......................................................................................... 16 13. Dealing with StrConv: converting between Unicode and ANSI strings..................................................................... 17 14. Dealing with AddressOf/Missing Delegate issues................................................................................................... 19 15. Dealing with Dir() function warnings....................................................................................................................... 21 16. Dealing with Item issues in Collections .................................................................................................................. 21 17. Dealing with late-bound Object references............................................................................................................. 22 18. Dealing with VB6 parameterless defaults............................................................................................................... 22 19. Dealing with VB6 Null Propagation ........................................................................................................................ 23 20. Dealing with referencing Objects before they are initialized .................................................................................... 23 21. Dealing with TextChanged and Resize events firing before the Form Load event.................................................... 23 22. Dealing with renamed properties ........................................................................................................................... 24 23. Dealing with the loss of the ListCount property....................................................................................................... 24 24. Dealing with changed MousePointer warnings....................................................................................................... 24 25. Dealing with changes to RECT structures.............................................................................................................. 25 26. Dealing with the loss of the Initialized and Terminate events .................................................................................. 26 27. Dealing with changes to Enumeration references................................................................................................... 26 28. Dealing with VB6 Namespace Twips conversions .................................................................................................. 27 29. Dealing with user-defined Twips constants ............................................................................................................ 27 30. Speeding code by removing references to the VB6 Compatibility Library ................................................................ 28 31. Speeding returned VB6 Namespace List Item values............................................................................................. 28 32. Dealing with changed Date/Time shortcut format options ....................................................................................... 29 33. Dealing with Date value conversions ..................................................................................................................... 29 34. Speeding Format command use in VB.NET........................................................................................................... 30 35. Dealing with Screen properties.............................................................................................................................. 30 36. Dealing with On Iexpr GOTO................................................................................................................................. 31 37. Dealing with On Iexpr GoSub................................................................................................................................ 34 38. Dealing with updating VB6 error trapping............................................................................................................... 34 39. Dealing with destroying Objects............................................................................................................................. 35 40. Dealing with changes to Common Dialogs............................................................................................................. 36 41. Dealing with VB6.CopyArray ................................................................................................................................. 38 42. Dealing with the loss of the ItemData List Object property ...................................................................................... 39 43. Dealing with changes to Font manipulation............................................................................................................ 40 44. Dealing with changes to Form commands.............................................................................................................. 44 45. Dealing with VB6’s automatic Boolean conversions................................................................................................ 46 46. Dealing with Option Strict On issues...................................................................................................................... 47 47. Dealing with Image and Picture Object upgrades ................................................................................................... 48 48. Dealing with the loss of VB6 Control Lists and how to recover their functionality ..................................................... 49 49. Dealing with changes to MouseMove parameter list changes ................................................................................. 52 50. Dealing with changes to remotely firing Button clicks.............................................................................................. 52 51. Dealing with no AVI animation control in VB.NET and how to easily add a free one................................................. 52 52. Dealing with changes to Resources Management.................................................................................................. 54 53. Dealing with the loss of the App statement............................................................................................................. 57 54. Dealing with updating default ByRef and ByVal method parameters flags ............................................................... 57 55. Dealing with Collection and List clearing................................................................................................................ 58 56. Dealing with Adding Auxiliary Files to ClickOnce Deployment (Publish) .................................................................. 59 57. Dealing with changes to Text Box SelStart and SelLen Properties.......................................................................... 60 58. Dealing with changes to ToolTips.......................................................................................................................... 60 59. Dealing with changes to ListView .......................................................................................................................... 61 60. Dealing with changes to Toolbar Button and Button Menu Clicks............................................................................ 61
  3. 3. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –3– 61. Dealing with Unload Form commands ................................................................................................................... 63 62. Dealing with The Loss of the VB6 NewIndex Property............................................................................................ 64 63. Dealing with Process Handling in the KeyPress, KeyDown, and KeyUp Events....................................................... 64 64. Dealing with Invoking Handled Events Under VB.NET............................................................................................ 65 65. Dealing with TextBox Locked Property Changes.................................................................................................... 67 66. Dealing with changes to the Tag Property.............................................................................................................. 67 67. Dealing with Changes to the GotFocus and LostFocus Events ............................................................................... 67 68. Dealing with Long-Pathing Through Namespaces.................................................................................................. 68 69. Dealing with Changes to the VB6 SetFocus Command .......................................................................................... 68 70. Dealing with Changes to Multiple Document Interfaces .......................................................................................... 68 71. Dealing with Changes to a Button’s Cancel and Default Properties......................................................................... 67 72. Dealing with Changes to CheckBoxes ................................................................................................................... 69 73. Dealing with Property Conflicts With VB Commands .............................................................................................. 69 74. Dealing with using Icons for Menu Images (Bitmaps) under VB.NET....................................................................... 70 75. Dealing with Converting VB6 Fast FSO File I/O to Faster VB.NET Streaming Methods ........................................... 70 76. Dealing with Changes to Counting CheckBoxed ListBoxes..................................................................................... 71 77. Dealing with Changes to Mouse Pointer Icons ....................................................................................................... 71 78. Dealing with Specific Changes to the KeyDown and KeyPress Events.................................................................... 72 79. Dealing with Changes to Drag and Drop................................................................................................................ 74 80. Dealing with the Loss of MAPI Controls ................................................................................................................. 77 81. Dealing with Displaying a Checkbox as a Button.................................................................................................... 77 82. Dealing with some Click Events changing to SelectedIndexChanged Events .......................................................... 78 83. Dealing with VB6-style printing under VB.NET....................................................................................................... 79 84. Dealing with the Form Cursor not displaying over Form Controls............................................................................ 80 Closing Remarks ....................................................................................................................................................... 82 About the Author....................................................................................................................................................... 83 Free Online PDF Documents Available by David Ross Goben................................................................................. 84 Open Letters Sent to Advocates for the Electric Universe and Expansion Tectonics Theories................................. 84 Navigating Your Way through Visual Basic 6.0 to Visual Basic .NET Application Upgrades .................................... 85 Enhancing Visual Basic .NET Applications Far Beyond the Scope of Visual Basic 6.0............................................ 86 Doom 3 Walkthrough and Strategy Guide ............................................................................................................. 87 Also Available from the Author................................................................................................................................. 88 A Gnostic Cycle: Exploring the Origin of Christianity............................................................................................... 88 NOTE: This manual was first written when VB2008 was still a new product. Presently VB2013 is the current contender, but the contents of this manual are still fully applicable to the VB2010, VB2012, VB2013, and VB2015 environments. Further, the Upgrade Wizard referenced herein was last available in VB2008, which is still available in its free Microsoft Visual Basic 2008 Express edition. Also, it is my feeling that VB2008 has so far actually been their very best effort, where Microsoft ® finally fully realizing their .NET philosophy in Visual Basic. Although VB2005 was a decent start, I have always looked at it as more of a “Release Candidate” for Visual Basic .NET, because translating between VB6 and VB2005 was still sometimes a rather tricky, and often frustratingly complicated effort, almost always never worth all the fuss. VB.NET betas, VB2002, and VB2003 were extremely poor implementations that were filled with too often convoluted, complicated ‘trash’ that should have never been found or accepted within the .NET environment. It is my feeling that they should have never bothered releasing any of that code as a salable item, but releasing it only to those who wanted to freely download and test it, and to report bugs and enhancement suggestions. It is my strongest feeling that VB6 should have been fully and aggressively supported through the release cycle of VB2008, not the marginalized nodding support that it received through the release of VB2008. Until that time, Microsoft should have more assertively supported extensions to forms and the ability to access .NET forms, workspaces, and code until that time (as it was, they did in fact do this, but no one in the VB6 community ever seemed to be aware of it), easing the VB6 transition from its procedural code platform roots to the more powerful Object Oriented world of .NET. Unlike the claims made by some critics, everything that can be done in VB6 can be easily accomplished in .NET as of VB2008, no longer requiring convoluted alternative solutions or intense, deep research to find a compatible alternative. However, many developers do not have a need for object-based programming, and VB6 still fills that procedural language niche for VB.
  4. 4. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –4– Introduction When a Visual Basic 6.0 (VB6) application is upgraded to Visual Basic .NET (VB.NET; to at least VB2008) using the Visual Basic Upgrade Wizard (you can still download VB2008 Express to get this upgrade wizard), chances are that, once the upgraded application comes up in Visual Studio or Visual Basic Express, its Task List will present you with a number of, or typically quite a number of alerts, upgrade issues, to-dos, warnings, notes, global warnings, run-time warnings, and design issues. DO NOT PANIC! It is not as bad as you might at first think it is. Most of them are notices, and are only that: Notices. And virtually all of these added tagged comments can be safely reviewed, ignored, and then deleted without fanfare. Of those that actually do require attention; most can be solved in very short order using quick and easy edits. The numbered solution points provided in this document will solve almost every one of your upgrade woes. These solutions will work when you are upgrading to either the full Visual Studio.NET editions, or to the Visual Basic .NET Express Editions. But before diving into those solutions, you should also understand exactly why all these upgrade issues exist in the first place; why the current generation of Visual Basic has just a little bit of trouble upgrading from the previous generation (VB6). It is not, as many detractors will most vehemently (but without any merit) contrarily shout, because VB is now somehow broken (from my own personal point of view, I believe that it is the other way around – VB6 was a patchwork of hacks, fixes, and upgrades that did not seem to always follow uniform standards of syntax). But the best answer is that it is because VB.NET is now what those VB6 programmers desperately wished for VB to be (so be careful what you wish for). When the majority of VB6 developers demanded the ability to build VB code in a common language IDE (Integrated Development Environment), have 100% unfettered cross-language interoperability, and expecting no less than 100% unrestricted object oriented programming language capabilities, most of them did not have the first clue, not the slightest understanding, of the earth-shattering impact their passionate, relentless, spittle-laced howling would have on their beloved VB. An immense host of them, mostly amateur programmers and hobbyists, naively believed that after such a necessarily monumental undertaking, they could simply continue on their merry little way, writing VB code exactly as they had done before, using the very same often non-uniform syntax that they had been using before, and, oh yes, they expected there would be a few additional commands here and there to address full class inheritance, and also allow seamless access to methods whose source code was written in some other programming language, such as C++. They did not grok the fact that in order to provide them with exactly what they were keenly expecting would also clearly necessitate colossal changes to their beloved VB so that it would be a fully integrated, object oriented environment that would also interoperate with and act exactly like the other Visual Studio languages (or other .NET-compliant languages). This requires perfect synchronicity between all those languages; that each of them could clearly understand and use objects from each other without the slightest misconstruction. Though most VB6 users feel they are alone in this upgrade quandary, they most certainly are not. They have it easy compared to some language upgrades. Try translating between C (C89; K&R C) and C99, or C++85 and C++95, or better, between upgrades of the very first high level language, FORTRAN (Formula Translation), between FORTRAN57 and FOTRAN77, or even FORTRAN95. Upgrades bite everyone. Regardless of whether you write something in VB.NET, C#, C++, Delphi.NET, Google Chrome, or even IL (Intermediate Language – Microsoft’s brilliant cross-platform assembly language), their syntax must always sync up perfectly. In fact, code written in one language should be instantly translatable into one of the other languages based solely on the compiled IL instructions from any of those languages. For example, suppose you wrote the following VB.NET code within Form1 so that you could emulate the old VB6 command “ZOrder(Me, 0)”, to bring your form to the front of the window display stack: Public Sub ZOrder(ByVal frm As Form, ByVal Position As Integer) If (Position = 0) Then frm.BringToFront()'use VB.NET function to bring the form to the top of the z-order Else frm.SendToBack() 'use VB.NET function send the form to the back of the z-order End If End Sub
  5. 5. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –5– This code is compiled into Microsoft Intermediate Language (MIL) as the following: method public instance void ZOrder(class [System.Windows.Forms]System.Windows.Forms.Form frm, int32 Position) cil managed { .maxstack 8 L_0000: ldarg.2 L_0001: ldc.i4.0 L_0002: bne.un.s L_000c L_0004: ldarg.1 L_0005: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::BringToFront() L_000a: br.s L_0012 L_000c: ldarg.1 L_000d: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::SendToBack() L_0012: ret } The above machine independent language can then be immediately translated into C#: public void ZOrder(Form frm, int Position) { if (Position == 0) { frm.BringToFront(); } else { frm.SendToBack(); } } Or into C++: public: void __gc* ZOrder(Form __gc* frm, Int32 __gc* Position) { if (Position == 0) { frm->BringToFront(); } else { frm->SendToBack(); } } Or into Delphi.NET: procedure Form1.ZOrder(frm: Form; Position: Integer); begin if (Position = 0) then frm.BringToFront else frm.SendToBack end; Or into Google Chrome: method Form1.ZOrder(frm: Form; Position: Int32); begin if (Position = 0) then frm.BringToFront else frm.SendToBack end; Or into any other .NET-compliant language. But to achieve such a fluid transition, one cannot hide a lot of code. So much of the code that was previously hidden away from VB6 developers had to be brought out into the open. The fact that it had been hidden in the first place was an attempt to simplify the language to fully empower it as a RAD (Rapid Application Development) platform, which that end most certainly achieved, but it was also one of the main reasons why many C++ programmers looked down their noses at VB6, chiding it as a tinker toy language, because the total breadth, the total power, and the total unreserved system control that VB developers could otherwise wholly exploit was withheld from their grasp, which in my view severely handicapped VB developmental capabilities, limiting them far beyond that which most might venture to imagine, and, as Microsoft later agreed, this tactic also severely inhibited VB’s RAD potential.
  6. 6. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –6– Having said that, am I a fan of VB6? Yes, I am. But will I continue to use it? I doubt it, except for maintenance purposes, to support my customer base who have invested heavily in VB6 software and either have no desire to upgrade, or do not currently wish to incur the expense or can afford the time of an upgrade. But I like too much the colossal power and the magnificent freedom I enjoy while using VB.NET to want to turn back to VB6. In fact, I have become so comfortable in VB.NET that I may now have trouble writing original code in VB6 again, mainly because of the conflicting syntax between controls and commands that VB6 permitted, or even required, is more complicated than the smoothly consistent syntax of VB.NET. I have always stressed the point that, because VB6 was a serious RAD platform, a developer could write in two hours what would take two weeks to match it in robustness using C++. I stand firm that this declaration is now even more true of VB.NET (this explains why I have finally made a full migration from C++). By the time VB2005 came out, VB.NET was clearly coming into its own as the new RAD platform of choice. The fact that its free version, VB2005 Express, was a full VB.NET compiler, with its only limitations being that it did not support direct interoperability with other .NET languages (which most developers do not bother with, anyway), it was missing a few professional-level templates, and it lacked support of a number of other features made available only to the full Visual Studio environment, such as the free Code Rush! Express editor enhancements (see www.devexpress.com/Products/Visual_Studio_Add-in/CodeRushX/). It also featured much stronger compatibility to VB6 code than VB2003 offered. VB2008 and VB2010 exhibit far greater compatibility to VB6, and they are in fact the only VB.NET platforms that I will recommend to VB6 users who have yet to migrate to the .NET domain. Like VB2005, free VB2008 and VB2010 Express editions are also available (see www.microsoft.com/express/download), plus Microsoft has also provided a number of excellent, previously pricey books regarding VB6 to VB.NET migration on hand for free download (see the end of this document for details and their web links). On top of that, the many numbered points listed in this document will make addressing pesky warnings and upgrade issues a snap to deal with. Not only will these points help you address those issues, but they will also hopefully give you a glimpse into the VB.NET approach to application development, and with any luck I will also be able to convey my excitement for this extremely potent and much more empowering rendition of Visual Basic, which is now a true force to be reckoned with; it finally being just as capable, just as powerful, but can be developed much faster and can be executed just as fast as the other contenders in the development world, such as VC++. The lists of notes in this document were initially compiled from personal references I had jotted down as I made my own migration over to VB.NET from VB6. It started out as a 6-page hodge-podge of terse cuff notes, but continued to grow as I learned new and better ways of adapting VB6 code to the .NET environment, first to VB2002 (a painful experience), then VB2003 (a little less painful), VB2005 (a rather pleasant upgrade), VB2008 (a very easy upgrade), and now VB2010. Prior to the release of VB2005, transitioning VB6 code to the .NET platform was in most cases, except for the simplest of applications, a dreadfully rocky path, fraught with aggravation and dead ends that often required major re-writing for segments of code. When VB2005 arrived on the scene, I noticed that massive pages of personal upgrade notes were now no longer required, and so they were duly discarded. When VB2008 was released, far greater amounts of my upgrade notes were no longer relevant. Indeed, VB2008 is probably the very first edition of VB.NET that I would even consider recommending to people who were thinking about making the transition over from VB6. VB2005 and VB2008 were focused strongly on the language itself and upon VB6 compatibility. VB2010 is geared mostly toward ADO.NET, web development, and business transactions. With just a little more tweaking, and I think these notes will shrink back to their original 6 pages, if not eliminate their need entirely. We can only dream. And now: on to those solutions… VERY IMPORTANT NOTICE: Every solution involving GCHandles, embracing angle brackets denoting Attributes “<>”, or have Marshaling instructions will simply assume that you will have also placed the following line in the heading of any file they are involved in; below any Option statements, if any, but before the Class or Module declaration: Imports System.Runtime.InteropServices
  7. 7. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –7– 11.. Dealing with structures passed to P/Invokes containing strings If a Structure (a VB6 User-Defined Type) is passed to a P/Invoke (a Processor Invocation; in VB6 parlance – an API Call) and it contains at least one string member that is expected to be of 8-bit ANSI Chars (as are most string-handling Win32 P/Invokes used by VB6), always be sure to marshal the structure by preceding it with the following special attribute: “<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>”. This ensures that all structure members are actually stored sequentially in memory in the exact order declared, Unicode/ANSI string conversion will be automatic between the application and the invoked code, and that all is packed tightly together; duplicating exactly what was actually done by default under VB6 (most Win32 DLLs process 8-bit ANSI text, but VB, both VB6 and VB.NET, plus newer Win32 DLLs, actually locally use 16-bit Unicode text). This attribute may not always be needed, but it is better to have it and not need it, than need it and not have it. For example: '*************************************************************** ' structure for system version information (Win32 P/INVOKE) '*************************************************************** <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _ Private Structure OSVERSIONINFO 'force what was done for VB6 by default byhind the scenes Dim dwOSVersionInfoSize As Integer Dim dwMajorVersion As Integer Dim dwMinorVersion As Integer Dim dwBuildNumber As Integer Dim dwPlatformId As Integer <VBFixedString(128)> Dim szCSDVersion As String 'Fixed maintenance string (128 8-bit characters) End Structure NOTE: You will want to change the ‘Auto’ CharSet to ‘Unicode’ if the P/Invoke instead expects 16-bit Unicode text. 22.. Dealing with structures passed to P/Invokes not containing strings If a Structure is passed to a P/Invoke but it does not contain strings, or if the P/Invoke will process those strings as 16-bit Unicode, you can instead precede the structure declaration with the following attribute, even if it is not really needed (though I usually still use the previous string template, even if I have to set Charset:=Charset.Unicode, because it would then become more of a habit): <StructLayout(LayoutKind.Sequential)> 33.. Dealing with the loss of the VB6 BackStyle property One upgrade warning that may frustrate more than a few users is reported when a control’s Backstyle is being modified. Consider the following upgraded VB6 code: frmMain.lblLoc.BackColor = Color.White 'set label background color to White 'UPGRADE_ISSUE: Label property lblLoc.BackStyle is not supported at runtime. Click for more: BLAH-BLAH-BLAH frmMain.lblLoc.BackStyle = 1 'set label BackStyle to opaque in order to show the color White Under VB6, the BackStyle was by default set to 0 (Transparent). If set to 1, its background was set to Opaque, which allowed the color set in the control’s BackColor property to be displayed. Under VB.NET, these two properties were merged; it was such a waste of resources to have a BackColor property that was in most cases unused. This added to an overall heavier code overhead. Therefore, the BackColor property, when set to a color, determined the background color of the object, and logically forced the back style to be opaque. By setting the BackColor to the new color value Color.Transparent, the background and back style would be logically rendered transparent. To correct the above code, we simply delete the line setting BackStyle, because by setting the BackColor property under VB.NET to a non-Transparent color, we have in fact set it to opaque. If you want to set it to transparent, use something like “frmMain.lblLoc.BackColor = Color.Transparent”, though you would still delete the BackStyle setting line: “frmMain.lblLoc.BackStyle = 0”. NOTE: Even with the BackColor set to Transparent, its background assumes the color of its parent; the form (its default parent). To make it transparent to a control behind it, at runtime you must set the object’s Parent to the control it is over, and adjust its Left and Top properties to those relative to its new parent. For example, on a form you have a label (Label1) and a PictureBox (PictureBox1). To display the label over the PictureBox with the background transparent on the image, within the Form_Load event, enter this: 'Set Label1 over Picture1, relative to their current positioning With Me.Label1 .Parent = Me.PictureBox1 'set Label1 parent to Picture1 .BackColor = Color.Transparent 'set Label1 background color to transparent .Left = .Left - .Parent.Left 'adjust Label1’s location to be relative to its new parent .Top = .Top - .Parent.Top End With
  8. 8. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –8– 44.. Dealing with passing strings directly to P/Invokes If a string is passed as a parameter to a P/Invoke, you should usually be sure to also include the Auto verb after Declare in the P/Invoke declaration statement if the P/Invoke will be expecting to process 8-bit ANSI text, especially in declarations that to do not have an Alias modifier. These take the forms “Declare Auto Function” and “Declare Auto Sub”. The additional “Auto” verb will tell VB.NET to automatically convert its string parameter(s) to/from Unicode/ANSI when being passed to/from P/Invoke procedures. However, if the P/Invoke will also specify an Alias that will explicitly declare the method to in fact invoke, then do not specify Auto after Declare. The reason for this is that .NET would otherwise double-convert the text; once to address the Auto specification, and a second time for the direct specification of the method, which the .NET compiler will know will be either ASCII or Unicode. For example, consider the following declarations for GetTempPath(), which will return to us the directory path to the system’s TEMP folder. The following signature using the Alias verb is valid: Private Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" _ (ByVal nBufferLength As Integer, _ ByVal lpBuffer As String) As Integer 'Valid using Alias This alternative declaration using the Auto verb (and no Alias) is also valid: Private Declare Auto Function GetTempPath Lib "kernel32" _ (ByVal nBufferLength As Integer, _ ByVal lpBuffer As String) As Integer 'Valid using Auto (or Unicode or ANSI) But this declaration, specifying both Auto and Alias, is not, and will return non-recognizable text: Private Declare Auto Function GetTempPath Lib "kernel32" Alias "GetTempPathA" _ (ByVal nBufferLength As Integer, _ ByVal lpBuffer As String) As Integer 'This will generate invalid text data NOTE: The execution of this erroneous code will not blow up or crash the computer, but your text will look as though it had gone through a kitchen blender set on Liquefy Mode. Also, you can actually get the Temp folder from VB.NET using “Dim Temp As String = Environment.GetEnvironmentVariable("Temp")” 55.. Dealing with VarPtr, ObjPtr, StrPtr, VarPtrArray, and VarPtrStrArray If your upgraded VB6 code requires the undocumented VB6 functions VarPtr, ObjPtr, StrPtr, VarPtrArray, or VarPtrStrArray, use these VB.NET functions to replace and duplicate them: Imports System.Runtime.InteropServices '****************************************************************************** ' This Module Provides the addresses of Numeric Variables, Objects, and Strings '****************************************************************************** Module modVarPtr 'VB.NET version of VB6 VarPtr 'also works for VarPtrArray Public Function VarPtr(ByVal o As Object) As Integer 'use Object as a 'catch all' universal data type Dim GC As GCHandle = GCHandle.Alloc(o, GCHandleType.Pinned) 'get a trackable handle and pin the object address VarPtr = GC.AddrOfPinnedObject.ToInt32 'get (and return) the memory address of the pinned object GC.Free() 'free the allocated space used End Function 'VB.NET version of VB6 ObjPtr Public Function ObjPtr(ByVal o As Object) As Integer 'note that these additional methods actually simply use VarPtr() Return VarPtr(o) 'use VarPtr -- it will return the address of the VB.NET object End Function 'VB.NET version of VB6 StrPtr 'also works for VarPtrStrArray Public Function StrPtr(ByVal o As String) As Integer Return VarPtr(o) 'use VarPtr -- it will return the address of the VB.NET string End Function End Module 66.. More on StrPtr If your VB6 code used the StrPtr() function to pass the 32-bit address of the string, be aware that strings are no longer stored in the ancient BSTR format, which was itself a pointer into a Unicode string. Yet, like VB6, if one passes a string ByVal to a function you are basically performing the function of StrPtr(). Hence, in most cases the StrPtr() function is not needed under VB.NET, just as it was seldom needed under VB6. Even so, if you do need to pin down and pass or manipulate the actual address of the string as a 32-bit Integer value, then of course refer back to the previous point. NOTE: This additional VB6-compatible feature of ByVal was introduced in VB2005 in order to enhance compatibility to VB6 and to simplify program conversion from it. Previous to this update, under VB.NET you had to pass a string ByRef and also include a special VBByRefStr attribute, which shall be discussed in the next point.
  9. 9. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –9– 77.. Dealing with passing strings ByRef to P/Invokes If a string must be passed ByRef to a non-CLR-managed Win32 interop P/Invoke in VB.NET because that process will need to alter that text string or needs to assign a string of text to it, most gurus instruct you to marshal it with VBByRefStr. For example, if the VB6 API parameter ByRef declaration was: ByRef myString As String 'Note that, under VB6, this is the VERY SAME as using: ByVal myString As String Upgrade it so that a string ByRef actually works for a P/Invoke under VB.NET like this: <MarshalAs(UnmanagedType.VBByRefStr)> ByRef myString As String NOTE: Passing strings ByRef, as a rule, does pass them By Reference, such as when passing it to a VB.NET method, but an interop P/Invoke is a special exception to ByRef processing because it passes string data to an unmanaged memory method, which VB.NET endeavors to protect itself from, and so an additional attribute was required to allow it. Consider the following VB.NET example using a typical ByRef string: 'this function returns with lpBuffer containing the System directory and the function result is set to the string length Declare Auto Function GetSystemDirectory Lib "kernel32" ( _ <MarshalAs(UnmanagedType.VBByRefStr)> ByRef lpBuffer As String, _ ByVal nSize As Integer) As Integer ... Dim Sd As String = New String(Chr(0), BufSize) 'init string as a receiving buffer to get API return data Dim I As Integer = GetSystemDirectory(Sd, BufSize) 'now get system directory to String and its length to I Dim S As String = Left(Sd, I) 'aquire returned text as a string in variable S In the above example, we marshaled a ByRef string using the VBByRefStr attribute. Using A StringBuilder (an alternative to ByRef processing) Microsoft suggests an alternative to using a ByRef string, and that is to use a StringBuilder; an object introduced with VB2008 that is one of the fastest (200 times faster at standard string manipulations), most powerful, and most amazing string handling objects you may never have even heard of. A StringBuilder is a class defined in the System.Text namespace. One of the great thing about it, other than awfully fast string manipulation, is that you can pass it to a P/Invoke ByVal and it will be handled as a string. Even though sent ByVal, its member string is still able to receive data. A native VB.NET StringBuilder does not require any special marshaling tags, which internet gurus typically recommend, as shown below: 'this function returns with lpBuffer containing the System directory and the function result is set to the string length Declare Auto Function GetSystemDirectory Lib "kernel32" ( _ ByVal lpBuffer As System.Text.StringBuilder, _ ByVal nSize As Integer) As Integer 'note the ByVal for StringBuilder. An exception results if sent ByRef ... Dim Sd As New System.Text.StringBuilder(nSize) 'use a StringBuilder as a receiving buffer to get API return data GetSystemDirectory(Sd, Sd.Capacity) 'get system dir to StringBuilder and its length (result no longer needed) Dim S As String = Sd.ToString 'aquire returned text as a string in variable S NOTE: This might seem to go against ingrained rules, but we pass a StringBuilder ByVal to the P/Invoke, even though it requires the address of string data. The reason for this is that the Common Language Runtime (CLR) has knowledge of StringBuilder objects. Passing them ByVal will in fact pass a pointer to its internal string buffer. Were we to pass a StringBuilder ByRef, it would actually be treated as passing a pointer to the pointer, because we are passing a pointer (ByRef), and the CLR will in turn pass a pointer to that, thus resulting in an exception error being generated. Using A ByVal String (the BEST solution to ByRef processing) A better solution than using a StringBuilder or a ByRef String is to actually use a ByVal String, as you may have surmised from reading Point 4 (but mind you that this VB6-style solution has only been valid since the release of VB2005, and so it would have been flagged as an exception error under VB2002 and VB2003). As indicated under Point 6, sending a string ByVal will in fact pass a 32-bit Integer pointer to its base address. This is also what happens under VB6 (passed as what it would call a 32-bit Long). When VB.NET passes a 16-bit Unicode string to an 8-bit ANSI string and it knows that it must convert between the string formats (either informed through the Auto modifier verb or by specifying a P/Invoke name that VB.NET recognizes as using ANSI strings, such as through an Alias or by the declared method name that .NET tracks in a library), it will set aside temporary 8-bit ANSI string space, transcribe .NET’s 16-bit managed string to it, pass the address of the ANSI string to the method, and finally translate the ANSI string back to the 16-bit managed string space. As a point of interest, this also happens to be how passing strings ByRef using the marshalling tag did it, as described earlier.
  10. 10. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –10– For example, consider the following working declaration for the above GetSystemDirecty() P/Invoke that will pass a string ByVal, but which can still receive back valid string data: Declare Auto Function GetSystemDirectory Lib "Kernel32" ( _ ByVal Path As String, _ ByVal nSize As Integer) As Integer ... '************************************************* 'note that under VB.NET, you can actually get the System Directory using: ' GetSystemDir(): get SYSTEM directory 'Dim SystemDir As String = Environment.SystemDirectory '************************************************* Public Function GetSystemDir() As String Dim sd As New String(ChrW(0), nSize) 'set aside space for string (note the easy fixed-string-size substitute) Dim I As Integer = GetSystemDirectory(sd, nSize) 'now get system directory to string and its length to I Return Left(sd,I) 'return system directory path End Function Please note that the above GetSystemDirectory() P/Invoke could have been alternately declared as: Declare Function GetSystemDirectory Lib "Kernel32" Alias "GetSystemDirectoryA" ( _ ByVal Path As String, _ ByVal length As Integer) As Integer Or in native .NET format (which I do not care much for, considering the complicated parameters): <System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _ Public Function GetSystemDirectory(ByVal Path As String, ByVal length As Integer) As Integer End Function 88.. Dealing with fixed-length strings If the VB6 code uses fixed-length strings, it will be upgraded in the context of their use. Specifically, a Field or a Local Variable will be upgraded differently than a Member Variable of a Structure or Class. By default, the Upgrade Wizard will take the following VB6 Local Variable or Field: Dim FixedLengthTest As String * 128 'Test Code It will be upgraded for VB.NET’s use like this: Dim FixedLengthTest As New VB6.FixedLengthString(128) 'Test Code The greatest advantage of this is that everything is done for you. No mess. No fuss. The disadvantage of this is that it is more complex than we actually need it to be. Remember, VB.NET does not actually support fixed-length strings. Though we used a FixedLengthString() method, the string is still just a VB.NET dynamic string, but it is also a protected string, preventing accidental alteration to its size. Alternatively, we can easily 1) dramatically reduce code overhead, 2) gain a more string-test-friendly object, 3) make the program code execute faster, and 4) you will not have to resort to using the object’s ToString() method just to obtain its actual string text. And that is to simply do this: Dim FixedLengthTest As New String(ChrW(0), 128) 'Test Code – ChrW() accepts 16-bit UShort-range values (You can also just use Chr()) Fixed-Length String Members of Structures and Classes AN upgrade acts differently with string members of Structures and Classes. Consider this string member of a VB6 UDT (User Defined Type) that will be passed to a P/Invoke (note also that it shall be assumed that the VB.NET declaration for the structure encapsulating this variable will stipulate a Charset.Auto Attribute (see point 1), as in “<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>”): Dim szCSDVersion As String * 128 'Fixed-length maintenance version string declared within a Structure or Class You may notice that the VB.NET upgrade to the above VB6 code will have been rewritten, and converted into the following Char array (note that you can trim off the highlighted command path qualifiers if you have also imported “system.Runtime.InteropServices”): <VBFixedString(128),System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray,SizeConst:=128)>P ublic szCSDVersion() As Char 'Fixed-length maintenance version string You can simplify this line much further and still provide the very same functionality and ignore the conversion from a string to a fully compatible array of type Char (I want my reviewers to specifically know that we are dealing with text here; not with array elements) by instead defining it as follows: <VBFixedString(128)> Public szCSDVersion As String 'Fixed-length maintenance version string Or, employing a simpler version of the above converted Char array, you can also use the following: <VBFixedString(128)>Public szCSDVersion() As Char 'Fixed-length maintenance version string
  11. 11. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –11– What is really important here is that if we read the documentation for VBFixedString, it states: “The VBFixedStringAttribute is informational and cannot be used to convert a variable length string to a fixed string. The purpose of this attribute is to modify how strings in structures and non-local variables are used by methods or API calls that recognize the VBFixedStringAttribute. Keep in mind that this attribute does not change the actual length of the string itself. The VBFixedStringAttribute specifies the length of a string in bytes, not characters.” What does this mean to us? Mainly, it means that the VBFixedString attribute is 1) Informational and 2) It does not allocate space. If we are new to this sort of thing and had just read the above documentation, it may make us pause to wonder if we will need to afterward manually allocate real space for this string after we have instantiated a copy of the structure. The quick, fast answer is: No, we will not. We must understand that when the term “allocate” is used here, it really refers to initialized space. The fact is, VB.NET will set aside space for each fixed-length string member of a structure (by proxy, essentially, because the next pointer will simply point beyond where that “unallocated” string data is to be located). It would have been better to qualify it by saying that space for the string will be set aside, though it will not be initialized space. VB.NET has all the information it needs to compute these string sizes, making any additional allocation unnecessary, except in the rare cases when we will be required to pre-fill the string with some sort of default text data. But as it is, VB.NET will initialize it to an empty string. Hence, we never have to do any post-allocation, allowing us to process it exactly as we had done it under VB6. On the other hand, if you would feel more comfortable initializing the space, even though it is not necessary (unless we need to pre-initialize it), what follows are four solutions for doing just that: II.. The first, quickest, and best solution is to simply assign the structure to a variable using the New keyword, such as “Dim Struct1 As New MYSTRUCT”. Although a structure is not a Reference Class (it does not instantiate a reference object), it is an Abstract Class1 , which acts like a scalar variable. However, just like its object-based cousin, it does feature an optional New() method. The New() method will force all scalar members (numeric fields) to be initialized to zero, and all others, such as strings, to Nothing. IIII.. The next solution is to assign the structure to a reference variable and then initialize its string member(s). For example, were the string variable a member of a structure named MYSTRUCT (see its declaration in point IV, below), then we could perform our declaration and initialization in two lines: Dim Struct1 As New MYSTRUCT 'declare structure variable (NEW required, due to next line) Struct1.szText = New String(Chr(0), 128) 'initialize new blank buffer (16-bit Chars will become 8-bit) IIIIII.. Alternatively, if you want to initialize multiple members after assigning a reference variable to your structure, you should consider writing a method that you would be able to pass the structure variable you just declared to, and initialize it that way. Pass it the structure ByRef so that the members of the structure can actually be accessed and updated. Otherwise, if passed ByVal, only a copy of the data will be passed, leaving the original data unchanged. Consider this subroutine: 'initialize fixed strings in MYSTRUCT structure after the MYSTRUCT reference is declared Private Sub InitStructStrVar(ByRef stVar As MYSTRUCT) With stVar .szText = New String(Chr(0), 128) 'place other initializations here... End With End Sub IIVV.. Lastly, you may want to consider a solution based on the above method, but merging it with the solution that the Upgrade Wizard provides for the VBFixedArray attribute (discussed in the next point), and that is to take advantage of the fact that a VB.NET Structure is, in Object Oriented Programming terms, an Abstract Class. An Abstract Class can be viewed as a class with exceptions. Significantly, because it is a class type, it can contain methods and properties in addition to data fields. 1 An Abstract Class would be used to declare such items as scalars (value-type variables), and, of course, structures. Unlike the classes that you are likely familiar with from VB6, an Abstract Class can only be assigned to one variable that can reference its actual data. When assigning an Abstract Class to another variable, it passes a copy of itself, giving the target variable a replica of the original (much like using the Clone() method, discussed much later in Point 41). This is juxtaposed to a Concrete Class, which is the standard class type in VB.NET that can have instances instantiated, and a single instance can be pointed to by multiple reference variables (copying a reference variable to another reference variable would simply copy the referenced address in the absence of the Clone method (which would in turn simply instantiate another referenced object)).
  12. 12. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –12– What all of this boils down to is that after all our member variables are declared in our structure; we can also adjoin structure-embedded methods. I typically name any such imbedded helper method as Initialize(), for consistency. Consider the following example: <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _ Public Structure MYSTRUCT Dim dwInfoSize As Integer Dim dwEntryId As Integer <VBFixedString(128)> Dim szText As String 'Fixed reference string ' ' Initialize the 'fixed-length' string to the proper size ' Public Sub Initialize() szText = New String(Chr(0), 128) 'place other initializations here... End Sub End Structure With the above, you can declare and initialize a new copy of the structure with these two lines: Dim Struct1 As New MYSTRUCT 'The New keyword also makes sure that member variables are in default states; Struct1.Initialize() ' meaning scalars are 0, and all else, such as strings, are set to Nothing Do not panic if you must also pass this structure to a P/Invoke. When a Structure is passed ByRef to a P/Invoke (or to one’s own methods), the P/Invoke process will pass a pointer to its data in the same old way it had done so reliably under VB6. Further, internally, data and program code are also stored unconnectedly in entirely separate system memory locations, so there is no chance of this program code being passed right along with the structure’s data fields. As an aside, if you were to take a look at the length of the structure declared in solution IV, above, such as by using “Len(OSver)” (see Point 11 regarding VB6 and its LenB() method that would have been used here instead of Len()), you will find that the returned length would report 136 (bytes). This is the size of 2 Integer variables (32-bit/4-byte), allocating 8 bytes, plus the length of the fixed- length string, which is 128 bytes long, yielding a total of 136 bytes. Notice further that I stated that the length of the string was 128 bytes. This is something very important to keep in mind! It is because most Win32 P/Invokes use 8-bit ANSI characters instead of VB’s 16-bit Unicode characters. Even more, the documentation for the GetVersionEx P/Invoke I extracted this example from is actually the GetVersionExA (ANSI) version (as declared in its Alias modifier). Further, if you re-read the previously-stated documentation, the very last line states: “The VBFixedStringAttribute specifies the length of a string in bytes, not characters.” Had I been using the Unicode (Wide) version of the Kernel32.DLL function, GetVersionExW, I would have had to have stated 256 (bytes) as the designated width of the VBFixedArray attribute, in order to fully accommodate 128 16-bit Unicode characters. However, even so, do not be wracked with dread because we had initialized our string to just 128 bytes, yet we, within our code, may be, by proxy anyway, associating it with a string of 128 Unicode characters. This is because the CharSet.Auto property will tell the VB.NET interop marshal operator to automatically handle the Unicode/ANSI conversion for us, providing the required 128 8-bit ANSI characters needed during interop from the structure by the P/Invoke. This material appeared complicated the first time I ran into it, but after using it, it quickly became quite simple and normal. My own personal solution is to only apply special attributes to the body of the structure when I will be performing any Win32 interop processes with it, which is to say that I will be invoking methods declared in unmanaged or non-.NET space. Also, I prepend any structure strings that must be of a fixed size with “<VBFixedString(xxx)>”, specifying the number of bytes the string will occupy, or doubling it if I am dealing with a Unicode P/Invoke, and I typically declare structure variables using the “New” verb so that I will know that its data fields will be initialized. When specifying fixed-size arrays in my structures, I must also perform the additional easy step of adding a simple initialization method to my structure, which is outlined in the following point.
  13. 13. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –13– 99.. Dealing with fixed-length arrays within Structures If a Structure requires at least one fixed-length array (VB.NET only allows un-initialized dynamic arrays to be declared within a Structure), then an upgrade will modify the structure to define an un- initialized dynamic array in place of the fixed-size array, but unlike Point 8, above, in order to address this discrepancy, the upgrade will also provide a structure-embedded Initialize() method. It will further inform you in an “UPGRADE_TODO” comment that you must also invoke the Initialize() method after assigning the structure reference to a variable for field. This special method is meant to be used to ensure that the array can be easily set to its mandatory startup dimensions after assignment. Consider the following upgraded code segment of a VB6 LOGFONT structure: Structure LOGFONT Dim lfHeight As Integer Dim lfWidth As Integer Dim lfEscapement As Integer Dim lfOrientation As Integer Dim lfWeight As Integer Dim lfItalic As Byte Dim lfUnderline As Byte Dim lfStrikeOut As Byte Dim lfCharSet As Byte Dim lfOutPrecision As Byte Dim lfClipPrecision As Byte Dim lfQuality As Byte Dim lfPitchAndFamily As Byte <VBFixedArray(LF_FACESIZE)> Dim lfFaceName() As Byte '(the Upgrade modified this from "Dim lfFaceName(LF_FACESIZE) As Byte") 'UPGRADE_TODO: "Initialize" must be called to initialize instances of this structure. BLAH-BLAH-BLAH Public Sub Initialize() ReDim lfFaceName(LF_FACESIZE) End Sub End Structure In the above (further remembering to marshal the structure as outlined in point 1 or 2), what we see is that our fixed array, lfFaceName, appears to be declared to the initial size we require, which is a value defined by a constant named LF_FACESIZE. We should leave this code alone and simply invoke the Initialize() method after we assign the structure to a reference variable. If we need to supply this structure to a P/Invoke, then some think you might not be able to safely keep the Initialize() method within the structure. But rest assured that only the structure’s data fields is passed (ByRef) to a P/Invoke. Further, a Len() function performed on the above structure will return only its data’s byte length. But if you feel really squeamish about it, you might try something really unfortunate, like simply ignoring it or, worse, even deleting the Initialize() method. Is it OK to not bother with or to delete the Initialize() method, assuming that the array initialization can be ignored, just like simple string members can be? ABSOLUTELY NOT! The VBFixedArray documentation clearly states “The VBFixedArrayAttribute is informational and does not allocate any storage. The purpose of this attribute is to modify how arrays in structures and non-local variables are used by methods or API calls that recognize the VBFixedArrayAttribute. Keep in mind that this attribute does not convert a variable length array to a fixed array and that you must still allocate array storage using Dim or ReDim statements (underlining mine).” Meaning? It means that the VBFixedArray attribute, just like the VBFixedString attribute, does not actually initialize the space. However, in the case of arrays it boils down to us being required to declare dimensions for the array after we have assigned the structure to a new field or variable, and hence the upgrade’s automatic addition of the Initialize() method. That means to us that after we declare a variable to be of type LOGFONT, such as “Dim lfLogFont As New LOGFONT”, we are required to then also dimension the lfFaceName array to size LF_FACESIZE. Assuming we wisely retained the Initialize() method, as I hope you will, then we can simply invoke it using “lfLogFont.Initialize()”. Or, if we had suffered a brain fart and deleted it, we must alternatively resize our dynamic array manually, such as in the following form: “ReDim lfLogFont.lfFaceName(LF_FACESIZE)”.
  14. 14. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –14– Consider these two examples: ' Declare LOGFONT structure and initialize using Initialize() method (E-Z. It gives me time to raid the Fridge) Dim lfLogFont As New LOGFONT 'NEW causes all data members to be initialized to their default values. lfLogFont.Initialize() 'initialize using upgrade-provided method. ' Declare LOGFONT structure and initialize manually (yuck; more typing for me) Dim lfLogFont As New LOGFONT 'NEW causes all data members to be initialized to their default values. ReDim lfLogFont.lfFaceName(LF_FACESIZE) 'initialize manually. NOTE: A good rule of thumb to follow is this: If a structure contains an Initialize() method, then always invoke it after you instantiate it. If it does not contain one, then no worries. But remember that in applications you later develop under VB.NET, it is strongly recommended that you also create an Initialize() method within a structure containing any dimensioned arrays in order to properly dimension those array members, because VB.NET will not do that for you. NOTE: The reason that we separately allocate a fixed-sized array and not a fixed-sized string all has to do with VB.NET’s inability to predetermine preset array pointers (which, goofy me, I think is easy, because I create them all the time). With fixed-length strings declared with the <VBFixedString(nnn)> attribute, it can easily determine, reserve, and point to the base of the data in a structure or class for that string. A Fixed-size array is a different thing entirely, typically being a list or a matrix of pointers to data. Therefore, simply allocating space and pointing to its base for arrays is not acceptable, because the structure is expecting a list or matrix of pointers, even though VB.NET can and will reserve the space required to store all those many pointers. Call me silly, but I do not understand why VB.NET does not simply initialize that set-aside pointer space to a list or matrix of null pointers. Some may argue that multi-dimensional arrays is where this simplicity ends, but if you understand how arrays are internally constructed, then there should be no issue at all! However, since VB.NET already sets aside space for the array, why could it not simply perform this initialization for us, or at least auto-invoke an Initialize method during instantiation, if an Initialize method exists (I would prefer allowing a non-parameterized New(), which first performs its base function, then user-defined code)? NOTE: If you require multiple dimensions, such as 32x32, adjust pertinent lines in the structure as demonstrated here: <VBFixedArray(32,32)> Dim strMatrix(,) As String 'set aside space for 32 rows of 32 columns of strings '(note the rank setting (,) in strMatrix) ... ReDim StrMatrix(32,32) 'note that these parameters MUST match the VBFixedArray parameters, above NOTE: As noted above, you can specify multiple dimensions, such as “<VBFixedArray(1023, 63)> Dim I(,) As Integer”. Notice that we should match the values in the VBFixedArray parameter in the ReDim statement. Note also that the dimension Rank must be included in the declared un-dimensioned variable (the commas). For example, a 3- dimensional array such as “Dim I(20, 40, 128) As Integer” would be declared as “<VBFixedArray(20, 40, 128)> Dim I(,,) As Integer”. Notice that the rank for “I” changed to 2 commas, implying 3 dimensions. 1100..Dealing with RichTextBox Property Renaming For some dumb reason during an upgrade, when Rich Text format data is being copied from one RichTextBox to another through their RichText property fields, the assigned property will change. First, under VB.NET, the VB6 “TextRTF” property is simply named “Rtf”. This typically upgrades OK, except during an assignment; a target “TextRTF” property will be incorrectly upgraded to “Text” rather than properly to “Rtf”. For example, consider the following original VB6 code: With Me.rtbInfo .TextRTF = Me.rtbSearch.TextRTF 'now copy data to help display form ... End With When it is upgraded to VB.NET, this code results in: With Me.rtbInfo 'UPGRADE_WARNING: TextRTF was upgraded to Text and has a new behavior. Click for more: BLAH-BLAH-BLAH .Text = Me.rtbSearch.Rtf 'now copy data to help display form ... End With Notice that the source “TextRTF” property has been properly upgraded to “Rtf”, but the destination “TextRTF” was ‘upgraded’ instead to “Text”. This statement will have to be fixed by you by further changing the “Text” property to correctly reflect “Rtf”. Some upgrade…
  15. 15. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –15– 1111.. Dealing with recovering the LenB() function A VB6 program would use LenB() to obtain the actual byte length of a string or structure, usually to insert the length of a structure into its first member (some variable-sized structures require this). However, during an upgrade to VB.NET, code using LenB() is always flagged as not being supported by VB.NET. Although Microsoft’s help is a bit lacking in providing a clear resolution to this important issue, the clarifications below should resolve this quandary. Online gurus often state the VB.NET Len() function does not support Structures (misinformation blamed on Microsoft; but that was only true for early releases, to which Microsoft was referring to), and advise you to look to the Marshal Class (part of the System.Runtime.InteropServices namespace), which has a SizeOf() method that will accept objects, such as structures, and will return its size in bytes. At first glance, this seems to be exactly what we need. In actual testing, however, I found that the result does not accommodate the actual allocated sizes of string members declared within those structures, except as 4-byte IntPtr types (Integer Pointers), which they technically are. Using that class, the MYSTRUCT structure described below, will return a value of 12, which accounts for 2 Integers, plus 1 IntPtr referencing its string data ((2+1) x 4): <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _ Public Structure MYSTRUCT Dim dwInfoSize As Integer '4 byte footprint Dim dwEntryId As Integer '4 byte footprint <VBFixedString(128)> Dim szText As String 'A string with a declared footprint size of 128 bytes Public Sub Initialize() 'Initialize the 'fixed-length' string to the proper size szText = New String(Chr(0), 128) 'This method occupies no space in regard to the size of the structure End Sub End Structure On the other hand, VB.NET’s Len() function now fully supports Structures and will return its full allocated size, to include the allocated lengths of any VBFixString()-declared string members. NOTE: Allocated lengths of String members in structures refers to an actual declared size. For normal string declarations, this would be a length of 4 (the size of an IntPtr). But if the string is declared with a fixed string prefix, such as “<VBFixedString(128)>”, then this alters its initial footprint size to the byte size that is declared. For individual Unicode Strings, such as “Dim S As String = "abc" : Dim I As Integer = Len(S)” you can either double the value of the string’s length, or you can use the GetByteCount property of the System.Text namespace’s Unicode.Encoding class. Consider the following upgraded VB6 code: 'UPGRADE_ISSUE: LenB function is not supported. 'Click for more: 'ms-help://MS.VSCC.v90/... BLAH BLAH CopyMemory(MnMxInfo, lParam, LenB(MnMxInfo)) 'get structure byte size 'UPGRADE_ISSUE: LenB function is not supported. 'Click for more: 'ms-help://MS.VSCC.v90/... BLAH BLAH Dim iLen As Integer = LenB(myString) 'get string byte size We can manually fix the above two lines by updating them to this bit of bother: CopyMemory(MnMxInfo, lParam, Len(MnMxInfo)) 'get structure byte size Dim iLen As Integer = System.Text.Encoding.Unicode.GetByteCount(myString) 'get string byte size 'Or: Dim iLen As Integer = 2 * Len(myString) 'get string byte size Or better, the above fixes can be eliminated and the original code will become once again viable simply by adding our own overloaded LenB functionality. In a module (here named modLenB), just add these two functions: Module modLenB '******************************************************** ' Provide the VB6 LenB Functionality – Type-Safe approach '******************************************************** Public Function LenB(ByVal ObjStr As String) As Integer 'Note that ObjStr.Length will fail if ObjStr was set to Nothing, so use Len() If Len(ObjStr) = 0 Then Return 0 Return System.Text.Encoding.Unicode.GetByteCount(ObjStr) End Function Public Function LenB(ByVal Obj As Object) As Integer If Obj Is Nothing Then Return 0 Try 'Structure Return Len(Obj) Catch 'Leave blank for catch-all Try 'Type-def objects Return System.Runtime.InteropServices.Marshal.SizeOf(Obj) Catch 'Leave blank for catch-all Return -1 'Allow user to check for <0 as an error End Try End Try End Function End Module
  16. 16. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –16– 1122.. Dealing with passing parameters to P/Invokes “As Any” If a P/Invoke has parameters tagged “As Any”, you are actually much better off using type-safe overloaded methods in VB.NET. With it you can declare more than one type-safe method with the same name, but using different parameters. This simplifies coding because you do not need to be concerned about which method to invoke. This is a feature VB6 users had for years been clamoring loudly for, but which VB6, as its platform had been implemented, was sadly not able to support. Indeed, VB6 had to jump through enough hoops as it was just to enable support to perform API calls in the first place (a feat worthy of praise for the VB Development Team at Microsoft – a goal that many had believed was impossible). But function overloading was just asking too much of a platform that was already stretching to its limits. In fact, the whole reason why the As Any type was ever introduced was just as a cheat to get around VB6’s inability to support method overloading. Now, it seems, many VB programmers cannot seem to survive without the As Any type, which was, purely and simply, a hack to enable functionality that was otherwise inaccessible to VB6 developers, though one that was absolutely necessary for the pre-VB.NET platforms, for otherwise too many Win32 API methods would have been off limits to VB programmers. This would have certainly been totally unacceptable, because it would have held VB programmers too far back and would have been a nail in VB’s coffin, not to mention giving its detractors more ammunition to fire at it. For example, consider the following typical VB6 API Call that has found a lot of use in VB6: Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _ ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _ ByVal lParam As Any) As Long This method was often used to send string text assigned to the lParam value to another control in another application running on the computer, or to a different process, or to a different control. If there was no text, then a value of zero was usually sent as lParam. Using “As Any” allowed both text and values to be passed by the very same parameter. Under VB6, if you wanted to break this API up to support values and strings separately without resorting to As Any, you did it by declaring two separate APIs and giving them separate names, such as SendMessageByNum and SendMessageByStr, and manually invoking the appropriate method, depending on what type of data you needed to send. For example: Declare Function SendMessageByNum Lib "user32" Alias "SendMessageA" ( _ ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _ ByVal lParam As Long) As Long Declare Function SendMessageByStr Lib "user32" Alias "SendMessageA" ( _ ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _ ByVal lParam As String) As Long With VB.NET, you are now able to declare two separate method bodies, but provide them with the very same name. This way, you do not have to think about which method to use because they will be the same, though the compiler will be able to tell by examining the parameters, and it would select the proper method for you. Consider the following declarations, both named SendMessage: Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _ ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _ ByVal lParam As Integer) As Integer Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _ ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _ ByVal lParam As String) As Integer However, if you are one who still feels as though you cannot live without it, or simply feel that using it will get around having to learn how to write overloaded methods, what are actually quite simple, as shown above, you can modify them as follows. If the VB6 parameter was declared: lpKeyName As Any 'not type-safe, so do not just throw any ol' thing into this stew pot Upgrade it to actually work As Any under VB.NET like this: <MarshalAs(UnmanagedType.AsAny)> ByVal lpKeyName As Object 'not type-safe, but works exactly like VB6
  17. 17. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –17– 1133.. Dealing with StrConv: converting between Unicode and ANSI strings Normally you can use the StrConv() method as you had used it under VB6 and the upgrade will adjust it. But when converting to/from ANSI and Unicode text, you must do it differently because Unicode/ANSI conversion is no longer supported by StrConv under VB.NET, which is now geared more toward much- needed foreign language handling, but has been moved to the more logical System.Text namespace. To convert an ANSI Byte array (Bytes) to a Unicode String (strText), take these VB6 statements: Dim strText As String strText = StrConv(Bytes, vbUnicode) ' convert ANSI text to Unicode text Convert this VB6 command line to VB.NET using the following equivalent statement: Dim strText As String = System.Text.Encoding.ASCII.GetChars(Bytes) ' equivalent to VB6 StrConv(Bytes, vbUnicode) To convert a Unicode String to an ANSI Byte array, you can upgrade the following VB6 statements: Dim Bytes() As Byte Bytes = StrConv(strText, vbFromUnicode) ' convert Unicode text to ANSI text To VB.NET using the following equivalent statement (you can also use UTF7/8 instead of ASCII): Dim Bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(StrText) ' equivalent to VB6 StrConv(strText, vbFromUnicode) NOTE: Some online techs have widely reported different VB.NET functions to achieve these ends, but their solutions most often maintain 16-bit encoding, which is not what many users asked for (but in some cases we do need that). Let us take a quick look at this. To convert each two consecutive byte array elements into one Unicode character, use this: Dim strText As String = System.Text.Encoding.Unicode.GetChars(Bytes) 'convert each 2 consecutive byte array elements to Unicode Chars Conversely, to convert a Unicode String to an equivalent Byte array, you should use this: Dim Bytes() As Byte = System.Text.Encoding.Unicode.GetBytes(strText) 'convert each Unicode Char into 2 consecutive byte array elements The above two methods both maintain 16-bit encoding, the first combining every two consecutive byte array elements into one consecutive Unicode character, and the other breaking each 16-bit Character into two consecutive Bytes. What follows is a practical example for user-side conversion between Unicode text and ANSI text: One thing driving many VB.NET developers crazy has been the process of converting a Unicode String into an ANSI string required by some WIN32 P/Invokes. In certain situations we will need to set aside a string area in unmanaged space, copy a string to that memory area, next provide the address to that stored text to a structure member, then finally provide that structure to a P/Invoke (SHBrowseForFolder() is the example I see most often used to exemplify this problem). Consider the following partial code clip: ' ---------------------------Declare P/Invokes------------------------- 'copy memory P/Invoke (do you see the error in the following P/Invoke declaration? It will be discussed in the text) Private Declare Auto Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDest As Integer, _ ByVal pSource As String, _ ByVal dwLength As Integer) ' Allocation P/Invoke to reserve a specified byte count of unmanaged local memory space Private Declare Function LocalAlloc Lib "kernel32" (ByVal uFlags As Integer, ByVal uBytes As Integer) As Integer ' Consider using System.RunTime.Interopservices: Dim GCptr As GCHandle = GCHandle.Alloc(x) in place of the above (DRG) ' Private Const LMEM_FIXED As Integer = &H0 'allocated buffer location will be locked in place Private Const LMEM_ZEROINIT As Integer = &H40 'allocated buffer will be initialized to zeros Private Const Lflgs As Integer = (LMEM_FIXED Or LMEM_ZEROINIT) 'combine constants ' ' Deallocation P/Invoke to release unmanaged local memory space set aside by LocalAlloc P/Invoke Private Declare Function LocalFree Lib "kernel32" (ByVal hMem As Integer) As Integer ' Consider using GCptr.Free in place of the above (DRG) ' ' This API displays a browser dialogbox, enabling the user to select a file or folder. Private Declare Function SHBrowseForFolder Lib "shell32" (ByRef lpbi As BrowseInfo) As Integer ... Dim udtBI As BrowseInfo 'set up structure reference variable to a BrowseInfo structure Dim lpmt As Integer 'used to store pointer to allocated space ' ' Build the BrowseInfo structure assigned to udtBI With udtBI .hwndOwner = hwndOwner 'owner handle (Me.Handle, or the handle of the form to 'parent' the dialog) .pidlRoot = 0 'Address of an ITEMIDLIST structure, or use NULL (0) if it is not used Dim Pmt As String = sPrompt & Chr(0) 'set local prompt "Select Source Path" and include a null terminator in its size lPmt = LocalAlloc(Lflgs, Len(Pmt)) 'set aside space for text (use char count of the prompt for its 8–bit ANSI length) CopyMemory(lPmt, Pmt, Len(Pmt)) 'copy string (Pmt) to set-aside space (lPmt) for the char count of Pmt .lpszTitle = lPmt 'set address of string stored in unmanaged memory to structure member ... 'other initializations go here... End With ' ' browse... Dim lpIDList As Integer = SHBrowseForFolder(udtBI) 'browse and automatically allocate resources for aquired data ' ' free set-aside unmanaged memory space created by LocalAlloc() LocalFree(lPmt) 'free space we allocated for our custom prompt ...
  18. 18. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –18– What we are going to discover is that the displayed prompt will consist of just its first character (showing “S”, when it should be “Select Source Path”, for example). Why? Because most developers automatically placed an Auto verb in the declaration of the CopyMemory() P/Invoke, remembering only that we will be processing a string (Private Declare Auto Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"), just as all the online examples I have seen do. To clarify, refer to Point 4 concerning my warning against including both the Auto and Alias verbs in a P/Invoke declaration. Further, because VB.NET has special knowledge of RtlMoveMemory(), it will enforce Unicode to ANSI text translation if any string is used in this method. What results is our Unicode string remains intact, and so, in binary terms, the string begins with the Unicode character “S” (x’0083’), which is actually stored internally in memory as x’83’ and then x’00’ (the ‘high’ byte x’00’ is listed after the low x’83’ because, internally, memory progresses, relatively, from left to right; from low to high – even bit sequences are reversed from how we typically imagine them as right to left – but this is all hidden from you), and because text is processed internally as ANSI by the P/Invoke, it finds an 8-bit null text terminator following the “S”. NOTE: Even though the Auto verb had been present, be aware that if it was retained, but no parameter contained a string type, then the method would have worked OK, because strings would not be a factor in those situations. Even though the actual solution to this problem is to simply remove the Auto verb from the CopyMemory() declaration, those developers apparently did not figure that out. Even so, we can still explore the useful alternative method they developed for passing 8-bit ANSI strings to P/Invokes. One idea they came up with was to pass MoveMemory() a pre-converted ANSI string as a Byte array instead of a Unicode string. I will also assume that the declared CopyMemory() P/Invoke will not be used elsewhere in the current module, so I will simply redefine it to a version that will accept a Byte array (I could have easily just added an overloaded method). Since the P/Invoke now does not contain strings, we will remove the Auto verb (but you watching at home know that we just fixed the actual bug in the program, and needing no other changes, because apparently those developers were not aware of the rule warning against using both Auto and Alias in a P/Invoke declaration). 'copy memory API (note that if Auto were still present in this type of declaration, it ' (would still work perfectly, because string parameters are not present) Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDest As Integer, _ ByVal pSource() As Byte, _ ByVal dwLength As Integer) And to pass ANSI text to the CopyMemory() method, I will need to translate the Unicode text to a Byte Array. I would therefore update my code with the following darker shaded segments: With udtBI .hwndOwner = hwndOwner 'owner handle (Me.Handle, or the handle of the form to 'parent' the dialog) .pidlRoot = 0 'Address of an ITEMIDLIST structure, or use NULL (0) if it is not used Dim Pmt As String = sPrompt & Chr(0) 'set local prompt copy and include a trackable null terminator in its size Dim Byt() As Byte = System.Text.Encoding.ASCII.GetBytes(Pmt) 'convert Unicode String to ANSI Byte Array lPmt = LocalAlloc(Lptr, Len(Pmt)) 'set aside space for text (use char count of the prompt for its 8-bit ANSI length) CopyMemory(lPmt, Byt, Len(Pmt)) 'copy byte array (Byt()) to set aside space (lPmt) for the char count of Pmt .lpszTitle = lPmt 'set address of string stored in unmanaged memory to structure member ... 'other initializations go here... End With NOTE: By the way, on NEW projects that involve folder and file selection dialogs, you should take the E-Z route and simply employ the FolderBrowserDialog or OpenFileDialog controls available in the VB.NET IDE Toolbox. With these you can set custom prompts and starting paths easily and quickly, and they require absolutely no interop code from you. For reference, what follows is a module that will provide Unicode/ANSI string conversion: Module modUnicodeANSI '****************************************************** ' StrConv_Uni2ANSI ' Provide conversion from Unicode string to ANSI string '****************************************************** Public Function StrConv_Uni2ANSI(ByVal StrUni As String) As Byte() If Len(StrUni) = 0 Then Return Nothing Return System.Text.Encoding.UTF8.GetBytes(StrUni) 'convert Unicode string to ASCII bytes End Function '****************************************************** ' StrConv_ANSI2Uni ' Provide conversion from ANSI string to Unicode string '****************************************************** Public Function StrConv_ANSI2Uni(ByVal Bytes() As Byte) As String Return System.Text.Encoding.UTF8.GetChars(Bytes) 'convert ASCII bytes to Unicode string End Function End Module
  19. 19. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –19– 1144.. Dealing with AddressOf/Missing Delegate issues When a warning about the use of the AddressOf() function indicates that the object needs a Delegate, this is easy to remedy, but the solution is a 2-step process. First, a Delegate (a prototype or signature) of the object must be declared (E-Z to do), and then the storage location it is being assigned to must be changed, usually from Integer or IntPtr (both VB6 Long) to the name of the Delegate we must define (this is also E-Z to do). This solution can be implemented quickly. A Delegate is used as a template to tell the compiler how it should treat a method name when it is encountered. A Delegate is very similar to a C/C++ prototype declaration; it provides clues to the system so the system knows that when a method name is specified, if it is a function, what its return type is, and the types of any parameters. Unlike VB6, which was subject to errors in this venue, VB.NET takes steps to avoid misused method invocation errors by requiring method prototypes. Probably the quickest way to deal with this situation is to place a bookmark at the offending location (the line containing the erroneous assignment and the AddressOf statement), so we can return to it. The first thing to do is to go to the line containing the offending function addressed by AddressOf. Suppose that we are assigning a Callback function to a member of some structure, and the line instigating the Upgrade Warning is flagged on the following line of code: 'UPGRADE_WARNING: Add a delegate for AddressOf BrowseCallbackProcStr. Click for more: BLAH BLAH BLAH myStruct.lpfnCallback = AddressOf BrowseCallbackProcStr First, click on that line to place the cursor in it, then either hit the Toggle Bookmark toolbar icon, or select Edit / Bookmarks / Toggle Bookmark from the menu, to place a bookmark at that location. Next, right-click “BrowseCallbackProcStr” and select “Go To Definition”. At the definition, we will need to select the full function heading, copy it to the clipboard, then go to the beginning of the class or module, but still within its body, and paste the function heading to a new line. What we need to do with this copied heading is to edit it in order to convert it into a unique Delegate. For example, if the copied function heading looked like the following: Public Function BrowseCallbackProcStr(ByVal hwnd As Integer, _ ByVal uMsg As Integer, _ ByVal lParam As Integer, _ ByVal lpData As Integer) As Integer To convert it to a Delegate, simply insert the term Delegate before Function (or Sub, if a subroutine), and change the name of the method. I simply add the text “Delegate” to the end of the name, like so: Public Delegate Function BrowseCallbackProcStrDelegate(ByVal hwnd As Integer, _ ByVal uMsg As Integer, _ ByVal lParam As Integer, _ ByVal lpData As Integer) As Integer After that, select the text “BrowseCallbackProcStrDelegate” and copy it to the clipboard. Next, go back to our book-marked location, right-click “lpfnCallback”, where the address is to be assigned to, and then select “Go To Definition”. For this example, it took me to the following line that is located within a structure definition: Dim lpfnCallback As Integer 'Address of Callback Change the “Integer” to “BrowseCallbackProcStrDelegate” by double-clicking “Integer” and hitting Ctrl-V to replace it with the Delegate name. My line would afterward look like the following: Dim lpfnCallback As BrowseCallbackProcStrDelegate 'Address of Callback We have just fixed the missing delegate problem. NOTE: If the above were not a member of a structure, but a parameter provided directly to a P/Invoke; simply change its type to the name of the Delegate, just as we did above. You may also want to go back to your book-marked location and toggle that bookmark off, as well as deleting the UPGRADE_WARNING comment inserted by the Upgrade Wizard). NOTE: Be aware that the Delegate will be internally interpreted as an IntPtr, which is an Integer Pointer, being the exact same size as an Integer. All addresses returned by AddressOf also return an Intptr (treaded by VB6 as a Long).
  20. 20. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –20– In another case, in instances where you hook and unhook window/form subclasses, intercepting the Windows Message Queue, this requires using the SetWindowLong P/Invoke. You may want to simplify the delegate handling process by defining multiple versions of this P/Invoke. Consider the following code clipping, which must invoke SetWindowsLong in two different ways; one using a Delegate (notice the use of AddressOf), and the other using a simple Integer value, as shown below: '************************************** ' Declare our P/Invoke to hook/unhook our code to the Windows Message Queue '************************************** Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _ ByVal nIndex As Integer, _ ByVal dwNewLong As Integer) As Integer '*********************************************** ' HookWin(): Subclass hwnd to szWndProc: Insert a hook in the Windows Message Queue to divert queue processing to szWndProc '*********************************************** Public Sub HookWin(ByVal hWnd As Integer, ByRef PrvhWnd As Integer) 'UPGRADE_WARNING: Add a delegate for AddressOf SzWndProc. Click for more: BLAH BLAH PrvhWnd = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf szWndProc) 'insert our hook, and save the previous one End Sub '*********************************************** ' UnhookWin(): remove subclass hook to szWndProc: remove our function diversion and stuff the old saved invocation back in '*********************************************** Public Sub UnhookWin(ByVal hWnd As Integer, ByRef PrvhWnd As Integer) If CBool(PrvhWnd) Then 'if somethinging (the old windproc) to unhook... Call SetWindowLong(hWnd, GWL_WNDPROC, PrvhWnd) 'reset previous hook by over-writing ours PrvhWnd = 0 'indicate no held hooks set aside End If End Sub '*********************************************** ' Our custom subclassing method to insert into the Windows Message Queue (only the header is shown below). '*********************************************** Private Function szWndProc(ByVal hWnd As Integer, _ ByVal uMsg As Integer, _ ByVal wParam As Integer, _ ByVal lParam As Integer) As Integer ... 'rest of program and function was here After defining the needed Delegate using the technique previously outlined, say szWndProcDelegate, to prototype our subclass method, szWndProc, some creative people have attempted to dance the Delegate name around, which resolves to an IntPtr/Integer, and declared PrvhWnd in HookWin, the PrvHwnd parameter for UnhookWin, and so on, to this Delegate. We might try fraught things like this as we blink excessive sweat from our eyes while struggling desperately to stop VB.NET from incessantly barking at us after each compile attempt with it badgering us about improper data typing; this stemming from our myopic attempts to resolve common addressing to our P/Invoke, SetWindowLong. But the above is making a simple problem a lot more complicated than it needs to be. Instead of trying to adjust variable typing to suit our P/Invoke, why not think broadly and simply massage our P/Invoke into addressing the two different ways that HookWin and UnhookWin need to invoke it? So, after thinking peaceful Zen thoughts about fluffy puppies and kittens, and having the full power of object oriented programming at hand, we should realize that all we really need to do is take advantage of VB.NET’s capacity to support overloaded methods. With that in our bag of tricks, all we must do to correct our problem is 1) create the needed Delegate for szWndProc, and then 2) define another, but overloaded, SetWindowLong method to support the Delegate. This way we will have one version of SetWindowLong with an Integer version to handle parameter dwNewLong, and another version of SetWindowLong that uses our Delegate as the type for dwNewLong. With just that, suddenly this otherwise potentially very complicated problem (well, it was complicated if we think too narrowly) is completely solved, as the darker shaded areas demonstrate below: '*********************************************** ' Declare our Delegate – declare it before it is referenced. This delegate resolves to a simple Integer/IntPtr '*********************************************** Public Delegate Function szWndProcDelegate(ByVal hWnd As Integer, _ ByVal uMsg As Integer, _ ByVal wParam As Integer, _ ByVal lParam As Integer) As Integer ... '*********************************************** ' Declare our overloaded P/Invokes to hook/unhook our code to/from the Windows Message Queue '*********************************************** Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _ ByVal nIndex As Integer, _ ByVal dwNewLong As szWndProcDelegate) As Integer ' "dwNewLong As szWndProcDelegate" will be treated at the machine level as simply an Integer/IntPtr Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _ ByVal nIndex As Integer, _ ByVal dwNewLong As Integer) As Integer
  21. 21. Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –21– 1155.. Dealing with Dir() function warnings If you are warned that the Dir() method has changed behavior, this concerns the sequencing of folder shortcuts “.” (Current) and “..” (Parent) during a directory scan. In VB6 these two folders were always the first two entries gathered. Some programmers got goofy and just skipped checking the first two entries based on this assumption. This is no longer true, nor should it have been assumed. To ignore them, see if the left character of the file/folder name is not “.” (If DirEntry.Substring(0, 1) <> "." Then). As long as you are doing this, reports of changed behavior can be safely ignored. However, you should consider abandoning the archaic and slow Dir() function and resort to using the extremely fast and much more powerful My.Computer.FileSystem or System.IO classes and methods, which work much faster than even the File System Object declared in the ActiveX COM reference Windows Script Host Object (also known as Windows Script Host Object Model, or WSHOM), embodied by wshom.ocx (this OCX in turn redirects to IWshRuntimeLibrary.DLL) or scrrun.dll (if you reference Microsoft Scripting Runtime instead). 1166.. Dealing with Item issues in Collections If you use Collection-type objects, such as Collections, ListBoxes, or ComboBoxes, you often get a warning that a default property for Item in a Collection cannot be determined. This is because Collection Items in VB.NET do not default to a text property as they did under VB6. The Item property of a VB6 Collection was of type String, but under VB.NET it is now a more versatile type Object (but with the proviso that the provided Object also features a ToString method). You can resolve this issue by choosing to ignore the warning, because you have in fact already been assigning a String Object to the Item property, then this will be corrected during the late binding process. Yet, if you want to avoid this supported late-binding bit-fiddling, which is much slower, you can greatly speed it up by employing the collection Item object’s ToString() method (i.e., myListBox.Items.Item(Index).ToString()) to resolve it during the much faster early-binding stage. If you are using an actual Collection, consider changing the collection object itself, such as into one of the System.Collections.Generic strongly-typed zero-based Collection classes. If you will only be working with text data, then define it as a List, or, if you also require an associated Key, try using a Dictionary (or a SortedDictionary if you would like to auto-sort the dictionary based on the Key). For example, you could change your VB6 declaration from something like this: Dim myCol As New Collection To something more robust and strongly typed, like this VB.NET declaration: Dim myCol As New Collections.Generic.List(Of String) You can of course separate the definition from the instantiation of the collection. You can also use a structure or class instead of String as the type of object to strongly tie this collection to. Or, you can simplify editing even more by redefining the Collection control in your app in just 3 lines of code: Public Class Collection 'make this OF a custom Class or Structure if you want to add more features, such as keys. Inherits System.Collections.ObjectModel.Collection(Of String) 'replace 1-based Collection with 0-based string-typed collection. End Class 'or you can use the KeyedCollection or ReadOnlyCollection classes of ObjectModel. NOTE: Because a generic collection’s Remove() method will expect an object (used like a Key) to identify what object to delete, use instead the new RemoveAt() method, which will allow you to specify an index. NOTE: Remember, the warning about an Item’s default property will probably be the most frequently encountered warning in an upgrade if the VB6 project did much of anything with collections. Keep in mind that using the Item object’s ToString() property will be the safest, easiest solution, especially if you are instead using a ListBox or a ComboBox, but which can also use custom classes for data instead of just strings (as it had been for VB6). However, be mindful that these new collections are zero-based, not 1-based as the default ‘classic’ Collection. If you need it to be 1-based, stuff a dummy entry as the first item, but remember that the Count property will always be one higher.

×