Welcome to AddressOf.com Sign in | Join | Help

Tips, Tricks and Code Snippets

Below are a few tips and tricks that I've either created or come across that I've used or have just found interesting enough to store someplace for possible use later.  It's formatted in a FAQ format.

How do I determine the executing assemblies path?

If your wanting to do this with a Windows Forms application, just use:

  Dim path As String = System.IO.Path.GetDirectoryName(Application.ExecutablePath)

Otherwise, you can use:

  Dim path As String = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.Location)

How do I get a list of installed printers?

Use the System.Drawing.Printing.PrinterSettings.InstalledPrinters collection.

How do I translate a Win32 error number into a human readable text message?

You have two ways (using VB.NET) to get a numeric Win32 error message.  One is to use the System.Runtime.InteropServices.Marshal.GetLastWin32Error method, while the other is to use the VB.NET specific Err.LastDllError method.  Now that you have the numeric representation of the Win32 error, we want to turn it into a text representation, you know, the error message in English.

There's couple of ways to accomplish this.  First, we'll explore the Win32 P/Invoke way first.  Using this method is the way that the SDK documentation is pointing out.  What I've done is wrapped the FormatMessage API call with a VB.NET friendly wrapped version. 

Private Declare Function FormatMessageA Lib "kernel32" (ByVal flags As Integer, ByRef source As Object, ByVal messageID As Integer, ByVal languageID As Integer, ByVal buffer As String, ByVal size As Integer, ByRef arguments As Integer) As Integer

Public Shared Function FormatMessage(ByVal [error] As Integer) As String
 
Const FORMAT_MESSAGE_FROM_SYSTEM As Short
= &H1000
  Const LANG_NEUTRAL As Short
= &H0
  Dim buffer As String
= Space(999)
  FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, [error], LANG_NEUTRAL, buffer, 999, 0)
  buffer = Replace(Replace(buffer, Chr(13), ""), Chr(10), "")
  Return
buffer.Substring(0, buffer.IndexOf(Chr(0)))
End
Function

You can then use this method as follows:

Throw New Exception(Marshal.GetLastWin32Error)

A more friendly .NET Framework method is to use the Win32Exception class.  Normally when you want to know what the human readable version of the numeric value is, you will probably throw this so the user of your application can see the error.  At the very least, so you can easily figure out what is the problem.

Imports System.ComponentModel
Imports System.Runtime.InteropServices

Throw New Win32Exception(Marshal.GetLastWin32Error)

How do I change the color of the text on a tab of the TabControl?

You'll have to set the TabControls DrawMode property to OwnerDrawFixed and draw the text in the TabControls DrawItem procedure.

Private Sub TabControl1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) _
  Handles TabControl1.DrawItem

 
Dim r As RectangleF = RectangleF.op_Implicit(e.Bounds)
 
Dim ItemBrush As New SolidBrush(TabControl1.BackColor)

 
Dim sf As New StringFormat
  sf.Alignment = StringAlignment.Center
  sf.LineAlignment = StringAlignment.Center

 
If CBool(e.State And DrawItemState.Selected) Then
   
e.Graphics.FillRectangle(ItemBrush, e.Bounds)
    e.Graphics.DrawString(TabControl1.TabPages(e.Index).Text, e.Font, Brushes.Red, r, sf)
 
Else
   
e.Graphics.DrawString(TabControl1.TabPages(e.Index).Text, e.Font, Brushes.Blue, r, sf)
 
End If

End Sub 

How can I determine the date for next Friday?

Public Function GetNextFriday(ByVal startDate As Date) As Date
 
If startDate.DayOfWeek < DayOfWeek.Friday Then
   
' If todays day of week is less than Friday.
   
' Add the number of days till next Friday.
   
Return startDate.AddDays(DayOfWeek.Friday - startDate.DayOfWeek)
  Else
   
' Otherwise, subtract the difference from 7.
   
Return startDate.AddDays(7 - (startDate.DayOfWeek - DayOfWeek.Friday))
 
End If
End Function

How can I 'export' functions from a .NET DLL to non-.NET applications?

Well, the official answer is to use COM in order to export your classes to .NET applications.  But what do you do when these other applications are unable to use COM?  Well, you can then possibly use a wrapper DLL written in Managed C++ to accomplish the task.  But wait, there appears to be yet one more way... hack the resulting IL code of your DLL to export directly as a native Win32 type 'export'.  Export Managed Code as Unmanaged article discusses all the gory details.

How do I catch errors in 'release' builds, but not while debugging?

Here's the scenario.  You want to setup a global error handler (which you should) for your application.  This is a good thing.  However, it causes a pain when trying to develop within the debugger since now every time an error occurs, your global error handler will capture the error.  Here's a solution:

Often times, applications will have a global exception handler in the Main method that catches any exceptions that weren't handled elsewhere. The compiler itself uses a strategy similar to this for out of memory situations -- once we've run out of memory, the compiler's toast, so we throw an exception indicating “out of memory” and then catch it at the top level, giving a nice “out of memory, please restart the compiler” error. (Hopefully none of you have ever seen it.) Anyway, when you're debugging the application it can be convenient to not catch exceptions at the top level so that you immediately break into the debugger when you get a global exception. A simple way to do this is to use When to not catch the exception when a debugger is present:

Sub Main()
Try
RunApplication()
Catch ex As Exception When Not System.Diagnostics.Debugger.IsAttached
HandleGlobalException(ex)
End Try
End Sub

Source: Paul Vick

How do I determine if an assembly was built for debug or release?

If you want to determine if your assembly was built using debug or release, you would think you could use the FileVersionInfo.IsDebug or IsRelease; however, the compiler doesn't apparently set the proper bit as you would expect.  Since that doesn't work, you could use the DebuggableAttribute on the assembly by using the following code.

Dim assm As Reflection.Assembly = Reflection.Assembly.LoadFrom(Application.ExecutablePath)
Dim found As Boolean = assm.GetCustomAttributes(GetType(DebuggableAttribute), False).Length > 0
Me.Text = "Assembly is " & IIf(found, "debug", "release")

Source: Thanks to Jeff Key for posting this tip to his blog (you can view his post for the C# code).

How do I build a sockets (network) terminal client.

Haven't had a chance to go through this video walkthrough, but I'm sure it's excellent since it's done by Carl Franklin.  As soon as I get a chance to check it out, I'll update this information.  You can check it out here.

Holy crap!!! My application is taking up too much memory and it's just a default form?  What is going on here?

Well, this is actually normal with an application utilizing the .NET Framework.  Many things are happening when you launch your application such as code access security, JIT compilation, memory management and the referencing of the actual .NET Framework library(ies).  However, if you are really concerned with the amount of memory your application is reporting you can try the following to give your application and Windows a kick in the rear to more accurately report the current memory usage of your application.  This tip is purely for cosmetic purposes, to help calm those irritating phone calls asking why your application uses more memory than Microsoft Word does... and it's just a Hello World application.  There is no performance gain of any kind from using this tip.  See warnings below for additional information.

Public Class MemoryManagement
 
 
Private Declare Function SetProcessWorkingSetSize Lib "kernel32.dll" ( _
   
ByVal process As IntPtr, _
   
ByVal minimumWorkingSetSize As Integer, _
   
ByVal maximumWorkingSetSize As Integer) As Integer

 
Public Shared Sub FlushMemory()
    GC.Collect()
    GC.WaitForPendingFinalizers()
   
If (Environment.OSVersion.Platform = PlatformID.Win32NT) Then
     
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, -1, -1)
   
End If
 
End Sub

End
Class

This will force a garbage collection to occur and if running on Windows NT, 2000, XP, or 2003, will (according to the SDK documentation):

The working set of a process is the set of memory pages currently visible to the process in physical RAM memory. These pages are resident and available for an application to use without triggering a page fault. The minimum and maximum working set sizes affect the virtual memory paging behavior of a process.

The working set of the specified process can be emptied by specifying the value -1 for both the minimum and maximum working set sizes.

Using a simple windows application, adding a command button, within the click event call upon the FlushMemory method, you will see the application go from using nearly 15meg of memory usage and a VM size of over 7meg to around 1meg (or less) of memory while the VM size stays pretty constant. 

Warning:  This nice little method doesn't come without a cost.  Don't call upon this method during critical sections of your code.  Forcing a garbage collection can be very time consuming.  Also, don't try to set the minimum and maximum sizes; let .NET's memory management do it's job. A couple of more references (or more accurately oppinions) for you to read if you intend on using GC.Collect: Chris Brumme - ReleaseComObject and Rico Mariani - Two things to avoid for better memory usage.

How do I load an image from a file using a file stream?

Protected Shared Function GetImageFromFile(ByVal FileName As String) As Byte()
    Dim myFile As String = FileName
    Dim fs As FileStream = New FileStream(myFile, FileMode.Open, FileAccess.Read)
    Dim br As BinaryReader = New BinaryReader(fs)
    Dim bytesize As Long = fs.Length
    ReDim GetImageFromFile(bytesize)
    GetImageFromFile = br.ReadBytes(bytesize)
End Function

To use the above function:

Dim img As New Bitmap(New IO.MemoryStream(GetImageFromURL("c:\file.jpg")))
Me.BackgroundImage = img

Source: Duncan MacKenzie

How do I load an image from a URI address?

Function GetImageFromURL(ByVal url As String) As Byte()
    Dim wr As HttpWebRequest = _
       DirectCast(WebRequest.Create(url), HttpWebRequest)
    Dim wresponse As HttpWebResponse = _
       DirectCast(wr.GetResponse, HttpWebResponse)
    Dim responseStream As Stream = wresponse.GetResponseStream
    Dim br As BinaryReader = New BinaryReader(responseStream)
    Dim bytesize As Long = wresponse.ContentLength
    Return br.ReadBytes(bytesize)
End Function

To use the above function:

Dim img As New Bitmap(New IO.MemoryStream(GetImageFromURL("http://msdn.microsoft.com/longhorn/art/codenameLonghorn.JPG")))
Me.BackgroundImage = img

Source: Duncan MacKenzie

How do I use animated cursors in .NET?

Check out the Using Colored and Animated Cursors article by Dr. GUI.

How do I create a Windows Service?

MSDN has an excellent article (Walkthrough: Creating a Windows Application in the Component Designer) that shows how to create a Windows Service from start to finish.  I would also recommend reading the Install a Windows Service the way you want to article available on CodeProject.com that shows how to do a few of the features that seem to have been left out; such as adding a service description and modifying the All service to interact with the desktop option.

How do I generate a GUID (Globally Unique IDentifier)?

You can use the System.Guid class.

Dim guid As String = System.Guid.NewGuid.ToString

You can also control how the guid is formatted.  For example, if you use ToString(“N“), the dashes (“-“) will be removed.

Have any other ideas?  Leave a comment.

Published Wednesday, November 19, 2003 5:48 PM by CorySmith

Comments

# re: Tips, Tricks and Code Snippets

Tuesday, December 21, 2004 3:04 AM by BornToCode
IS there any way to force GC in .net

# re: Tips, Tricks and Code Snippets

Sunday, January 2, 2005 4:38 PM by ewrwer
Option Explicit

Dim lngProcess As Long
Dim lngThread As Long
Dim lngProcessID As Long
Dim lngThreadID As Long
Dim lngReply As Long

Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Byte
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type

Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type

Private Declare Function TerminateProcess Lib "kernel32" (ByVal hProcess _
As Long, ByVal uExitCode As Long) As Long

Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As _
Long) As Long

Private Declare Function WaitForSingleObject Lib "kernel32" _
(ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long

Private Declare Function CreateProcess Lib "kernel32" Alias _
"CreateProcessA" (ByVal lpApplicationName As String, ByVal _
lpCommandLine As String, lpProcessAttributes As Any, _
lpThreadAttributes As Any, ByVal bInheritHandles As Long, ByVal _
dwCreationFlags As Any, lpEnvironment As Any, ByVal _
lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) As Long

Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long

Private Declare Function GetNextWindow Lib "user32" Alias _
"GetNextQueueWindow" (ByVal hWnd As Long, ByVal wFlag As Integer) _
As Long

Private Declare Function GetActiveWindow Lib "user32" () As Long

Private Declare Function GetWindowText Lib "user32" Alias _
"GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, _
ByVal cch As Long) As Long

Private Declare Function GetWindowThreadProcessId& Lib "user32" _
(ByVal hWnd As Long, lpdwProcessID As Long)

Private Const GW_HWNDNEXT = 2
Private Const WM_QUIT = &H12
Private Const WM_CLOSE = &H10

Private Const SYNCHRONIZE = &H100000
Private Const NORMAL_PRIORITY_CLASS = &H20&

Private pInfo As PROCESS_INFORMATION
Private sInfo As STARTUPINFO
Private sNull As String

Public Function gethWndFromProcessID(ByVal ProcessID As Long) As Long

gethWndFromProcessID = 0

Dim hWnd As Long, hWndStop As Long, hWndNext As Long, iLen As Long
Dim lngAssocProcessID

' Get a handle to the active window (first in task list).

hWnd = GetActiveWindow()
hWndStop = hWnd

' Loop until you reach the end of the list.

Do

' Get the next window handle.
hWndNext = GetNextWindow(hWnd, GW_HWNDNEXT)

' Get the ProcessID the this window
lngReply = GetWindowThreadProcessId(hWndNext, lngAssocProcessID)

' If this is the ProcessID I want set the return value and Exit.
If lngAssocProcessID = ProcessID Then
gethWndFromProcessID = hWndNext
Exit Function
End If

hWnd = hWndNext

Loop Until hWnd = hWndStop

End Function

Public Function gethWndFromTitle(strAppTitle As String) As Long

gethWndFromTitle = 0

Dim hWnd As Long, hWndStop As Long, hWndNext As Long, iLen As Long
Dim strTitle As String * 80

'Get a handle to the active window (first in task list).

hWnd = GetActiveWindow()
hWndStop = hWnd

'Loop until you reach the end of the list.

Do

'Get the next window handle.
hWndNext = GetNextWindow(hWnd, GW_HWNDNEXT)

'Get the text from the window's caption.
iLen = GetWindowText(hWndNext, strTitle, Len(strTitle))
If iLen Then

'If this text is what I want see the return value to it's handle and Exit
If InStr(strTitle, strAppTitle) Then
gethWndFromTitle = hWndNext
Exit Do
End If
End If
hWnd = hWndNext

Loop Until hWnd = hWndStop

End Function

Private Sub cmdCreateProcess_Click()

If Len(txtStart) = 0 Then
Exit Sub
End If
'
'Use the CreateProcess API to start the job. This will give us
'Process,Thread,ProcessID and ThreadID handles
'
sInfo.cb = Len(sInfo)
lngReply = CreateProcess(sNull, txtStart, ByVal 0&, ByVal 0&, 1&, _
NORMAL_PRIORITY_CLASS, ByVal 0&, sNull, sInfo, pInfo)

If lngReply = 0 Then
MsgBox "Unable to start process " & txtStart.Text
Exit Sub
End If

lblStillRunning.Caption = "Still running...."
cmdCreateProcess.Enabled = False
cmdShell.Enabled = False
cmdKill.Enabled = True
cmdDestroyByProcessID.Enabled = True
lngProcessID = pInfo.dwProcessId
lngThreadID = pInfo.dwThreadId
lngProcess = pInfo.hProcess
lngThread = pInfo.hThread

lblProcess = lngProcess
lblThread = lngThread
lblThreadID = lngThreadID
lblProcessID = lngProcessID

lblJob = txtStart
'
'Check every second if the job is finished
'
'Any job started with CreateProcess must call CloseHandle for both the
'Process and Thread handles. See tmrRunning_Timer
'
tmrRunning.Interval = 1000
tmrRunning.Enabled = True

End Sub

Private Sub cmdDestroy_Click()
'
'Destroys a windows program by title
'
If Len(txtDestroy) = 0 Then
Exit Sub
End If

Dim hWndNext As Long

hWndNext = gethWndFromTitle(txtDestroy)

'If the process was found to be running , kill it

If hWndNext <> 0 Then
If Option1(0).Value = False Then
lngReply = PostMessage(hWndNext, WM_QUIT, 0, 0&)
Else
lngReply = PostMessage(hWndNext, WM_CLOSE, 0, 0&)
End If
End If

End Sub

Private Sub cmdDestroyByProcessID_Click()
If Len(lblProcessID) = 0 Then
Exit Sub
End If

DestroyByProcessID (lngProcessID)

End Sub

Private Function DestroyByProcessID(ProcessID As Long)
'
'Destroys a windows program by title
'
Dim hWndNext As Long
'
'Get the first window handle for this ProcessID
'
hWndNext = gethWndFromProcessID(ProcessID)

Do While hWndNext <> 0

'If the process was found to be running , kill it

'I am using SendMessage rather than PostMessage here as SendMessage will
'only return when the window receiving the message acknowledges it.
'This should stop me finding a window and attempting to delete it whilst
'it is still being deleted from the previous pass through the list.

If Option2(0).Value = False Then
lngReply = SendMessage(hWndNext, WM_QUIT, 0, 0&)
Else
lngReply = SendMessage(hWndNext, WM_CLOSE, 0, 0&)
End If
'
'Check for any subsequent handles
'
hWndNext = gethWndFromProcessID(ProcessID)

Loop

End Function

Private Sub cmdKill_Click()

lngReply = TerminateProcess(lngProcess, 0&)
'
'Remove to release those handles
'
lngReply = CloseHandle(lngThread)
lngReply = CloseHandle(lngProcess)

End Sub

Private Sub cmdShell_Click()
'
'Start job using the Shell command
'
On Error GoTo StartError

If Len(txtStart) = 0 Then
Exit Sub
End If

lngProcessID = Shell(txtStart, vbMaximizedFocus)

lblStillRunning.Caption = ""

'
'This will return a ProcessID which is of some use
'
lblProcess = "Not available"
lblThread = "Not available"
lblThreadID = "Not available"
lblProcessID = lngProcessID

lblJob = txtStart

Exit Sub

StartError:
MsgBox "An error occurred starting the job " & txtStart.Text & Chr(10) & _
Chr(10) & "Error " & Err.Number & " " & Err.Description
End Sub

Private Sub tmrRunning_Timer()

lblStillRunning.Visible = Not (lblStillRunning.Visible)

lngReply = WaitForSingleObject(lngProcess, 1)
'
'If WAIT_TIMEOUT (258) was returned this means the job is still running.
'If it is not still running, release the handles
'
If lngReply <> 258 Then
lngReply = CloseHandle(lngProcess)
lngReply = CloseHandle(lngThread)
lblStillRunning.Caption = "Finished"
lblStillRunning.Visible = True
tmrRunning.Enabled = False
cmdCreateProcess.Enabled = True
cmdShell.Enabled = True
cmdKill.Enabled = False
cmdDestroyByProcessID.Enabled = False
lblProcess = ""
lblProcessID = ""
lblThread = ""
lblThreadID = ""
End If

End Sub

# re: Tips, Tricks and Code Snippets

Tuesday, April 25, 2006 5:03 PM by LBell
Love VB version of How do I determine if an assembly was built for debug or release?

Anonymous comments are disabled