Welcome to MSDN Blogs Sign in | Join | Help

Adventures in interop code: explore string interop memory

 

In Create an ActiveX control using ATL that you can use from Fox, Excel, VB6, VB.Net, I showed how to create a control (with which the user can interact) which can be hosted in many places.

 

Today’s sample creates a class in C++ that doesn’t necessarily have UI, and thus isn’t visually hosted, but can still be used from these clients.

 

It allows you to experiment with various coding techniques from within a VB (or C#) app.

 

I was playing around with BSTR and CComBSTR objects and had in the back of my mind that there’s a cache that can get in the way of inspecting shared memory use.

I was using IMallocSpy to track memory use and I knew I had to turn off that cache with SET OANOCACHE=1

 

Sometimes code in one module needs to allocate memory that another module needs to free. For example, across module, thread, process or machine boundaries, strings or structs can be passed by reference: my code may request a string from some other code. That other code needs to allocate the memory, but has no idea when to free it. It is my code’s responsibility to free it. See Memory Management Rules

                                                                                                                                                                                                                                                        

So to experiment, I first created a simple VB Interop sample, then added code to test memory allocations. Then I tried with and without OANOCACHE.

 

To set this option, the environment variable OANOCACHE must be set to 1 before the process starts. One way to do this is to open a CMD prompt, set it, then start the process from that command prompt.

Otherwise, from Control Panel->System->AdvancedSystemSettings->Advanced. (Double Advance means it’s super advanced! J)

 

Try running this code, with and without this option.

 

At first glance, I had originally thought that the way the Ole Automation cache worked was like a string lookup: if the string already exists in the cache, it would be ref counted.

Running this experiment proves otherwise.

 

Also see Raymond’s recent post: http://blogs.msdn.com/oldnewthing/archive/2009/11/27/9929238.aspx

 

 

 

 

Start Visual Studio (I was using VS 2008 for this sample)

 

File->New->Project->VB WPF Application. I called mine VBInterop.

 

Switch to Window1.xaml.vb, replace the contents with the VB code below.

 

Choose File->Add->New Project->VC++ ATL->ATL Project. I named mine AtlInterop. (Yes: you can add multiple projects to your solution.) The ATL wizard comes up. Just choose the defaults.

 

Choose Project->Add Class ->ATL->ATL Simple Object.  I named mine TestInterop. Note how the ATL Simple Object Wizard creates names for the various parts, like “ITestInterop” for the interface, “CTestInterop” for the class, etc.

 

Now choose View->Class View. Navigate to the ATLTestInterop->ITestInterop interface, right click->Add Method to bring up the “Add Method Wizard”

 

Give it a name “Test” with a single “in” parameter of type BSTR, named Parm1 (don’t forget to click on the “Add” button to add the parameter).

Also, add a single Out, Retval parameter of type LONG *, with a name pRetval.

 

The wizard changes a bunch of things, and is unfortunately not reentrant. If you forget to add the retval parameter, this is what it does: changes the IDL (Interface Definition Language), then make the corresponding change to the implementation of that interface (the TestIntop.h and .cpp files).

 

For adding a return value to the Test method. (All COM methods return an HRESULT, which is just an integer. To handle return values, an additional parameter is added and it’s passed ByRef).

 

I opened the AtlInterop.idl file, found the definition of the Test method

            [id(1), helpstring("method Test")] HRESULT Test([in] BSTR str);

 

and changed it to:

            [id(1), helpstring("method Test")] HRESULT Test([in] BSTR str, [out, retval] long *pRetval);

 

 

Go to the Solution Explorer, then open TestInterop.cpp, paste in the below VC code.

 

Build the solution, which builds TestInterop.dll . This can now be used to add a reference to the VB project. You get an error that AtlInteropLib doesn’t exist. Hit F8 (or dbl-click the error) to get to the VB code, then add a COM reference to ATLInterop 1.0 Type Library in the VB project.

 

Hit F5 to go. You can put a breakpoint on the btnClick method, but to break in your C++ code, you need to Project->Properties->Debug->Enable Unmanaged Code debugging.

 

Also, if you’re on a 64 bit OS, you may need to change Project->Compile->Advanced->Target CPU from AnyCPU to x86 to enable interop debugging.

 

An alternative: you can do all native debugging: set the StartupProject to be AtlInterop by right click on the AtlInterop Project in the Solution Explroer, then from the same menu change the Project->Property->Configuration->Debugging->Command line to point to the VBInterop EXE, set debugger type to “Native”, hit F5

 

When running, open Task manager and watch how much memory gets used when the loop runs.

Now comment out the SysFreeString line, F5 and observe.

Now try with OANOCACHE=1

 

See also:

Create an ActiveX control using ATL that you can use from Fox, Excel, VB6, VB.Net

How fast is interop code?

The OLE Memory Allocator: http://msdn.microsoft.com/en-us/library/ms688453(VS.85).aspx

 

<VB code>

Class Window1

    Private WithEvents m_btn As New Button With {.Content = "Push me"}

    Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        Me.Content = m_btn

    End Sub

 

    Sub btnClick() Handles m_btn.Click

        Dim a = New AtlInteropLib.TestInterop

        a.Test("From VB")

 

        'from managed code, you can use IMalloc to allocate shared memory

        ' For fun, look at all the other members of Marshal

        Dim mem = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(1000)

        System.Runtime.InteropServices.Marshal.FreeCoTaskMem(mem)

 

    End Sub

End Class

</VB code>

 

<VC code>

// TestInterop.cpp : Implementation of CTestInterop

 

#include "stdafx.h"

#include "TestInterop.h"

 

 

// CTestInterop

 

 

STDMETHODIMP CTestInterop::Test(BSTR str, LONG* pRetval)

{

      // TODO: Add your implementation code here

      for (int i = 0 ; i < 10000 ; i++)

      {

            char buf[1000];

            CComBSTR str("\nIter # ");

            str.Append(_itoa(i,buf,10));

 

            OutputDebugString(str);

            for (int j=0 ; j<100000 ; j++)

            {

                  BSTR  p = SysAllocString(L"Foobar");

                  if (!p)

                  {

                        ::MessageBox(0,str,L"Out of memory",0);

                        return E_OUTOFMEMORY;

 

                  }

                  SysFreeString(p);

            }

      }

      ::MessageBox(0,str,L"From VC!",0);

      return S_OK;

}

</VC code>

 

 

 

Posted by Calvin_Hsia | 1 Comments
Filed under: , , ,

You can develop code faster

You can make your Visual Studio experience faster.

 

Often you write some code in Visual Studio, hit F5 to test the code. Repeat.

 

Here’s a simple way to make this experience faster. (works with managed, native, mixed code).

 

When a process is started from the debugger, it is started with some special DEBUG flags which tell the entire process to use the Debug heap, which is quite a bit slower than the non-Debug heap.

 

If you start the process without the debugger (Ctrl-F5 from VS or from Explorer, Start Menu, etc), then Attach the debugger after the process has started, then those flags are not present.

 

Here’s proof.

 

Start Visual Studio

 

We’ll need to set symbols once:

Tools->Options->Debugging->Symbols->Symbol File Locations. Add a single entry:

http://msdl.microsoft.com/download/symbols

 

Click the checkbox: Search the above locations only when symbols are loaded manually (to make symbol loads faster)

 

Cache the symbols locally (type in something like C:\Symbols)

 

Choose File->New Project->VB  console application. (similarly with C#)

 

On the toolbar, make sure to select Release build (so you don’t think this is just with Debug bits)

 

Enable Project->Properties->Debug->Enable Unmanaged Code Debugging.

 

Add a single line of code to your fancy program:

        MsgBox("hi")

 

Or in C# if you want the VB MsgBox function: add a references to Microsoft.VisualBasic.dll

 

            Microsoft.VisualBasic.Interaction.MsgBox("hi",Microsoft.VisualBasic.MsgBoxStyle.OkOnly,"title");

 

Or you can add a reference to System.Windows.Forms.dll (both C# and VB)

            System.Windows.Forms.MessageBox.Show("hi");

 

 

Hit F5 to start the process under the debugger and see the MsgBox.

 

Without terminating your fancy program, switch back to VS and choose Debug->Windows->Modules

 

Right click on NTDLL.dll, load symbols. Hit Ctrl-B to bring up the Breakpoint dialog, paste in “_RtlDebugFreeHeap@12” and make sure the Language says C++.

 

The breakpoint dialog (Debug->Windows->Breakpoints) will show the Breakpoint with a solid red dot when the breakpoint is resolved. This Breakpoint is in the debug heap code.

 

Now dismiss the MsgBox

 

The debugger breaks. Here’s a sample call stack:

                ntdll.dll!_RtlpInsertFreeBlock@12()  + 0x3299 bytes       

                ntdll.dll!_RtlpDeCommitFreeBlock@16()  + 0x41 bytes  

                ntdll.dll!@RtlpFreeHeap@16()  + 0x2bb9 bytes 

                ntdll.dll!_RtlFreeHeap@12()  + 0x2e49 bytes      

                ntdll.dll!_RtlDebugFreeHeap@12()  + 0x1f8 bytes            

                ntdll.dll!@RtlpFreeHeap@16()  + 0x13cdd bytes               

>             ntdll.dll!_RtlFreeHeap@12()  + 0x2e49 bytes      

                kernel32.dll!_HeapFree@12()  + 0x14 bytes       

 

Now stop debugging, launch the process again, this time without the debugger (Ctrl-F5).

 

With the MsgBox showing, attach the debugger (Tools->Attach to Process->navigate to ConsoleApplication) in Native mode: select Native in Attach To: Select Code Type.

 

Repeat loading symbols, and setting the breakpoint. Now when you dismiss the MsgBox, the breakpoint doesn’t get hit because the Debug heap isn’t used.

 

So, how do I make my experience faster? One way is to hit Ctrl-F5, then Attach (if your fingers are fast).

 

However, there’s a simpler way:

Control->Panel->System Properties->Advanced System Settings->Environment Variables->System Variables.

Add a new Variable called “_NO_DEBUG_HEAP” with a value of “1”

 

Now repeat the steps above and you’ll not hit that breakpoint.

 

Beware of taking performance measurements when you start the process under the debugger. For example, I sometimes use OutputDebugString to output some times of some crude measurements. When running under the debugger, the performance of CPU bound code that doesn’t load modules or have breakpoints is pretty much the same as without a debugger, *IF* you don’t use the debug heap!

 

Of course, if you’re debugging heap problems, you may not want to set the environment variable.

 

See also:

 

Dynamically attaching a debugger

 

Posted by Calvin_Hsia | 4 Comments

Look at some hdden code in your VB project

Some code is always added to your VB project.

Try this:

 

Start VS (2008 or 2010 beta: the numbers below are for 2008)

 

File->New->Project->VB->Console App.

Build->Build Solution.

 

(I like to use temporary projects: see Use temporary projects in Visual Studio. I’m also using the General Profile (Tools->Import/Export Settings->Reset All Settings)

 

The build output window shows this:

------ Build started: Project: ConsoleApplication1, Configuration: Debug Any CPU ------

C:\Windows\Microsoft.NET\Framework\v3.5\Vbc.exe /noconfig /imports:Microsoft.VisualBasic,System,System.Collections,System.Collections.Generic,System.Data,System.Diagnostics,System.Linq,System.Xml.Linq /optioncompare:Binary /optionexplicit+ /optionstrict:custom /nowarn:42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 /optioninfer+ /rootnamespace:ConsoleApplication1 /doc:obj\Debug\ConsoleApplication1.xml /define:"CONFIG=\"Debug\",DEBUG=-1,TRACE=-1,_MyType=\"Console\",PLATFORM=\"AnyCPU\",_MYTYPE=\"Empty\"" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll","C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.DataSetExtensions.dll",C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll,C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Deployment.dll,C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll,C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll,"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll" /main:ConsoleApplication1.Module1 /debug+ /debug:full /filealign:512 /out:obj\Debug\ConsoleApplication1.exe /resource:obj\Debug\ConsoleApplication1.Resources.resources /target:exe Module1.vb "My Project\AssemblyInfo.vb" "My Project\Application.Designer.vb" "My Project\Resources.Designer.vb" "My Project\Settings.Designer.vb"

ConsoleApplication1 -> C:\Users\calvinh\AppData\Local\Temporary Projects\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe

========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========

 

 

You can make your programs smaller by removing some features.

 

Now right click on the Project in Solution Explorer, and “choose Open Folder in Windows Explorer”, navigate to the ConsoleApplication1.EXE file to see how big it is.

 

Open a Visual Studio Command Prompt( Program Files->Visual Studio->Visual Studio Tools) and do this:

 

dir "C:\Users\calvinh\AppData\Local\Temporary Projects\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe"

 

The size I get is 14,336 bytes.

 

Now use the Intermediate Language Disassembler :

Ildasm "C:\Users\calvinh\AppData\Local\Temporary Projects\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe"

 

You can navigate the tree view to see things like MyProject, MySettingsProperty, etc.

 

For example, even if you’re not using web services, this code is in your binary:

 

.method private static !!T  Create__Instance__<.ctor T>(!!T 'instance') cil managed

{

  .custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 )

  // Code size       32 (0x20)

  .maxstack  2

  .locals init ([0] !!T Create__Instance__,

           [1] bool VB$CG$t_bool$S0)

  IL_0000:  nop

  IL_0001:  ldarg.0

  IL_0002:  box        !!T

  IL_0007:  ldnull

  IL_0008:  ceq

  IL_000a:  stloc.1

  IL_000b:  ldloc.1

  IL_000c:  brfalse.s  IL_0018

  IL_000e:  call       !!0 [mscorlib]System.Activator::CreateInstance<!!0>()

  IL_0013:  stloc.0

  IL_0014:  br.s       IL_001e

  IL_0016:  br.s       IL_001d

  IL_0018:  nop

  IL_0019:  ldarg.0

  IL_001a:  stloc.0

  IL_001b:  br.s       IL_001e

  IL_001d:  nop

  IL_001e:  ldloc.0

  IL_001f:  ret

} // end of method MyWebServices::Create__Instance__

 

 

Where does all this code come from? Is there a simple way to get rid of it?

 

VB Intellisense and background compiler know about this extra stuff too.

Type in this:

        Dim r = My.WebService

Notice that after “My.” you see a a list of members of “My.”

 

Application

Computer

MySettings

Resources

Settings

User

WebServices

 

You can make the colored ones disappear.

Try this:

Project->Settings->Compiler->Advanced Compiler Options->Custom Constants

Put in _MYTYPE="Empty"

 

Immediately, you’ll see that there are compile errors for the My.Webservice line and that intellisense doesn’t show the colored members.

 

Now rebuild and see that the file size is 12,288.

 

This size difference is multiplied if you have multiple VB projects.

You can see some of the source VB that is used to generate this code:

 

File->Open->File-> "C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.VisualBasic.dll"

 

This will open the DLL in the VS Resource Editor. Navigate in the treeview to RCDATA->300 and you’ll see a binary dump of some code.

If you right click on the 300, choose Export->T.VB, then open T.VB, you see the contents below:

 

Now you can see the effect of changing “_MYTYPE” in the code.

 

See also:

Use temporary projects in Visual Studio

Host the CLR and Generate IL to call a MessageBox

 

 

<T.vb>

 

Option Strict On

Option Explicit On

Option Compare Binary

 

'* Copyright (C) Microsoft Corporation. All Rights Reserved.

 

#If TARGET = "module" AndAlso _MYTYPE = "" Then

#Const _MYTYPE="Empty"

#End If

 

#If _MYTYPE = "WindowsForms" Then

 

#Const _MYFORMS = True

#Const _MYWEBSERVICES = True

#Const _MYUSERTYPE = "Windows"

#Const _MYCOMPUTERTYPE = "Windows"

#Const _MYAPPLICATIONTYPE = "WindowsForms"

 

#ElseIf _MYTYPE = "WindowsFormsWithCustomSubMain" Then

 

#Const _MYFORMS = True

#Const _MYWEBSERVICES = True

#Const _MYUSERTYPE = "Windows"

#Const _MYCOMPUTERTYPE = "Windows"

#Const _MYAPPLICATIONTYPE = "Console"

 

#ElseIf _MYTYPE = "Windows" OrElse _MYTYPE = "" Then

 

#Const _MYWEBSERVICES = True

#Const _MYUSERTYPE = "Windows"

#Const _MYCOMPUTERTYPE = "Windows"

#Const _MYAPPLICATIONTYPE = "Windows"

 

#ElseIf _MYTYPE = "Console" Then

 

#Const _MYWEBSERVICES = True

#Const _MYUSERTYPE = "Windows"

#Const _MYCOMPUTERTYPE = "Windows"

#Const _MYAPPLICATIONTYPE = "Console"

 

#ElseIf _MYTYPE = "Web" Then

 

#Const _MYFORMS = False

#Const _MYWEBSERVICES = False

#Const _MYUSERTYPE = "Web"

#Const _MYCOMPUTERTYPE = "Web"

 

#ElseIf _MYTYPE = "WebControl" Then

 

#Const _MYFORMS = False

#Const _MYWEBSERVICES = True

#Const _MYUSERTYPE = "Web"

#Const _MYCOMPUTERTYPE = "Web"

 

#ElseIf _MYTYPE = "Custom" Then

 

#ElseIf _MYTYPE <> "Empty" Then

 

#Const _MYTYPE = "Empty"

 

#End If

 

#If _MYTYPE <> "Empty" Then

 

Namespace My

 

#If _MYAPPLICATIONTYPE = "WindowsForms" OrElse _MYAPPLICATIONTYPE = "Windows" OrElse _MYAPPLICATIONTYPE = "Console" Then

 

    <Global.System.CodeDom.Compiler.GeneratedCodeAttribute("MyTemplate", "8.0.0.0")> _

    <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> Partial Friend Class MyApplication

 

#If _MYAPPLICATIONTYPE = "WindowsForms" Then

        Inherits Global.Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase

#If TARGET = "winexe" Then

        <Global.System.STAThread(), Global.System.Diagnostics.DebuggerHidden(), Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _

        Friend Shared Sub Main(ByVal Args As String())

            Try

               Global.System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(MyApplication.UseCompatibleTextRendering())

            Finally

            End Try              

            My.Application.Run(Args)

        End Sub

#End If

 

#ElseIf _MYAPPLICATIONTYPE = "Windows" Then

        Inherits Global.Microsoft.VisualBasic.ApplicationServices.ApplicationBase

#ElseIf _MYAPPLICATIONTYPE = "Console" Then

        Inherits Global.Microsoft.VisualBasic.ApplicationServices.ConsoleApplicationBase

#End If '_MYAPPLICATIONTYPE = "WindowsForms"

 

    End Class

 

#End If '#If _MYAPPLICATIONTYPE = "WindowsForms" Or _MYAPPLICATIONTYPE = "Windows" or _MYAPPLICATIONTYPE = "Console"

 

#If _MYCOMPUTERTYPE <> "" Then

 

    <Global.System.CodeDom.Compiler.GeneratedCodeAttribute("MyTemplate", "8.0.0.0")> _

    <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> Partial Friend Class MyComputer

 

#If _MYCOMPUTERTYPE = "Windows" Then

        Inherits Global.Microsoft.VisualBasic.Devices.Computer

#ElseIf _MYCOMPUTERTYPE = "Web" Then

        Inherits Global.Microsoft.VisualBasic.Devices.ServerComputer

#End If

        <Global.System.Diagnostics.DebuggerHidden()> _

        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _

        Public Sub New()

            MyBase.New()

        End Sub

    End Class

#End If

 

    <Global.Microsoft.VisualBasic.HideModuleName()> _

    <Global.System.CodeDom.Compiler.GeneratedCodeAttribute("MyTemplate", "8.0.0.0")> _

    Friend Module MyProject

 

#If _MYCOMPUTERTYPE <> "" Then

        <Global.System.ComponentModel.Design.HelpKeyword("My.Computer")> _

        Friend ReadOnly Property Computer() As MyComputer

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Return m_ComputerObjectProvider.GetInstance()

            End Get

        End Property

 

        Private ReadOnly m_ComputerObjectProvider As New ThreadSafeObjectProvider(Of MyComputer)

#End If

 

#If _MYAPPLICATIONTYPE = "Windows" Or _MYAPPLICATIONTYPE = "WindowsForms" Or _MYAPPLICATIONTYPE = "Console" Then

        <Global.System.ComponentModel.Design.HelpKeyword("My.Application")> _

        Friend ReadOnly Property Application() As MyApplication

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Return m_AppObjectProvider.GetInstance()

            End Get

        End Property

        Private ReadOnly m_AppObjectProvider As New ThreadSafeObjectProvider(Of MyApplication)

#End If

 

#If _MYUSERTYPE = "Windows" Then

        <Global.System.ComponentModel.Design.HelpKeyword("My.User")> _

        Friend ReadOnly Property User() As Global.Microsoft.VisualBasic.ApplicationServices.User

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Return m_UserObjectProvider.GetInstance()

            End Get

        End Property

        Private ReadOnly m_UserObjectProvider As New ThreadSafeObjectProvider(Of Global.Microsoft.VisualBasic.ApplicationServices.User)

#ElseIf _MYUSERTYPE = "Web" Then

        <Global.System.ComponentModel.Design.HelpKeyword("My.User")> _

        Friend ReadOnly Property User() As Global.Microsoft.VisualBasic.ApplicationServices.WebUser

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Return m_UserObjectProvider.GetInstance()

            End Get

        End Property

        Private ReadOnly m_UserObjectProvider As New ThreadSafeObjectProvider(Of Global.Microsoft.VisualBasic.ApplicationServices.WebUser)

#End If

 

#If _MYFORMS = True Then

 

#Const STARTUP_MY_FORM_FACTORY = "My.MyProject.Forms"

 

        <Global.System.ComponentModel.Design.HelpKeyword("My.Forms")> _

        Friend ReadOnly Property Forms() As MyForms

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Return m_MyFormsObjectProvider.GetInstance()

            End Get

        End Property

 

        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _

        <Global.Microsoft.VisualBasic.MyGroupCollection("System.Windows.Forms.Form", "Create__Instance__", "Dispose__Instance__", "My.MyProject.Forms")> _

        Friend NotInheritable Class MyForms

            <Global.System.Diagnostics.DebuggerHidden()> _

            Private Shared Function Create__Instance__(Of T As {New, Global.System.Windows.Forms.Form})(ByVal Instance As T) As T

                If Instance Is Nothing OrElse Instance.IsDisposed Then

                    If m_FormBeingCreated IsNot Nothing Then

                        If m_FormBeingCreated.ContainsKey(GetType(T)) = True Then

                            Throw New Global.System.InvalidOperationException(Global.Microsoft.VisualBasic.CompilerServices.Utils.GetResourceString("WinForms_RecursiveFormCreate"))

                        End If

                    Else

                        m_FormBeingCreated = New Global.System.Collections.Hashtable()

                    End If

                    m_FormBeingCreated.Add(GetType(T), Nothing)

                    Try

                        Return New T()

                    Catch ex As Global.System.Reflection.TargetInvocationException When ex.InnerException IsNot Nothing

                        Dim BetterMessage As String = Global.Microsoft.VisualBasic.CompilerServices.Utils.GetResourceString("WinForms_SeeInnerException", ex.InnerException.Message)

                        Throw New Global.System.InvalidOperationException(BetterMessage, ex.InnerException)

                    Finally

                        m_FormBeingCreated.Remove(GetType(T))

                    End Try

                Else

                    Return Instance

                End If

            End Function

 

            <Global.System.Diagnostics.DebuggerHidden()> _

            Private Sub Dispose__Instance__(Of T As Global.System.Windows.Forms.Form)(ByRef instance As T)

                instance.Dispose()

                instance = Nothing

            End Sub

 

            <Global.System.Diagnostics.DebuggerHidden()> _

            <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _

            Public Sub New()

               MyBase.New()

            End Sub

 

            <Global.System.ThreadStatic()> Private Shared m_FormBeingCreated As Global.System.Collections.Hashtable

 

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> Public Overrides Function Equals(ByVal o As Object) As Boolean

                Return MyBase.Equals(o)

            End Function

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> Public Overrides Function GetHashCode() As Integer

                Return MyBase.GetHashCode

            End Function

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> _

            Friend Overloads Function [GetType]() As Global.System.Type

                Return GetType(MyForms)

            End Function

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> Public Overrides Function ToString() As String

                Return MyBase.ToString

            End Function

        End Class

 

        Private m_MyFormsObjectProvider As New ThreadSafeObjectProvider(Of MyForms)

 

#End If

 

#If _MYWEBSERVICES = True Then

 

        <Global.System.ComponentModel.Design.HelpKeyword("My.WebServices")> _

        Friend ReadOnly Property WebServices() As MyWebServices

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Return m_MyWebServicesObjectProvider.GetInstance()

            End Get

        End Property

 

        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _

        <Global.Microsoft.VisualBasic.MyGroupCollection("System.Web.Services.Protocols.SoapHttpClientProtocol", "Create__Instance__", "Dispose__Instance__", "")> _

        Friend NotInheritable Class MyWebServices

 

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never), Global.System.Diagnostics.DebuggerHidden()> _

            Public Overrides Function Equals(ByVal o As Object) As Boolean

                Return MyBase.Equals(o)

            End Function

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never), Global.System.Diagnostics.DebuggerHidden()> _

            Public Overrides Function GetHashCode() As Integer

                Return MyBase.GetHashCode

            End Function

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never), Global.System.Diagnostics.DebuggerHidden()> _

            Friend Overloads Function [GetType]() As Global.System.Type

                Return GetType(MyWebServices)

            End Function

            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never), Global.System.Diagnostics.DebuggerHidden()> _

            Public Overrides Function ToString() As String

                Return MyBase.ToString

            End Function

 

            <Global.System.Diagnostics.DebuggerHidden()> _

            Private Shared Function Create__Instance__(Of T As {New})(ByVal instance As T) As T

                If instance Is Nothing Then

                    Return New T()

                Else

                    Return instance

                End If

            End Function

 

            <Global.System.Diagnostics.DebuggerHidden()> _

            Private Sub Dispose__Instance__(Of T)(ByRef instance As T)

                instance = Nothing

            End Sub

 

            <Global.System.Diagnostics.DebuggerHidden()> _

            <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _

            Public Sub New()

                MyBase.New()

            End Sub

        End Class

 

        Private ReadOnly m_MyWebServicesObjectProvider As New ThreadSafeObjectProvider(Of MyWebServices)

#End If

 

#If _MYTYPE = "Web" Then

 

        <Global.System.ComponentModel.Design.HelpKeyword("My.Request")> _

        Friend ReadOnly Property Request() As Global.System.Web.HttpRequest

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Dim CurrentContext As Global.System.Web.HttpContext = Global.System.Web.HttpContext.Current

                If CurrentContext IsNot Nothing Then

                    Return CurrentContext.Request

                End If

                Return Nothing

            End Get

        End Property

 

        <Global.System.ComponentModel.Design.HelpKeyword("My.Response")> _

        Friend ReadOnly Property Response() As Global.System.Web.HttpResponse

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Dim CurrentContext As Global.System.Web.HttpContext = Global.System.Web.HttpContext.Current

                If CurrentContext IsNot Nothing Then

                    Return CurrentContext.Response

                End If

                Return Nothing

            End Get

        End Property

 

        <Global.System.ComponentModel.Design.HelpKeyword("My.Application.Log")> _

        Friend ReadOnly Property Log() As Global.Microsoft.VisualBasic.Logging.AspLog

            <Global.System.Diagnostics.DebuggerHidden()> _

            Get

                Return m_LogObjectProvider.GetInstance()

            End Get

        End Property

 

        Private ReadOnly m_LogObjectProvider As New ThreadSafeObjectProvider(Of Global.Microsoft.VisualBasic.Logging.AspLog)

 

#End If  '_MYTYPE="Web"

 

        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _

        <Global.System.Runtime.InteropServices.ComVisible(False)> _

        Friend NotInheritable Class ThreadSafeObjectProvider(Of T As New)

            Friend ReadOnly Property GetInstance() As T

#If TARGET = "library" Then

                <Global.System.Diagnostics.DebuggerHidden()> _

                Get

                    Dim Value As T = m_Context.Value

                    If Value Is Nothing Then

                        Value = New T

                        m_Context.Value() = Value

                    End If

                    Return Value

                End Get

#Else

                <Global.System.Diagnostics.DebuggerHidden()> _

                Get

                    If m_ThreadStaticValue Is Nothing Then m_ThreadStaticValue = New T

                    Return m_ThreadStaticValue

                End Get

#End If

            End Property

 

            <Global.System.Diagnostics.DebuggerHidden()> _

            <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _

            Public Sub New()

                MyBase.New()

            End Sub

 

#If TARGET = "library" Then

            Private ReadOnly m_Context As New Global.Microsoft.VisualBasic.MyServices.Internal.ContextValue(Of T)

#Else

            <Global.System.Runtime.CompilerServices.CompilerGenerated(), Global.System.ThreadStatic()> Private Shared m_ThreadStaticValue As T

#End If

        End Class

    End Module

End Namespace

#End If

 

</T.vb>

 

Posted by Calvin_Hsia | 3 Comments
Filed under: ,

What is your computer doing with all that memory? Write your own memory browser

 

What is your computer doing with all that memory? There are various kinds of memory allocated and used in each process. These include:

·         Managed memory (VB.Net, C#, managed C++)

·         Heap memory

·         Stacks

·         Images (files loaded into a process)

 

VirtualAlloc is the basis of these allocations. If a process needs more of these types of memory, VirtualAlloc is called.

 

You can inspect the various kinds of memory by running the sample code below. It offers a list of current processes, from which you dbl-click one to inspect.

 

Once you choose a process, a snapshot of all Virtual memory allocations is shown in a ListView, along with the size, type, etc. If it’s an image, the filename is shown too.  Hovering over addresses shows the memory contents in a tooltip. Double click to open another window with the entire memory contents. You can read strings in memory in these displays.

 

One can imagine this being the base of your own custom Task Manager. Perhaps you want to search for all strings in all processes. “Microsoft” occurs quite often!

 

Imagine taking a memory snapshot of a process, doing something in that process, then taking another snapshot and displaying the difference.

 

Caveats: you may not have rights to read some processes or some memory. Also, if you build to 32 bit (Project Options->Compile->AdvancedCompileOptions->TargetCPU->AnyCpu) and run on 64 bit, you might have problems.

 

It can examine both 32 and 64 bit processes.  If you get Access Denied on some processes, you might have better luck running it as Administrator

 

Start Visual Studio 2008

File->New Project->VB WPF Application.

 

Open Window1.xaml.vb, replace the contents (about 650 lines) with the sample below.

 

 

See also:

Create your own media browser: Display your pictures, music, movies in a XAML tooltip

How to Create dynamic XAML to display arbitrary XML

Remove double spaces from pasted code samples in blog

 

 

 

 

<code sample>

Imports System.Runtime.InteropServices

 

Class Window1

    Private WithEvents _btnRefresh As Button

 

    Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        Me.Width = 800

        Me.Height = 800

 

        RefreshContent()

    End Sub

 

    Sub RefreshContent()

        Dim q = From proc In System.Diagnostics.Process.GetProcesses _

                Select proc.Id, _

                       proc.ProcessName, _

                       proc.WorkingSet64, _

                       proc.PrivateMemorySize64, _

                       Is64 = ProcessType(proc) _

                Order By ProcessName

        Me.Title = "Choose a process. Count = " + q.Count.ToString

        Dim XAML = _

        <Grid

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            >

            <Grid.RowDefinitions>

                <RowDefinition MaxHeight="20"/>

                <RowDefinition/>

            </Grid.RowDefinitions>

            <StackPanel Orientation="Horizontal" Grid.Row="0">

                <Button Name="btnRefresh">Refresh</Button>

            </StackPanel>

            <DockPanel Grid.Row="1" Name="dp">

 

            </DockPanel>

        </Grid>

        Dim gr = CType(System.Windows.Markup.XamlReader.Load(XAML.CreateReader), Grid)

        _btnRefresh = CType(gr.FindName("btnRefresh"), Button)

        Dim dp = CType(gr.FindName("dp"), DockPanel)

        dp.Children.Add(New Browse(q, delDblClick:=AddressOf OnProcWindDblClick))

        Me.Content = gr

    End Sub

 

    Shared Function ProcessType(ByVal proc As Process) As String

        Dim is32 = False

        If IntPtr.Size = 4 Then ' 64 bit=8, 32=4

            is32 = True

        End If

        Dim retProcType = "64"

        Try

            Dim IsrunningUnderWow64 = False

            If IsWow64Process(proc.Handle, IsrunningUnderWow64) AndAlso IsrunningUnderWow64 Then

            End If

            If IsrunningUnderWow64 OrElse is32 Then

                retProcType = "32"

            End If

 

        Catch ex As Exception

            retProcType = ex.Message

        End Try

        Return retProcType

    End Function

 

    <DllImport("Kernel32.dll", SetLastError:=True, CallingConvention:=CallingConvention.Winapi)> _

        Public Shared Function IsWow64Process( _

        ByVal hProcess As IntPtr, _

        <MarshalAs(UnmanagedType.Bool)> ByRef wow64Process As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean

    End Function

 

    Sub OnbtnRefresh_Click() Handles _btnRefresh.Click

        RefreshContent()

    End Sub

 

    Sub OnProcWindDblClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        Try

            Dim lv = TryCast(sender, Browse)

            If lv IsNot Nothing Then

                Dim tb = TryCast(e.OriginalSource, TextBlock)

                If tb IsNot Nothing Then

                    Dim dt = tb.DataContext

                    If dt IsNot Nothing Then

                        Dim procTarget = tb.Text

                        Dim q = From a In Process.GetProcessesByName(procTarget)

                        If q.Count > 0 Then

                            Dim oWin = New VirtMemWindow(q.First) With {.Owner = Me}

                        End If

                    End If

                End If

            End If

        Catch ex As Exception

            MessageBox.Show(ex.Message)

        End Try

    End Sub

 

    Class VirtMemWindow

        Inherits Window

        Private _proc As Process

 

        Private WithEvents _btnAggregate As Button

        Private _Is32 As Boolean

 

        Sub New(ByVal proc As Process)

            Dim mbi As New MEMORY_BASIC_INFORMATION

            _proc = proc

            Dim ptype = ProcessType(_proc)

 

            If ptype = "32" Then

                _Is32 = True

            ElseIf ptype = "64" Then

                _Is32 = False

            Else

                Throw New InvalidOperationException(ptype)

            End If

            If IntPtr.Size = 4 AndAlso Not _Is32 Then

                Throw New InvalidOperationException("32 bit app can't examine 64 bit process")

            End If

 

            Dim lpMem As UInt64 = 0

            Do While VirtualQueryEx(_proc.Handle, New UIntPtr(lpMem), mbi, Marshal.SizeOf(mbi)) = Marshal.SizeOf(mbi)

                _virtAllocs.Add(mbi)

                lpMem = mbi.BaseAddress.ToUInt64 + mbi.RegionSize.ToUInt64

            Loop

            Dim XAML = _

            <Grid

                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                >

                <Grid.RowDefinitions>

                    <RowDefinition MaxHeight="20"/>

                    <RowDefinition/>

                </Grid.RowDefinitions>

                <StackPanel Orientation="Horizontal" Grid.Row="0">

                    <Button Name="btnAgg">Aggregate</Button>

                </StackPanel>

                <DockPanel Grid.Row="1" Name="dp">

 

                </DockPanel>

            </Grid>

            Dim gr = CType(System.Windows.Markup.XamlReader.Load(XAML.CreateReader), Grid)

            _btnAggregate = CType(gr.FindName("btnAgg"), Button)

            Dim dp = CType(gr.FindName("dp"), DockPanel)

            If _virtAllocs.Count = 0 Then

                dp.Children.Add(New TextBlock With {.Text = "No items found. Perhaps 64/32 bit or Rights issue"})

            Else

                Dim q = From a In _virtAllocs _

                        Select AllocationBase = Format64or32(a.AllocationBase), _

                                BaseAddress = Format64or32(a.BaseAddress), _

                                RegionSize = Format64or32(a.RegionSize), _

                                Filename = GetFileNameFromMBI(a), _

                                AllocationProtect = a.AllocationProtect.ToString, _

                                AllocationState = a.State.ToString, _

                                Type = CType(a.lType, AllocationType).ToString _

                                Order By Filename.ToString.Length Descending

 

                Me.Title += String.Format("{0} Virtual Memory Use #items={1}  ", proc.ProcessName, q.Count)

 

                dp.Children.Add(New Browse(q, _

                                        delMouseMove:=AddressOf On_VirtmemMouseMove, _

                                        deldblclick:=AddressOf On_VirtmemDblClick))

 

            End If

            Me.Content = gr

            Me.Show()

        End Sub

 

        Function Format64or32(ByVal num As UIntPtr) As String

            Dim retval = ""

            If _Is32 Then

                retval = num.ToUInt32.ToString("x8")

            Else

                retval = num.ToUInt64.ToString("x16")

            End If

            Return retval

        End Function

 

        Sub OnbtnAggClick() Handles _btnAggregate.Click

            Dim qAgg = From a In _virtAllocs _

                       Group By AllocationType = a.lType.ToString Into Tot = Sum(a.RegionSize.ToUInt64), Cnt = Count(a.RegionSize.ToUInt64 > 0)

            Dim oWin = New Window With {.Title = "Aggregate"}

            oWin.Content = New Browse(qAgg)

            oWin.Owner = Me

            oWin.Show()

        End Sub

 

        Private _virtAllocs As New List(Of MEMORY_BASIC_INFORMATION)

 

        Sub On_VirtmemDblClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

            Try

                Dim lv = TryCast(sender, Browse)

                If lv IsNot Nothing Then

                    Dim tb = TryCast(e.OriginalSource, TextBlock)

                    If tb IsNot Nothing Then

                        Select Case tb.Name

                            Case "AllocationBase", "BaseAddress"

                                ClearPriorToolTipIfAny()

                                Dim baseAddr = Convert.ToInt64(tb.Text, 16) ' convert from hex (base 16)

                                Dim allocQuery = From a In _virtAllocs _

                                        Where If(tb.Name = "AllocationBase", a.AllocationBase, a.BaseAddress) = baseAddr

 

 

                                If allocQuery.Count > 0 Then

                                    Dim virtAlloc = allocQuery.First

                                    Dim strAddrDump = GetAddressDump( _

                                        virtAlloc.BaseAddress, _

                                        virtAlloc.RegionSize, _

                                        nMax:=0 _

                                        )

 

                                    Dim tbxDump = New TextBox With { _

                                        .Text = strAddrDump, _

                                        .VerticalScrollBarVisibility = ScrollBarVisibility.Auto, _

                                        .BorderThickness = New Windows.Thickness(0), _

                                        .FontFamily = New FontFamily("Courier New"), _

                                    .FontSize = 10 _

                                    }

                                    Dim oWin = New Window With { _

                                        .Title = String.Format("Mem dump Address = {0:x8}, size = {1}", _

                                                                tb.Text, _

                                                                virtAlloc.RegionSize _

                                                                ) _

                                    }

                                    oWin.Content = tbxDump

                                    oWin.Owner = Me

                                    oWin.Show()

                                End If

                        End Select

                    End If

 

                End If

 

            Catch ex As Exception

 

            End Try

 

        End Sub

 

        Sub ClearPriorToolTipIfAny()

            If _LastTipObj IsNot Nothing Then

                Dim lastTip = CType(_LastTipObj.ToolTip, ToolTip)

                lastTip.IsOpen = False

                _LastTipObj = Nothing

            End If

        End Sub

 

        Private _LastTipObj As FrameworkElement

        Sub On_VirtmemMouseMove(ByVal sender As Object, ByVal e As RoutedEventArgs)

            Dim lv = TryCast(sender, Browse)

            If lv IsNot Nothing Then

                Dim tb = TryCast(e.OriginalSource, TextBlock)

                If tb IsNot Nothing Then

                    '                    Dim o = lv.ItemContainerGenerator.ContainerFromItem(tb)

                    If _LastTipObj IsNot Nothing Then

                        Dim lastTip = CType(_LastTipObj.ToolTip, ToolTip)

                        If tb Is _LastTipObj Then ' over same obj: don't create a new tip

                            Return

                        Else

                            ' different object: close the tip

                            lastTip.IsOpen = False

                        End If

                    End If

 

                    Dim dt = tb.DataContext

                    If dt IsNot Nothing Then

                        Dim ttipObj = New ToolTip

                        ttipObj.PlacementTarget = tb

                        ttipObj.Placement = Controls.Primitives.PlacementMode.Bottom

                        Select Case tb.Name

                            Case "AllocationBase", "BaseAddress"

                                Dim baseAddr = Convert.ToUInt64(tb.Text, 16) ' convert from hex (base 16)

                                Dim q = From a In _virtAllocs _

                                        Where If(tb.Name = "AllocationBase", a.AllocationBase, a.BaseAddress) = baseAddr

 

                                If q.Count > 0 Then

                                    Dim mbi = q.First

                                    Dim baddr = If(tb.Name = "AllocationBase", mbi.AllocationBase, mbi.BaseAddress)

                                    If CType(mbi.State, AllocationState) <> AllocationState.MEM_FREE AndAlso baddr.ToUInt64 > &H1000 Then

 

                                        Dim strAddrDump = GetAddressDump( _

                                            baddr, _

                                            mbi.RegionSize)

 

                                        Dim tbxDump = New TextBox With { _

                                            .Text = strAddrDump, _

                                            .BorderThickness = New Windows.Thickness(0), _

                                            .FontFamily = New FontFamily("Courier New"), _

                                            .FontSize = 10, _

                                            .Background = Brushes.LightYellow _

                                        }

                                        Dim sp = New StackPanel With {.Orientation = Orientation.Vertical}

                                        If CType(mbi.lType, AllocationType) = AllocationType.MEM_IMAGE Then

                                            Dim sbFilename As New Text.StringBuilder(300)

                                            Dim rr = GetFileNameFromMBI(mbi)

                                            GetModuleFileNameEx(_proc.Handle, New UIntPtr(mbi.AllocationBase.ToUInt64), sbFilename, sbFilename.Capacity)

                                            sp.Children.Add(New TextBlock With {.Text = "Filename = " + sbFilename.ToString})

                                        End If

                                        sp.Children.Add(tbxDump)

                                        ttipObj.Content = sp

                                    Else

                                        ttipObj.Content = baddr.ToString + " bad address for mem dump"

                                    End If

 

                                End If

                            Case "RegionSize"

                                ttipObj.Content = String.Format(" Base 10: {0}", Convert.ToInt32(tb.Text, 16))

                            Case "Where", "Filename"

                                ttipObj.Content = tb.Text

                            Case Else

                                Return

                        End Select

                        ToolTipService.SetShowDuration(tb, 100000)

 

                        ttipObj.IsOpen = True

                        tb.ToolTip = ttipObj

                        _LastTipObj = tb

                    End If

                End If

            End If

 

        End Sub

 

        Const BlockSize As Integer = 1024

        <StructLayout(LayoutKind.Sequential)> _

        Structure ProcMemBlock

            <MarshalAs(UnmanagedType.ByValArray, sizeconst:=BlockSize)> _

            Dim data() As Byte

        End Structure

 

        Public Function GetAddressDump(ByVal nAddress As UIntPtr, ByVal nTotalBytesToRead As UIntPtr, Optional ByVal nMax As Integer = 1024) As String

            Dim sbAddrDump As New Text.StringBuilder

            Try

                If nMax > 0 Then

                    nTotalBytesToRead = Math.Min(nMax, nTotalBytesToRead.ToUInt64)

                End If

                Dim blk = New ProcMemBlock

                Dim nBytesLeftToRead = nTotalBytesToRead

                Do While nBytesLeftToRead.ToUInt64 > 0

                    Dim nBytesToReadForThisBlock = Math.Min(nBytesLeftToRead.ToUInt64, BlockSize)

                    Dim nBytesRead As UIntPtr = 0

                    If ReadProcessMemory(_proc.Handle, nAddress, blk, nBytesToReadForThisBlock, nBytesRead) Then

                        nBytesLeftToRead = nBytesLeftToRead.ToUInt64 - nBytesRead.ToUInt64

                        Dim innerCnt = 0

                        Dim strBytes = ""

                        Dim strChars = ""

                        For i = 0 To nBytesRead.ToUInt64 - 1 Step 4

                            If innerCnt Mod 8 = 0 Then

                                If i > 0 Then ' if we're ending a line, dump out the bytes and chars

                                    sbAddrDump.AppendLine("  " + strBytes + "  " + strChars)

                                    strBytes = ""

                                    strChars = ""

                                Else

                                    sbAddrDump.AppendLine()

                                End If

                                If _Is32 Then

                                    sbAddrDump.Append(String.Format("{0:x8} : ", nAddress.ToUInt32 + CUInt(i)))

                                Else

                                    Dim offset = nAddress.ToUInt64 + CUInt(i)

                                    sbAddrDump.Append(String.Format("{0:x16} : ", nAddress.ToUInt64 + CUInt(i)))

                                End If

                            End If

                            innerCnt += 1

                            Dim dword = 0

                            For p = 0 To 3

                                dword += CInt(blk.data(i + p)) << shifts(3 - p)

                            Next

                            '                        Dim dword = Marshal.ReadIntPtr(New IntPtr(nAddress + i)).ToInt32

                            toBytes(dword, strBytes, strChars)

                            sbAddrDump.Append(String.Format("{0:x8} ", dword))

                        Next

                        ' some leftovers

                        Dim startLeftover = innerCnt

                        Do While innerCnt Mod 8 > 0

                            sbAddrDump.Append("         ")

                            innerCnt += 1

                        Loop

                        sbAddrDump.AppendLine("  " + strBytes + "  ")

                        innerCnt = startLeftover

                        Do While innerCnt Mod 8 > 0

                            sbAddrDump.Append("   ")

                            innerCnt += 1

                        Loop

                        sbAddrDump.AppendLine(strChars)

 

                        Do While innerCnt Mod 8 > 0

                            sbAddrDump.Append("         ")

                            innerCnt += 1

                        Loop

                    Else

                        Exit Do

                    End If

                    nAddress = nAddress.ToUInt64 + nBytesRead.ToUInt64

                Loop

            Catch ex As Exception

                sbAddrDump.Append("Exception when reading " + nAddress.ToString + " " + ex.Message)

 

            End Try

 

            Return sbAddrDump.ToString

        End Function

 

        Const badchars = vbCr + vbLf + vbTab + Chr(&HB) + Chr(&HC)

        Private Shared shifts As Integer() = {24, 16, 8, 0}

        Shared Sub toBytes(ByVal num As Integer, ByRef strBytes As String, ByRef strChars As String)

            For i = 3 To 0 Step -1

                Dim abyte = CByte((num >> shifts(i) And &HFF))

                strBytes += String.Format("{0:x2} ", abyte)

                Dim thechar = " "

                If abyte > 15 AndAlso abyte < 127 Then

                    If badchars.IndexOf(thechar) < 0 Then

                        thechar = Chr(abyte).ToString

                    End If

                End If

                strChars += thechar

            Next

        End Sub

 

        <DllImport("kernel32.dll", SetLastError:=True)> _

        Public Shared Function ReadProcessMemory( _

               ByVal hProcess As IntPtr, _

               ByVal lpBaseAddress As UIntPtr, _

               ByRef lpBuffer As ProcMemBlock, _

               ByVal dwSize As Integer, _

               ByRef lpNumberOfBytesRead As Integer _

         ) As Integer

        End Function

 

        <DllImport("psapi")> _

        Shared Function GetModuleFileNameEx(ByVal hProcess As IntPtr, ByVal hModule As UIntPtr, ByVal lpFileName As Text.StringBuilder, ByVal nSize As Integer) As Integer

        End Function

 

        <DllImport("kernel32")> _

        Shared Function VirtualQueryEx( _

                                    ByVal hProcess As IntPtr, _

                                    ByVal lpAddress As UIntPtr, _

                                    ByRef mbi As MEMORY_BASIC_INFORMATION, _

                                    ByVal dwLength As UInteger) As UInteger

        End Function

 

        <StructLayout(LayoutKind.Sequential)> _

        Structure MEMORY_BASIC_INFORMATION

            Dim BaseAddress As UIntPtr

            Dim AllocationBase As UIntPtr

            Dim AllocationProtect As AllocationProtect

            Dim RegionSize As UIntPtr

            Dim State As AllocationState

            Dim Protect As AllocationProtect

            Dim lType As AllocationType

        End Structure

 

        <Flags()> _

        Enum AllocationProtect

            PAGE_EXECUTE = &H10

            PAGE_EXECUTE_READ = &H20

            PAGE_EXECUTE_READWRITE = &H40

            PAGE_EXECUTE_WRITECOPY = &H80

            PAGE_NOACCESS = &H1

            PAGE_READONLY = &H2

            PAGE_READWRITE = &H4

            PAGE_WRITECOPY = &H8

            PAGE_GUARD = &H100

            PAGE_NOCACHE = &H200

            PAGE_WRITECOMBINE = &H400

        End Enum

 

        <Flags()> _

        Enum AllocationType

            MEM_IMAGE = &H1000000

            MEM_MAPPED = &H40000

            MEM_PRIVATE = &H20000

        End Enum

 

        <Flags()> _

        Enum AllocationState

            MEM_COMMIT = &H1000

            MEM_FREE = &H10000

            MEM_RESERVE = &H2000

        End Enum

 

        Function GetFileNameFromMBI(ByVal mbi As MEMORY_BASIC_INFORMATION) As String

            Dim retval = ""

            If CType(mbi.lType, AllocationType) = AllocationType.MEM_IMAGE Or True Then

                If mbi.AllocationBase.ToUInt64 > 0 Then

                    Dim sbFilename As New Text.StringBuilder(300)

                    If GetModuleFileNameEx(_proc.Handle, New UIntPtr(mbi.AllocationBase.ToUInt64), sbFilename, sbFilename.Capacity) > 0 Then

                        retval = sbFilename.ToString

                    End If

                End If

            End If

            Return retval

        End Function

 

    End Class

End Class

 

 

' see http://blogs.msdn.com/calvin_hsia/archive/2007/12/06/6684376.aspx

Public Class Browse

    Inherits ListView

 

    Private _Browse As Browse = Me

    Private _delDblClick As BrowEventDelegate

    Private _delMouseMove As BrowEventDelegate

    Public _query As IEnumerable

 

    Delegate Sub BrowEventDelegate(ByVal sender As Object, ByVal e As RoutedEventArgs)

 

    Sub New( _

               ByVal Query As IEnumerable, _

               Optional ByVal delDblClick As BrowEventDelegate = Nothing, _

               Optional ByVal delMouseMove As BrowEventDelegate = Nothing, _

               Optional ByVal fAllowHeaderClickSort As Boolean = True)

 

        _query = Query

        Dim gv As New GridView

 

        _Browse.View = gv

        _Browse.ItemsSource = Query

        If fAllowHeaderClickSort Then

            _Browse.AddHandler(GridViewColumnHeader.ClickEvent, New RoutedEventHandler(AddressOf HandleHeaderClick))

        End If

        If delDblClick IsNot Nothing Then

            _delDblClick = delDblClick

            _Browse.AddHandler(MouseDoubleClickEvent, New RoutedEventHandler(AddressOf OnRowDblClickEvent))

        End If

        If delMouseMove IsNot Nothing Then

            _delMouseMove = delMouseMove

            _Browse.AddHandler(MouseMoveEvent, New RoutedEventHandler(AddressOf OnMouseMoveEvent))

 

        End If

 

        If Query.GetType.GetInterface(GetType(IEnumerable(Of )).FullName).GetGenericArguments(0).Name = "XElement" Then ' It's XML

            Dim Elem1 = CType(Query, IEnumerable(Of XElement))(0).Elements ' Thanks Avner!

            For Each Item In Elem1

                Dim gvc As New GridViewColumn

                gvc.Header = Item.Name.LocalName

                gv.Columns.Add(gvc)

                Dim bind As New Binding("Element[" + Item.Name.LocalName + "].Value")

                gvc.DisplayMemberBinding = bind

                gvc.Width = 180

            Next

        Else ' it's some anonymous type like "VB$AnonymousType_1`3". Let's use reflection to get the column names

            For Each mem In From mbr In _

                    Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName) _

                    .GetGenericArguments()(0).GetMembers _

                    Where mbr.MemberType = Reflection.MemberTypes.Property

 

                Dim datatype = CType(mem, Reflection.PropertyInfo)

                Dim coltype = datatype.PropertyType.Name

                Select Case coltype

                    Case "Int32", "String", "Int64"

                        Dim gvc As New GridViewColumn

                        gvc.Header = mem.Name

                        gv.Columns.Add(gvc)

                        Dim XAMLdt = _

                        <DataTemplate

                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                            >

                            <StackPanel Orientation="Horizontal">

                                <TextBlock Name=<%= mem.Name %>

                                    Text=<%= If(coltype = "String", _

                                             "{Binding Path=" + mem.Name + "}", _

                                             "{Binding Path=" + mem.Name + "}") %>

                                    >

                                </TextBlock>

                            </StackPanel>

                        </DataTemplate>

                        gvc.CellTemplate = CType(System.Windows.Markup.XamlReader.Load(XAMLdt.CreateReader), DataTemplate)

                        If coltype <> "String" Then

                            '                           gvc.Width = 80

                            gvc.Width = Double.NaN ' auto

                        Else

                            '                                gvc.DisplayMemberBinding = New Binding(mem.Name)

                            '                            gvc.Width = 180

                            gvc.Width = Double.NaN ' auto

                        End If

                End Select

            Next

        End If

        Dim XAMLlbStyle = _

        <Style

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            TargetType="ListBoxItem">

            <Setter Property="Foreground" Value="Blue"/>

            <Style.Triggers>

                <Trigger Property="IsSelected" Value="True">

                    <Setter Property="Foreground" Value="White"/>

                    <Setter Property="Background" Value="Aquamarine"/>

                </Trigger>

                <Trigger Property="IsMouseOver" Value="True">

                    <Setter Property="Foreground" Value="Red"/>

                </Trigger>

            </Style.Triggers>

        </Style>

        _Browse.ItemContainerStyle = CType(Windows.Markup.XamlReader.Load(XAMLlbStyle.CreateReader), Windows.Style)

    End Sub

 

    Sub OnRowDblClickEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If _delDblClick IsNot Nothing Then

            _delDblClick.Invoke(sender, e)

        End If

    End Sub

 

    Sub OnMouseMoveEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If _delMouseMove IsNot Nothing Then

            _delMouseMove.Invoke(sender, e)

        End If

    End Sub

 

    Dim _Lastdir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

    Dim _LastHeaderClicked As GridViewColumnHeader = Nothing

    Sub HandleHeaderClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If e.OriginalSource.GetType Is GetType(GridViewColumnHeader) Then

            Dim gvh = CType(e.OriginalSource, GridViewColumnHeader)

            Dim dir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

            If Not gvh Is Nothing AndAlso Not gvh.Column Is Nothing Then

                If _LastHeaderClicked IsNot Nothing Then

                    _LastHeaderClicked.Column.HeaderTemplate = Nothing

                End If

                Dim hdr = gvh.Column.Header

                If gvh Is _LastHeaderClicked Then

                    If _Lastdir = ComponentModel.ListSortDirection.Ascending Then

                        dir = ComponentModel.ListSortDirection.Descending

                    End If

                End If

                gvh.Column.HeaderTemplate = CType(Windows.Markup.XamlReader.Load( _

                    <DataTemplate

                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                        >

                        <DockPanel>

                            <TextBlock HorizontalAlignment="Center"

                                Text="{Binding}"/>

                            <Path

                                Fill="DarkGray"

                                Data=<%= If(dir = ComponentModel.ListSortDirection.Ascending, _

                                         "M 5,10 L 15,10 L 10,5", _

                                         "M 5,5 L 10,10 L 15,5") %>/>

                        </DockPanel>

                    </DataTemplate>.CreateReader), DataTemplate)

 

                Sort(CStr(hdr), dir)

                _LastHeaderClicked = gvh

                _Lastdir = dir

            End If

        End If

    End Sub

 

    Sub Sort(ByVal sortby As String, ByVal dir As System.ComponentModel.ListSortDirection)

        _Browse.Items.SortDescriptions.Clear()

        Dim sd = New System.ComponentModel.SortDescription(sortby, dir)

        _Browse.Items.SortDescriptions.Add(sd)

        _Browse.Items.Refresh()

    End Sub

End Class

 

 

 

</code sample>

Posted by Calvin_Hsia | 1 Comments
Filed under: , , , ,

Its easy to create a graph of memory use in Excel

In this post Examine .Net Memory Leaks I showed how to find a .Net managed memory leak.

 

Now let’s create a graph of memory and resource use over time.

 

 

Start Visual Studio 2008, File->New->Visual Basic (or C#) Windows, WPF application. Dbl click the WPF form to get to the Xaml.cs or Xaml.vb file

Paste in the appropriate version from below.

Hit F5 to run the code for about 2 minutes. Then Excel starts (if you have Excel installed)

 

 

When Excel starts, type these keystrokes exactly (we can automate Excel with a macro or use Automation, but that’s another story)

                Right Arrow (to skip the iteration column)

                Shift-Ctrl-End (to select the entire table)

                Alt (to activate the menu shortcuts

                N (to choose Insert)

                N (to choose Line Graph)

                Enter (to choose the first kind of line graph

 

See how easy it is to create a picture?

 

Try experimenting with the garbage collection and how it behaves. Try commenting out the UnSubscribe call.

 

Open the log.csv file from within VS. As the file changes, VS will automatically detect and reload the text, so you can watch as it runs.

 

See also

Create your own Test Host using XAML to run your unit tests

Excel's new gradient Data Bar feature is cool: you can do it too!

Create an ActiveX control using ATL that you can use from Fox, Excel, VB6, VB.Net

 

<VB Code>

Imports System.IO

 

Class Window1

    Public Shared logFile = "log.csv"    ' Excel can read/create graph easily

 

    Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        logFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), logFile)

 

        Dim nLoops = 100

        Dim nItersPerLoop = 100

        Dim dummy(nItersPerLoop) As MyWatcher

        For i = 1 To nLoops

            For j = 1 To nItersPerLoop

                Dim oWatcher = New MyWatcher

                dummy(i) = oWatcher

                dummy(i).UnSubscribe()

            Next

            GarbageCollect(0)

        Next

 

        Process.Start(logFile) ' start Excel 2007

        End ' end the program

    End Sub

 

    Private Shared _nIter As Integer

    Sub GarbageCollect(ByVal nGarbageCollect As Integer)

        For i = 1 To nGarbageCollect

            GC.Collect()

            GC.WaitForPendingFinalizers()

            Dim start = DateTime.Now

            ' give time to other threads to finish (WPF managed objects on other threads)

            Do Until (DateTime.Now - start).Duration > TimeSpan.FromSeconds(1)

                Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Background, Function() Nothing)

            Loop

        Next

        If _nIter = 0 AndAlso File.Exists(logFile) Then

            File.Delete(logFile)

        End If

        Dim devenv = Process.GetCurrentProcess

        Dim sFmt = "{0,5}, [{1,22}], {2,10}, {3,5}, {4,5}, {5,5}"

        Using writer = If(File.Exists(logFile), File.AppendText(logFile), File.CreateText(logFile))

            _nIter += 1

            Dim sOutput As String

            If _nIter = 1 Then

                sOutput = String.Format(sFmt, "Iter", "When", "Priv Mb", "Hndle", "GDI", "User")

                writer.WriteLine(sOutput)

                Debug.WriteLine(sOutput)

            End If

 

            sOutput = String.Format(sFmt, _nIter, _

                    DateTime.Now, (devenv.PrivateMemorySize64 / 1000000.0).ToString("f6"), devenv.HandleCount, GetGuiResources(devenv.Handle, 0), GetGuiResources(devenv.Handle, 1))

            writer.WriteLine(sOutput)

            Debug.WriteLine(sOutput)

            writer.Close()

        End Using

 

    End Sub

    Declare Function GetGuiResources Lib "user32" (ByVal hHandle As IntPtr, ByVal uiFlags As Integer) As Integer

 

 

    Class MyWatcher

        Dim MyLargeMemoryEater(1000000) As String ' make the instance bigger to magnify issue: 4 bytes per array item on x86

        Dim fsw As IO.FileSystemWatcher

        Sub New()

            fsw = New IO.FileSystemWatcher

            fsw.Path = Path.GetDirectoryName(logFile)

            fsw.Filter = "*.*"

            AddHandler fsw.Created, AddressOf OnWatcherFileCreated

 

            fsw.EnableRaisingEvents = True

 

        End Sub

        Sub UnSubscribe()

            fsw.EnableRaisingEvents = False

            RemoveHandler fsw.Created, AddressOf OnWatcherFileCreated

        End Sub

        Sub OnWatcherFileCreated(ByVal sender As Object, ByVal args As System.IO.FileSystemEventArgs)

            Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + " " + args.FullPath)

        End Sub

        Protected Overrides Sub Finalize()  ' called when garbage collector collects on the GC Finalizer thread.

            MyBase.Finalize()

            '            Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + _nIter.ToString + " Thread= " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString)

        End Sub

 

    End Class

 

End Class

</VB Code>

 

<C# Code>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.Diagnostics;

using System.IO;

 

namespace WpfApplication1

{

    /// <summary>

    /// Interaction logic for Window1.xaml

    /// </summary>

    public partial class Window1 : Window

    {

        public static string logFile = "log.csv"; // Excel can read/create graph easily

        private static int _nIter;

 

        public Window1()

        {

            InitializeComponent();

        }

 

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            logFile = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), logFile);

            int nLoops = 100;

            int nItersPerLoop = 100;

            MyWatcher[] dummy = new MyWatcher[nItersPerLoop];

            for (int i = 0; i < nLoops; i++)

            {

                for (int j = 0; j < nItersPerLoop; j++)

                {

                    var oWatcher = new MyWatcher();

                    dummy[i] = oWatcher;

                    dummy[i].UnSubscribe();

                }

                GarbageCollect(01);

            }

 

            Process.Start(logFile);// start Excel 2007

            Process.GetCurrentProcess().CloseMainWindow();

 

        }

        private void GarbageCollect(int nGarbageCollect)

        {

            for (int i = 0; i < nGarbageCollect; i++)

            {

                GC.Collect();

                GC.WaitForPendingFinalizers();

                var start = DateTime.Now;

                // give time to other threads to finish (WPF managed objects on other threads)

                var dd = TimeSpan.FromSeconds(1);

                var yy = dd > TimeSpan.FromSeconds(2);

 

 

                while (((DateTime.Now - start)) < TimeSpan.FromSeconds(1))

                {

                    Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background,

                        new System.Windows.Threading.DispatcherOperationCallback(delegate

                            {

                                return null;

                            }

                            ),null);

 

                }

 

            }

            if (_nIter == 0 && System.IO.File.Exists(logFile))

            {

                System.IO.File.Delete(logFile);

            }

            var devenv = Process.GetCurrentProcess();

            var sFmt = "{0,5}, [{1,22}], {2,10}, {3,5}, {4,5}, {5,5}";

            using (TextWriter writer = File.Exists(logFile) ? File.AppendText(logFile) :  File.CreateText(logFile))

            {

                _nIter += 1;

                var sOutput = "";

                if (_nIter == 1)

                {

                    sOutput = String.Format(sFmt, "Iter", "When", "Priv Mb", "Hndle", "GDI", "User");

                    writer.WriteLine(sOutput);

                    Debug.WriteLine(sOutput);

                }

                    sOutput = String.Format(sFmt, _nIter,

                            DateTime.Now, (devenv.PrivateMemorySize64 / 1000000.0).ToString("f6"), devenv.HandleCount, GetGuiResources(devenv.Handle, 0), GetGuiResources(devenv.Handle, 1));

                    writer.WriteLine(sOutput);

                    Debug.WriteLine(sOutput);

                    writer.Close();

            }

        }

 

        [System.Runtime.InteropServices.DllImport("User32")]

        extern public static int GetGuiResources(IntPtr hProcess, int uiFlags);

        public class MyWatcher

        {

            string[] MyLargeMemoryEater = new string[1000000]; //' make the instance bigger to magnify issue: 4 bytes per array item on x86

            System.IO.FileSystemWatcher fsw;

            public MyWatcher()

            {

                fsw = new System.IO.FileSystemWatcher();

                fsw.Path = System.IO.Path.GetDirectoryName(logFile);

                fsw.Filter = "*.*";

 

                fsw.EnableRaisingEvents = true;

                fsw.Created += OnWatcherFileCreated;

 

            }

            public void UnSubscribe()

            {

                fsw.EnableRaisingEvents = false;

                fsw.Created -= OnWatcherFileCreated;

            }

            void OnWatcherFileCreated(Object sender, System.IO.FileSystemEventArgs args)

            {

                Debug.WriteLine(((new StackTrace()).GetFrames()).FirstOrDefault().GetMethod().Name + " " + args.FullPath);

            }

        }

    }

}

 

</C# Code>

 

 

 

 

 

Posted by Calvin_Hsia | 0 Comments
Filed under: , ,

Make your Task Switcher Window bigger

One of the best features of Windows is the ability to Cut and Paste data within an application or from one application to another.  

 

You can alt-tab or click on the task bar to switch between actively running applications, like Word and Visual Studio. When you hit Alt-Tab, a window comes up.

 

This Task switcher window, by default has 3 rows of 7 icons. If you have more than 21 top level windows open, that dialog gets confusing.

 

Without tabbed browsing a few years ago (the ability of Internet Explorer to open multiple Tabs to view multiple web pages in a single top level IE Window), I had tons of these windows.  (Try Window Key Tab to switch applications on any machine with Aero enabled!)

 

(see also Create your own Flip Task Bar with live thumbnails using Vista Desktop Window Manager DWM and The VB version of the Flip Task Bar for Vista Desktop Window Manager)

 

In fact, sometimes I run so many I get confused when I Alt-Tab and see the behavior of the default 21 (3 row by 7 column) icon display. So, I just modify this to be 5 rows by 9 columns.

 

Open RegEdit and change these keys:

 

HKEY_CURRENT_USER\Control Panel\Desktop\CoolSwitchRows

HKEY_CURRENT_USER\Control Panel\Desktop\CoolSwitchColumns

 

See also CoolSwitchRows:  http://technet.microsoft.com/en-us/library/cc978607.aspx

 

 

In the old days (the late 70's to early 80's, when a 5 Meg hard disk was cool!), you could only run one application on your computer at a time. To transfer data from one program to another, you would open one application, save the information temporarily to a file on (floppy) disk, then open the other application, import the temporary file.

 

Then along came Borland's Sidekick and AutoDesq DesqView.  I think Sidekick had a way of storing text data: it was a TSR: a Terminate and Stay Resident program. That means it was always available even if you were running another application. Once you start it, it would “terminate” but still be running. It would always be in memory, reducing the memory available to other programs.

 

I remember writing various TSRs. One was a simple program that could flash on the screen any message at any time interval and duration:

 

                                Flashmsg  /m:"You're getting hungry" /t:200 /d:1

 

Another would monitor key strokes. Every 100th keystroke it could double a keystroke, substitute another, record, or suppress one.  I didn't circulate these evil programs. (do you believe me? J)

 

A more useful one came about because a fellow ice hockey teammate and client heard I knew something about computers and wanted his business's Contact List to be instantly available at any time. So I wrote a TSR that would display a simple database of names and phone numbers around 1982. Over the years, I migrated the database maintenance to FoxBase and eventually FoxPro (1990?). So while the client was using WordPerfect, he could hit a key combination to bring up his Contact List instantly.

 

A TSR can monitor various interrupts, such as the keyboard,  by just replacing the interrupt vector with its own. The interrupt vector was just a pointer to the code to run if the keyboard is hit. My TSR would just forward most requests to the original ISR (Interrupt Service Routine). Back in those DOS days, it was easy to replace system memory. “Protected mode” was unheard of.

 

DesqView was another TSR, but it allowed you to run multiple applications "simultaneously". If I remember right, it "paged" out one app for another, writing to disk the 256K that one was using for the other. This was an immense boost in productivity.

 

In those days, I was using a really great text editor called PC-Write (one of the first "shareware" programs)  from Bob Wallace (Microsoft Employee #9). (It was around this time that dBase, FoxBase were born.)

 

 

When Windows 3.1 came out around the early 90's, you could use OLE (Object Linking and Embedding, which sort of got renamed to ActiveX later) and DDE (Dynamic Data Exchange) to have applications communicate with each other. The big demo was a Word Document or Fox database embedding an Excel spreadsheet or a photograph.

 

You can still embed a photo or spreadsheet in Foxpro using a General field (on XP, Vista and Windows Server08 R2(which is Win7 under the hood)):

 

CREATE CURSOR temp (name c(10), gen g)

INSERT INTO temp (name) VALUES ("Photo")

APPEND GENERAL gen FROM d:\kids.jpg

MODIFY GENERAL gen

INSERT INTO temp (name) VALUES ("Excel")

APPEND GENERAL gen FROM d:\2008.07.xlsx

BROWSE

 

You can double click the image or Excel spreadsheet to see or edit it within Fox. You can also use the Edit->Insert Special menu to insert other kinds of objects.

 

For fun, try this:  

      x=DDEINITIATE("visualfoxpro","")

or

      _cliptext="This text goes on the clipboard"

 

Or notice the intellisense when you try this from the Fox Command window

x=CREATEOBJECT("Excel.Application")

x.Visible=1

x.Workbooks.Add

x.Cells(1,1).Value="Hi"

 

 

Try starting Visual Studio and choose File->Open->File-> navigate to a photo or spreadsheet!

 

Create or open an existing VB project which has no build errors and then

       Dim x=CREATEOBJECT("Excel.Application")

       Stop

 

F5 to hit the breakpoint, Debug->Window->Immediate to open the immediate window.

 

Then resize VS so you can see Excel,  type

x.Visible=1

x.Workbooks.Add

x.Cells(1,1).Value="Hi"

 

 

 

Try something similar in C# (add a reference to Microsoft.VisualBasic.Dll)

 

            Object x = Microsoft.VisualBasic.Interaction.CreateObject("excel.application","");

            var z = "Put a bpt here";

 

 

Process Explorer and Process Monitor can help you understand a program

Process Explorer and Process Monitor are must-have free utilities that you can download from http://technet.microsoft.com/en-us/sysinternals/bb795533.aspx

 

For example you can use ProcExp to examine a particular behavior of Visual Studio.

 

Start Visual Studio (any version). I was using VS 2008. 

Start Process Explorer (as Administrator)

Click on the Devenv.exe process (the main VS process) to select it.

Choose View->Show Lower Pane and View->Lower Pane View->Handles

In VS, try creating a new VB or C# console application.

 

Now ProcExp shows a list of handles opened by this particular process. A handle can be a file, an Event, a process, a thread, a registry key, etc.

 

Did you ever get a permission denied error message because the file is “opened by another process” but you didn’t know which process?

 

If you click on the Name column, the list of handles will be sorted by name.

 

I chose File->Save As to save the whole shebang to a file. Here’s a portion, showing the handle type (File), the file name, and the handle value:

 

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\enterprisesec.config.cch 0x264

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\security.config.cch             0x260

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\Microsoft.VisualBasic.xml          0x14D0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\mscorlib.xml    0x8C0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.Data.xml            0x89C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.xml      0x13F4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.CSharp.targets  0x7F4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.VisualBasic.dll    0x88C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.VisualBasic.targets           0x7F8

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.WinFX.targets    0x7E8

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll              0x86C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll      0x13D4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Deployment.dll      0x924

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll                0x174C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.XML.dll      0x15B8

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CompactFramework.CSharp.targets     0x804

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CompactFramework.VisualBasic.targets                0x808

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CSharp.targets               0x7EC

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.VisualBasic.targets        0x7F0

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.WinFx.targets 0x80C

File         C:\Windows\Microsoft.NET\Framework\v3.5\SqlServer.targets               0x7FC

 

You can see this portion by scrolling the view to some framework assemblies.

 

Now play around with Visual Studio to see what files in this folder get opened and closed. The newly opened ones show in green for a couple seconds, while ones that close flash red, then disappear.

 

In particular, try creating a new VB Console application to see which assemblies get loaded. Close the solution, reopen it a few times. You’ll see these files get opened/closed:

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll      0x13D4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Deployment.dll      0x924

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll                0x174C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.XML.dll      0x15B8

 

Now type the letter “C” within Sub Main and you’ll see these XML files get opened:

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\Microsoft.VisualBasic.xml          0x14D0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\mscorlib.xml    0x8C0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.Data.xml            0x89C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.xml      0x13F4

They will remain opened until you close the solution.

 

Now try typing the letter “C” before the Sub Main, but after the Module Module1 line.

No files get opened! Why?

 

Why does where you type make a difference? What are these files used for?

 

Here’s an excerpt. Does it provide any clues?

 

    <member name="T:System.Uri">

      <summary>Provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI.</summary>

      <filterpriority>1</filterpriority>

    </member>

    <member name="M:System.Uri.#ctor(System.String)">

      <summary>Initializes a new instance of the <see cref="T:System.Uri" /> class with the specified URI.</summary>

      <param name="uriString">A URI. </param>

      <exception cref="T:System.ArgumentNullException">

        <paramref name="uriString" /> is null. </exception>

      <exception cref="T:System.UriFormatException">

        <paramref name="uriString" /> is empty.-or- The scheme specified in <paramref name="uriString" /> is not correctly formed. See <see cref="M:System.Uri.CheckSchemeName(System.String)" />.-or- <paramref name="uriString" /> contains too many slashes.-or- The password specified in <paramref name="uriString" /> is not valid.-or- The host name specified in <paramref name="uriString" /> is not valid.-or- The file name specified in <paramref name="uriString" /> is not valid. -or- The user name specified in <paramref name="uriString" /> is not valid.-or- The host or authority name specified in <paramref name="uriString" /> cannot be terminated by backslashes.-or- The port number specified in <paramref name="uriString" /> is not valid or cannot be parsed.-or- The length of <paramref name="uriString" /> exceeds 65534 characters.-or- The length of the scheme specified in <paramref name="uriString" /> exceeds 1023 characters.-or- There is an invalid character sequence in <paramref name="uriString" />.-or- The MS-DOS path specified in <paramref name="uriString" /> must start with c:\\.</exception>

    </member>

 

 

Now try typing this very slowly and watch the Proc Exp Handles list:

                Dim x = New Uri(

 

(In C#, try “Uri” )

 

You’ll get Intellisense and the parameter help for the constructor of the Uri. The strings for that help come from this XML file.

 

The “filterPriority” element indicates whether the item shows up on the Common or the All tab of the Intellisense list (VB only).

(BTW, did you know you can switch between the common and the All tabs Alt-Comma and Alt-Period?)

 

If you like, you can customize the XML of the framework assemblies. Edit the text and save it.

 

You can even author your own:

Try this: before Sub Main type 3 apostrophes. (for C#, try 3 slashes) This will appear:

    ''' <summary>

    '''

    ''' </summary>

    ''' <remarks></remarks>

 

Try hitting Enter then “<” after the </summary> tag.  Intellisense will prompt you for other XML Doc fields, such as Example, Remarks, See Also, Param.

 

You can fill out the “XmlDoc” information. On Project->Properties->Compile there’s a checkbox “Generate XML Documentation file” (see How to: Generate XML Documentation for a Project )

 

Now when your assemblies get referenced, you can provide the strings for intellisense, parameter help, info tips to show!

 

The “en” in the file path name indicates “English” version of the docs. Other languages will work too,  if you have them installed on your machine.

 

The letter “C” in the VB sample above triggers intellisense. If you’re not within a method, then only a few keywords are allowed that start with “C”, like Class, Const, or Custom. Thus the XML doc files aren’t necessary.

 

If you’re within the context of a method, you can potentially type the name of a shared method, for example: Console.WriteLine, so the XML files are loaded.

 

Try experimenting with other project types, such as C#, a Web app, or even start Visual FoxPro to see what files get opened under varying conditions. Try adding references to other assemblies.

 

We’ll explore more of ProcExp and ProcMon in future posts.

Posted by Calvin_Hsia | 0 Comments

You can use Visual Studio to debug itself!

How do you find out why your computer or a running program is so slow? Here’s one way.

 

Let’s attach the VS debugger to VS itself. The main executable for VS is devenv.exe.

 

Start Visual Studio 2008. This will be the “debugger”

 

Choose File->Open Project    C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe

 

(You can also choose Debug->Attach to process to debug another instance of devenv.exe, or any other EXE, like Foxpro.exe or Excel.exe)

 

 

Hit F5, and a dialog pops up:

“Debugging information for ‘devenv.exe’ cannot be found or does not match. Symbols not loaded. Do you want to continue debugging?”. 

Answer yes, and Visual Studio starts. This will be the “debuggee”

 

Do anything you like in the debuggee, such as create or load a project. Hit F12 to cause an asynchronous breakpoint (or go to the debugger and choose Debug->Break All).

 

That will freeze all the debuggee threads and put you in the debugger.

 

You can then examine the Threads window (Debug->Windows->Threads) and see what threads are running.  There are several. You can dbl-click various threads and look at the Call stack for it (Debug->Windows->Call stack).

 

You’ll probably see that most threads are just waiting for something to happen.

 

Choose the main thread. When VS is idling, the stack will look like this:

 

            ntdll.dll!7c90eb94()       

            [Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]

            ntdll.dll!7c90e9ab()       

            kernel32.dll!7c8094e2()

>          msvcr90.dll!_onexit_nolock(int (void)* func=0x0072006f)  Line 157 + 0x6 bytes    C

            00660072()       

 

Each stack entry shows the module and address that called the next stack entry. This isn’t very useful, so you need to load symbols. You can use the public Microsoft Symbol Server:

 

Tools->Options->Debug->Symbols

 

http://msdl.microsoft.com/download/symbols

 

Cache the symbols to a local dir, like C:\Symbols

 

Right click on the various modules (like ntdll.dll, kernel32.dll, msenv.dll etc.) in the call stack to load symbols. Now it’s a little more intelligible:

 

>          ntdll.dll!_KiFastSystemCallRet@0()       

            user32.dll!_NtUserKillTimer@8()  + 0xc bytes     

            msenv.dll!CMsoCMHandler::FPushMessageLoop()  + 0x36 bytes           

            msenv.dll!SCM::FPushMessageLoop()  + 0x4f bytes     

            msenv.dll!SCM_MsoCompMgr::FPushMessageLoop()  + 0x28 bytes      

            msenv.dll!CMsoComponent::PushMsgLoop()  + 0x28 bytes       

            msenv.dll!VStudioMainLogged()  + 0x19b bytes

            msenv.dll!_VStudioMain()  + 0x7d bytes

            devenv.exe!util_CallVsMain()  + 0xd8 bytes       

            devenv.exe!CDevEnvAppId::Run()  + 0x5cb bytes         

            devenv.exe!_WinMain@16()  + 0x60 bytes         

            devenv.exe!License::GetPID()  - 0x4cf9 bytes    

            kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes         

 

You can see the WinMain calls a MessageLoop.

 

 

Let’s make the foreground thread busy. Create a VB console application. Add an XML literal:

 

Module Module1

 

    Sub Main()

        Dim bigxml = <xml>

 

                  </xml>

    End Sub

 

End Module

 

Make the XML literal big: open the file C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.Linq.xml and copy everything except the <?xml version="1.0" encoding="utf-8"?> into the literal (between the <xml> and the </xml>), so you have about 2000 lines. 

 

Start Task Manager (Ctrl-Shift->Escape, or right click on the task bar and choose Task Manager). Observe the little tray icon in the System tray. It will indicate how busy your computer is.

 

For example, if you have 2 processors and 1 thread is very busy, it’ll show 50% busy.

 

Now, if you hover your mouse over the “bigxml”, you’ll trigger VB to create a Quick Info tooltip, but it takes quite a lot of calculation to figure out the tip.

 

Do your asynchronous breakpoint trick and you’ll see something like this:

 

>          msvb7.dll!BCSYM::IsNamedRoot()          

            msvb7.dll!BCSYM::AreTypesEqual()     

            msvb7.dll!EquivalentTypes()  + 0x17 bytes        

            msvb7.dll!ClassifyPredefinedCLRConversion()  + 0x22 bytes     

            msvb7.dll!Semantics::ClassifyPredefinedCLRConversion()  + 0x28 bytes

            msvb7.dll!Semantics::ClassifyPredefinedConversion()  + 0xbf bytes      

            msvb7.dll!Semantics::ResolveConversion()  + 0x234 bytes        

            msvb7.dll!Semantics::ClassifyUserDefinedConversion()  + 0x40685 bytes          

            msvb7.dll!Semantics::ClassifyConversion()  + 0x21e31 bytes     

            msvb7.dll!Semantics::CompareParameterTypeSpecificity()  + 0x57 bytes           

            msvb7.dll!Semantics::CompareParameterSpecificity()  + 0xa3 bytes       

            msvb7.dll!Semantics::InsertIfMethodAvailable()  + 0x461 bytes  

            msvb7.dll!Semantics::CollectOverloadCandidates()  + 0x1e6 bytes         

            msvb7.dll!Semantics::ResolveOverloading()  + 0xd9 bytes         

            msvb7.dll!Semantics::ResolveOverloadedCall()  + 0x5e bytes    

            msvb7.dll!Semantics::InterpretCallExpression()  + 0x2656f bytes

            msvb7.dll!Semantics::CreateConstructedInstance()  + 0xfa bytes

            msvb7.dll!Semantics::CreateConstructedInstance()  + 0x5b bytes           

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x241 bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlExpression()  - 0x13d bytes   

            msvb7.dll!Semantics::InterpretXmlExpression()  + 0xb0 bytes    

            msvb7.dll!Semantics::InterpretXmlExpression()  + 0xc3 bytes    

            msvb7.dll!Semantics::InterpretExpression()  - 0x1fc bytes          

            msvb7.dll!Semantics::InterpretExpressionWithTargetType()  + 0x43 bytes           

            msvb7.dll!Semantics::InterpretInitializer()  + 0x36 bytes  

            msvb7.dll!Semantics::InterpretInitializer()  + 0x1a bytes  

            msvb7.dll!Semantics::InterpretInitializer()  + 0x127 bytes 

            msvb7.dll!Semantics::InterpretVariableDeclarationStatement()  + 0x158c bytes    

            msvb7.dll!Semantics::InterpretStatement()  + 0x7b2f bytes         

            msvb7.dll!Semantics::InterpretStatementSequence()  + 0x2f bytes          

            msvb7.dll!Semantics::InterpretBlock()  + 0x24 bytes      

            msvb7.dll!Semantics::InterpretMethodBody()  + 0x1fa bytes      

            msvb7.dll!SourceFile::GetBoundMethodBodyTrees()  + 0x126 bytes       

            msvb7.dll!CBaseSymbolLocator::GetBoundMethodBody()  + 0xb6 bytes           

            msvb7.dll!CSymbolLocator::LocateSymbolInMethodImpl()  + 0x29 bytes

            msvb7.dll!CSymbolLocator::LocateSymbol()  + 0x81f bytes       

            msvb7.dll!CIntelliSense::GenQuickInfo()  + 0x52399 bytes          

            msvb7.dll!CIntelliSense::HandleQuickInfo()  + 0x29 bytes           

            msvb7.dll!CIntelliSense::ProcessCompletionInfo()  + 0x66cda bytes       

            msvb7.dll!CIntelliSense::GenIntelliSenseInfo()  + 0x402 bytes     

            msvb7.dll!SourceFileView::GenIntelliSenseInfo()  + 0x94 bytes   

            msvb7.dll!SourceFileView::GetDataTip()  + 0x743 bytes 

            msvb7.dll!CVBLangService::GetDataTip()  + 0x11d bytes           

            msenv.dll!CEditView::GetFilterDataTipText()  + 0x37 bytes         

            msenv.dll!CEditView::HandleHoverWaitTimer()  + 0x213 bytes    

            msenv.dll!CEditView::TimerTick()  + 0x7d bytes 

            msenv.dll!CEditView::WndProc()  + 0x1685b9 bytes      

            msenv.dll!CEditView::StaticWndProc()  + 0x39 bytes     

            user32.dll!_InternalCallWinProc@20()  + 0x28 bytes        

            user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes       

            user32.dll!_DispatchMessageWorker@8()  + 0xdc bytes

            user32.dll!_DispatchMessageW@4()  + 0xf bytes          

            msenv.dll!EnvironmentMsgLoop()  + 0xb6 bytes

            msenv.dll!CMsoCMHandler::FPushMessageLoop()  + 0x36 bytes           

            msenv.dll!SCM::FPushMessageLoop()  + 0x4f bytes     

            msenv.dll!SCM_MsoCompMgr::FPushMessageLoop()  + 0x28 bytes      

            msenv.dll!CMsoComponent::PushMsgLoop()  + 0x28 bytes       

            msenv.dll!VStudioMainLogged()  + 0x19b bytes

            msenv.dll!_VStudioMain()  + 0x7d bytes

            devenv.exe!util_CallVsMain()  + 0xd8 bytes       

            devenv.exe!CDevEnvAppId::Run()  + 0x5cb bytes         

            devenv.exe!_WinMain@16()  + 0x60 bytes         

            devenv.exe!License::GetPID()  - 0x4cf9 bytes    

            kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes         

 

Note how it’s easy to read the stack. The bottom of the stack (the first thing put on it) is _BaseProcessStart, which starts the process. You can see that a timer tick caused GetDataTip to call GenIntelliSenseInfo, which calls GenQuickInfo, -> LocateSymbol, etc.

 

Each stack entry indicates the name of the routine being executed. The “+ 0x23 bytes” means the # of bytes into the routine that the call occurred. A low number means near the beginning of that method.

 

Because XML is a tree, and is thus a recursive data structure, you see that XMLContent can contain an XMLExpression and vice versa. The depth of the recursion reflects the actual XML being processed.

 

 

BTW, the VB background compiler thread is:

 

0          >          4620     Worker Thread   ThreadSyncManager::ThreadProc           _KiFastSystemCallRet@0            Normal  0

 

And it’s stack at idle:

 

>          ntdll.dll!_KiFastSystemCallRet@0()       

            ntdll.dll!_ZwWaitForMultipleObjects@20()  + 0xc bytes   

            kernel32.dll!_WaitForMultipleObjectsEx@20()  - 0x48 bytes        

            user32.dll!_RealMsgWaitForMultipleObjectsEx@20()  + 0xd9 bytes        

            ole32.dll!CCliModalLoop::BlockFn()  + 0x76 bytes         

            ole32.dll!_CoWaitForMultipleHandles@20()  + 0xe6 bytes           

            msvb7.dll!ThreadSyncManager::ThreadProc()  + 0x98 bytes       

            kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes           

 

 

 

See also:

Dynamically attaching a debugger

Is a process hijacking your machine?

Posted by Calvin_Hsia | 2 Comments
Filed under: , ,

Stack overflow, expand your stack? Change your algorithm!

In the last post, Area fill algorithm: crayons and coloring book, I showed a program that emulates a kid drawing in a coloring book.

 

However, the algorithm wasn’t very efficient, and would explode even if you had a simple drawing: it was using the stack to store where to go.

 

The heart of the routine:

                void AreaFill(Point ptcell)

                {

                    if (ptcell.X >= 0 && ptcell.X < m_numCells.Width)

                    {

                        if (ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

                        {

        //                    System.Threading.Thread.Sleep(100);

                            if (DrawACell(ptcell, m_brushFill))

                            {

                                m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                                m_brushFill = new SolidBrush(m_oColor);

                                AreaFill(new Point(ptcell.X - 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X + 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X, ptcell.Y + 1));

                                AreaFill(new Point(ptcell.X, ptcell.Y - 1));

                            }

                        }

                    }

                }

 

 

It just checks boundaries, Draws the current point, then calls itself to draw the points North, South, East and West. Doing this for thousands of pixels eats up the current thread’s stack very fast.

 

One way to fix this is to expand the stack size.

 

You can examine the default stack size of an executable (EXE or DLL) by opening a Visual Studio command prompt (Start->Programs->VS2008->VSToos->VSCommand prompt), then type

link /dump /headers d:\dev\cs\Fill\bin\Debug\Fill.exe

 

Microsoft (R) COFF/PE Dumper Version 9.00.21022.08

Copyright (C) Microsoft Corporation.  All rights reserved.

 

 

Dump of file Fill.exe

 

PE signature found

 

File Type: EXECUTABLE IMAGE

 

FILE HEADER VALUES

             14C machine (x86)

               3 number of sections

        4A1ECBC2 time date stamp Thu May 28 10:37:06 2009

               0 file pointer to symbol table

               0 number of symbols

              E0 size of optional header

             10E characteristics

                   Executable

                   Line numbers stripped

                   Symbols stripped

                   32 bit word machine

 

OPTIONAL HEADER VALUES

             10B magic # (PE32)

            8.00 linker version

            2000 size of code

             800 size of initialized data

               0 size of uninitialized data

            3E7E entry point (00403E7E)

            2000 base of code

            4000 base of data

          400000 image base (00400000 to 00407FFF)

            2000 section alignment

             200 file alignment

            4.00 operating system version

            0.00 image version

            4.00 subsystem version

               0 Win32 version

            8000 size of image

             200 size of headers

               0 checksum

               2 subsystem (Windows GUI)

             540 DLL characteristics

                   Dynamic base

                   NX compatible

                   No structured exception handler

          100000 size of stack reserve

            1000 size of stack commit

          100000 size of heap reserve

            1000 size of heap commit

 

This is a little misleading, because the default stack size is 100000 Hex, which is 1,048,576, or about 1 Megabyte.

 

You can change the stack size using Editbin.exe

 

D:\dev\cs\Fill\bin\Debug>link /dump /headers Fill.exe | find "stack"

          100000 size of stack reserve

            1000 size of stack commit

 

D:\dev\cs\Fill\bin\Debug>editbin /stack:10000,1000 Fill.exe

Microsoft (R) COFF/PE Editor Version 9.00.21022.08

Copyright (C) Microsoft Corporation.  All rights reserved.

 

D:\dev\cs\Fill\bin\Debug>link /dump /headers Fill.exe | find "stack"

            2710 size of stack reserve

             3E8 size of stack commit

 

 

 

A better way to fix this is to use heap memory , rather than the stack:

 

 

        //* More efficient algorithm: don't use the stack to store state

        void AreaFill(Point ptcell)

        {

            Queue<Point> queueCells = new Queue<Point>();

            queueCells.Enqueue(ptcell);

            while (queueCells.Count > 0)

            {

                ptcell = queueCells.Dequeue();

                if (ptcell.X >= 0 && ptcell.X < m_numCells.Width)

                {

                    if (ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

                    {

                        //                    System.Threading.Thread.Sleep(100);

                        if (DrawACell(ptcell, m_brushFill))

                        {

                            m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                            m_brushFill = new SolidBrush(m_oColor);

                            queueCells.Enqueue(new Point(ptcell.X - 1, ptcell.Y));

                            queueCells.Enqueue(new Point(ptcell.X + 1, ptcell.Y));

                            queueCells.Enqueue(new Point(ptcell.X, ptcell.Y + 1));

                            queueCells.Enqueue(new Point(ptcell.X, ptcell.Y - 1));

                        }

                    }

                }

            }

        }

 

This solution is almost the same as the first: however, instead of recurring to go North, etc, the routine just puts the points to work on into a queue.

 

The VB solution is analogous:

    Sub AreaFill(ByVal ptcell As Point)

        Dim queueCells = New Queue(Of Point)

        queueCells.Enqueue(ptcell)

        While queueCells.Count > 0

            ptcell = queueCells.Dequeue()

            If (ptcell.X >= 0 And ptcell.X < m_numCells.Width) Then

 

                If (ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

                    '                    System.Threading.Thread.Sleep(100);

                    If (DrawACell(ptcell, m_brushFill)) Then

                        Me.m_oColor = Color.FromArgb((((Me.m_oColor.ToArgb And &HFFFFFF) + 140) And &HFFFFFF) Or &HFF000000)

 

                        '    m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                        m_brushFill = New SolidBrush(m_oColor)

                        queueCells.Enqueue(New Point(ptcell.X - 1, ptcell.Y))

                        queueCells.Enqueue(New Point(ptcell.X + 1, ptcell.Y))

                        queueCells.Enqueue(New Point(ptcell.X, ptcell.Y + 1))

                        queueCells.Enqueue(New Point(ptcell.X, ptcell.Y - 1))

                    End If

                End If

            End If

        End While

    End Sub

 

 

 

 

See also:

Tail Recursion

Cartoon animation program

Comment/Uncomment code to switch versions quickly without using macros

 

http://msdn.microsoft.com/en-us/library/8cxs58a6.aspx

http://bytes.com/groups/net-c/229335-stack-size

 

Posted by Calvin_Hsia | 1 Comments
Filed under: , ,

Area fill algorithm: crayons and coloring book

Kids know how to use crayons and a  coloring book. How do you write such a program?

 

In my last post (Which pixels do you turn on when you draw a line?) I showed how to draw a line. Now suppose you have some lines or shapes already drawn. How would you write code to fill in an area bounded by the drawn pixels?

 

IOW, imagine you’ve drawn a circle. You want to fill the circle with a color. Right click inside. What code should run? What if the figure were more complex, like a large block “W” or a curled up snake.

 

More formally: Given an array of pixels that represents the outline of a shape, and a point’s  x,y coordinate within that shape, how would you area fill that shape?

 

Perhaps you could write some code that will check all pixels to the East, North, West, South until it reached a pixel that was already painted. Then what?

 

What sort of algorithm and data structure would you use?

 

 

Below are C# and VB versions of a simple implementation that isn’t very efficient, but is quite simple.

 

The code is identical to the last post (the drawing parts) except for the areas delimited by “AREA”. This makes it easier for you to cut/paste the code.

 

Start Visual Studio 2008

Choose  File->New->Project->C# or VB->Windows Forms Application.

Choose View->Code

 

Paste in the VB or C# version of the code below, hit F5 to run it. Draw a shape, right click to fill.

 

How would you improve it? Why isn’t it efficient?

 

Try making the form bigger and paint more pixels. What happens?

 

Clue:  look at the source code for MineSweeper that I wrote to take advantage of the (then) new feature Collections. It’s part of the Task Pane for Visual Foxpro 9.0

 

See also:

Remove double spaces from pasted code samples in blog

 

 

<C# Sample>

#define AREA 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

    public partial class Form1 : System.Windows.Forms.Form

    {

        Size m_numCells = new Size(350, 200);// we'll use an array of Cells

        Boolean[,] m_cells; // the array of cells: whether they've been drawn or not

        Size m_cellSize = new Size(8, 8);  // cell height & width

        Size m_Offset = new Size(0, 0);

        bool m_MouseDown = false;

        Button btnErase;

        Point? m_PtOld;

        SolidBrush m_brushMouse = new SolidBrush(Color.Red);

        SolidBrush m_brushGenerated = new SolidBrush(Color.Black);

        delegate bool DrawCellDelegate(Point ptcell, Brush br);

        Graphics m_oGraphics;

        public Form1()

        {

            this.Load += new EventHandler(this.Loaded);

        }

        void Loaded(Object o, EventArgs e)

        {

            this.Width = 600;

            this.Height = 400;

            this.btnErase = new Button();

            this.btnErase.Text = "&Erase";

            this.btnErase.Click += new EventHandler(this.btnErase_Click);

            this.Controls.Add(this.btnErase);

            this.BackColor = Color.White;

            btnErase_Click(null, null);

        }

        void btnErase_Click(object sender, System.EventArgs e)

        {

            m_oGraphics = Graphics.FromHwnd(this.Handle);

            m_numCells.Width = this.Width / m_cellSize.Width;

            m_numCells.Height = this.Height / m_cellSize.Height;

            m_cells = new Boolean[m_numCells.Width, m_numCells.Height];

            m_oGraphics.FillRectangle(Brushes.White, new Rectangle(0, 0, this.Width, this.Height));

        }

        Point PointToCell(Point p1)

        {

            Point ptcell = new Point(

            (p1.X - m_Offset.Width) / m_cellSize.Width,

            (p1.Y - m_Offset.Height) / m_cellSize.Height);

            return ptcell;

        }

        protected override void OnMouseDown(MouseEventArgs e)

        {

            if (e.Button == MouseButtons.Left)

            {

                m_MouseDown = true;

                m_PtOld = new Point(e.X, e.Y);

                CheckMouseDown(e);

            }

#if AREA

            else

            {

                AreaFill(PointToCell(new Point(e.X, e.Y)));

            }

#endif

        }

        protected override void OnMouseMove(MouseEventArgs e)

        {

            if (m_MouseDown)

            {

                CheckMouseDown(e);

            }

        }

        protected override void OnMouseUp(MouseEventArgs e)

        {

            m_MouseDown = false;

        }

        void CheckMouseDown(MouseEventArgs e)

        {

            Point ptMouse = new Point(e.X, e.Y);

            Point ptcell = PointToCell(ptMouse);

            if (ptcell.X >= 0 && ptcell.X < m_numCells.Width &&

                ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

            {

                DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, new DrawCellDelegate(DrawACell));

                m_PtOld = ptMouse;

            }

        }

 

        bool DrawACell(Point ptcell, Brush br)

        {

            bool fDidDraw = false;

            if (!m_cells[ptcell.X, ptcell.Y]) // if not drawn already

            {

                m_cells[ptcell.X, ptcell.Y] = true;

                //*

                m_oGraphics.FillRectangle(br,

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                /*/

                 g.DrawRectangle(new Pen(Color.Blue,1),

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                

                  //*/

                fDidDraw = true;

            }

            return fDidDraw;

        }

        void DrawLineOfCells(Point p1, Point p2, DrawCellDelegate drawit)

        {

            // http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

            Brush br =  m_brushMouse;

            int x0 = p1.X;

            int y0 = p1.Y;

            int x1 = p2.X;

            int y1 = p2.Y;

            int x, cx, deltax, xstep,

                y, cy, deltay, ystep,

                 error;

            bool st;

 

            // find largest delta for pixel steps

            st = (Math.Abs(y1 - y0) > Math.Abs(x1 - x0));

 

            // if deltay > deltax then swap x,y

            if (st)

            {

                x0 ^= y0; y0 ^= x0; x0 ^= y0; // swap(x0, y0);

                x1 ^= y1; y1 ^= x1; x1 ^= y1; // swap(x1, y1);

            }

 

            deltax = Math.Abs(x1 - x0);

            deltay = Math.Abs(y1 - y0);

            error = (deltax / 2);

            y = y0;

 

            if (x0 > x1) { xstep = -1; }

            else { xstep = 1; }

 

            if (y0 > y1) { ystep = -1; }

            else { ystep = 1; }

 

            for (x = x0; (x != (x1 + xstep)); x += xstep)

            {

                cx = x; cy = y; // copy of x, copy of y

 

                // if x,y swapped above, swap them back now

                if (st)

                {

                    cx ^= cy; cy ^= cx; cx ^= cy;

                }

                if (drawit(new Point(cx, cy), br))

                {

                    br = m_brushGenerated;

                }

 

                error -= deltay; // converge toward end of line

                if (error < 0)

                { // not done yet

                    y += ystep;

                    error += deltax;

                }

            }

        }

 

#if AREA

 

        SolidBrush m_brushFill = new SolidBrush(Color.Blue);

        Color m_oColor = Color.Black;

        /*

        /*/

                void AreaFill(Point ptcell)

                {

                    if (ptcell.X >= 0 && ptcell.X < m_numCells.Width)

                    {

                        if (ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

                        {

        //                    System.Threading.Thread.Sleep(100);

                            if (DrawACell(ptcell, m_brushFill))

                            {

                                m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                                m_brushFill = new SolidBrush(m_oColor);

                                AreaFill(new Point(ptcell.X - 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X + 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X, ptcell.Y + 1));

                                AreaFill(new Point(ptcell.X, ptcell.Y - 1));

                            }

                        }

                    }

                }

        //*/

#endif

    }

}

 

</C# Sample>

 

<VB Sample>

#Const AREA = True

Public Class Form1

    Dim m_numCells = New Size(350, 300) ' we'll use an array of cells

    Dim m_cells(,) As Boolean   ' the array of cells: whether they've been drawn or not

    Dim m_cellSize = New Size(8, 8) ' cell size & width

    Dim m_Offset = New Size(0, 0)

    Dim m_MouseDown = False

    Dim WithEvents btnErase As Button

    Dim m_PtOld As Point?

    Dim m_brushGenerated = New SolidBrush(Color.Black)

    Dim m_brushMouse = New SolidBrush(Color.Red)

    Dim m_oGraphics As Graphics

    Delegate Function DrawCellDelegate(ByVal ptCell As Point, ByVal br As Brush) As Boolean

 

    Sub Form_Load() Handles Me.Load

        Me.Width = 600

        Me.Height = 400

        Me.btnErase = New Button()

        Me.btnErase.Text = "&Erase"

        Me.Controls.Add(Me.btnErase)

        Me.BackColor = Color.White

        btnErase_Click()

 

    End Sub

    Sub btnErase_Click() Handles btnErase.Click

        m_oGraphics = Graphics.FromHwnd(Me.Handle)

        m_numCells.Width = Me.Width / m_cellSize.Width

        m_numCells.Height = Me.Height / m_cellSize.Height

        ReDim m_cells(m_numCells.Width, m_numCells.Height)

        m_oGraphics.FillRectangle(Brushes.White, New Rectangle(0, 0, Me.Width, Me.Height))

 

    End Sub

    Function PointToCell(ByVal p1 As Point) As Point

        Dim ptcell = New Point( _

            (p1.X - m_Offset.Width) / m_cellSize.Width, _

            (p1.Y - m_Offset.Height) / m_cellSize.Height)

        Return ptcell

 

    End Function

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)

        If e.Button = Windows.Forms.MouseButtons.Left Then

            m_MouseDown = True

            m_PtOld = New Point(e.X, e.Y)

            CheckMouseDown(e)

#If AREA Then

        Else

            AreaFill(PointToCell(New Point(e.X, e.Y)))

#End If

        End If

    End Sub

    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)

        If m_MouseDown Then

            CheckMouseDown(e)

        End If

    End Sub

    Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)

        m_MouseDown = False

    End Sub

    Sub CheckMouseDown(ByVal e As MouseEventArgs)

        Dim ptMouse = New Point(e.X, e.Y)

        Dim ptcell = PointToCell(ptMouse)

        If (ptcell.X >= 0 And ptcell.X < m_numCells.Width And _

            ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

 

            DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, New DrawCellDelegate(AddressOf DrawACell))

            m_PtOld = ptMouse

        End If

    End Sub

    Function DrawACell(ByVal ptCell As Point, ByVal br As Brush) As Boolean

        Dim fDidDraw = False

        If Not m_cells(ptCell.X, ptCell.Y) Then

            m_cells(ptCell.X, ptCell.Y) = True

            m_oGraphics.FillRectangle(br, _

                    m_Offset.Width + ptCell.X * m_cellSize.Width, _

                    m_Offset.Height + ptCell.Y * m_cellSize.Height, _

                    m_cellSize.Width, _

                    m_cellSize.Height)

            fDidDraw = True

        End If

        Return fDidDraw

    End Function

    Sub DrawLineOfCells(ByVal p0 As Point, ByVal p1 As Point, ByVal drawit As DrawCellDelegate)

        Dim br = m_brushMouse

        Dim x0 = p0.X

        Dim y0 = p0.Y

        Dim x1 = p1.X

        Dim y1 = p1.Y

        Dim fSwapped = False

        Dim dx = Math.Abs(x1 - x0)

        Dim dy = Math.Abs(y1 - y0)

        If dy > dx Then

            fSwapped = True ' swap x0<=>y0, x1<->y1

            x0 = p0.Y

            y0 = p0.X

            x1 = p1.Y

            y1 = p1.X

            dx = Math.Abs(x1 - x0)

            dy = Math.Abs(y1 - y0)

        End If

        Dim err = CInt(dx / 2)

        Dim y = y0

        Dim xstep = 1

        If x0 > x1 Then

            xstep = -1

        End If

        Dim ystep = 1

        If y0 > y1 Then

            ystep = -1

        End If

        Dim x = x0

        While x <> x1 + xstep

            Dim cx = x, cy = y ' copy of x,y

            If fSwapped Then

                cx = y

                cy = x

            End If

            If drawit(New Point(cx, cy), br) Then ' if it wasn't already drawn

                br = m_brushGenerated

            End If

            err -= dy

            If err < 0 Then

                y += ystep

                err += dx

            End If

            x += xstep

        End While

    End Sub

#If AREA Then

    Dim m_brushFill = New SolidBrush(Color.Blue)

    Dim m_oColor = Color.Black

    Sub AreaFill(ByVal ptcell As Point)

        If (ptcell.X >= 0 And ptcell.X < m_numCells.Width) Then

 

            If (ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

                '                    System.Threading.Thread.Sleep(100);

                If (DrawACell(ptcell, m_brushFill)) Then

                    Me.m_oColor = Color.FromArgb((((Me.m_oColor.ToArgb And &HFFFFFF) + 140) And &HFFFFFF) Or &HFF000000)

 

                    '    m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                    m_brushFill = New SolidBrush(m_oColor)

                    AreaFill(New Point(ptcell.X - 1, ptcell.Y)) ' West

                    AreaFill(New Point(ptcell.X + 1, ptcell.Y)) ' East

                    AreaFill(New Point(ptcell.X, ptcell.Y + 1)) ' South

                    AreaFill(New Point(ptcell.X, ptcell.Y - 1)) ' North

                End If

            End If

        End If

    End Sub

#End If

 

End Class

 

</VB Sample>

 

Posted by Calvin_Hsia | 4 Comments
Filed under: , ,

Which pixels do you turn on when you draw a line?

When I wrote my cartoon animation program almost 30 years ago (see Cartoon animation program) I needed to know how to draw a line.

 

Of course, nowadays, we just call a library function that will draw a line given two points.

 

If you think about it, the problem is quite complex. Imagine a rectangular array of pixels. Which ones do you paint in order to “see” a straight line?

 

If the desired line is horizontal, or vertical, the problem is simple. However, choosing which pixels to draw for an oblique line is an interesting mathematical problem: see Bresenham's line algorithm - Wikipedia, the free encyclopedia

 

Even back then, I found Bresenham’s algorithm in a book and implemented it for my cartoon program.

 

Suppose you want to create a simple drawing program. When you move the mouse, you can handle the MouseMove event, and light up the pixel at the position of the mouse.

 

However, this will only create a dotted line, with fewer dots if the mouse moved quickly.

 

Let’s see if we can use Bresenham’s algorithm to light up the pixels between successive mouse events.

 

This sample uses larger “pixels”, so you can actually see them as squares. In the code I called them cells (mainly because I stole a lot of the code from my game of Life (see Cellular Automata: The Game of Life )

 

The actual mouse down causes a red pixel to be drawn, and the generated ones are black.

 

Below are C# and VB versions.

 

Start Visual Studio 2008

Choose  File->New->Project->C# or VB->Windows Forms Application.

Choose View->Code

 

Paste in the VB or C# version of the code below, hit F5 to run it.

 

Move the mouse slowly and you’ll see more red pixels. Quickly, and you’ll see more black generated pixels.

Note how the code needs to distinguish between 2 sets of x-y coordinates: actual pixels, and cell coordinates.

Try adjusting the Cell size.

 

Think about what a right click would do.

 

See also:

Comment/Uncomment code to switch versions quickly without using macros

Remove double spaces from pasted code samples in blog

 

 

<C#Sample>

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

    public partial class Form1 : System.Windows.Forms.Form

    {

        Size m_numCells = new Size(350, 200);// we'll use an array of Cells

        Boolean[,] m_cells; // the array of cells: whether they've been drawn or not

        Size m_cellSize = new Size(8, 8);  // cell height & width

        Size m_Offset = new Size(0, 0);

        bool m_MouseDown = false;

        Button btnErase;

        Point? m_PtOld;

        SolidBrush m_brushMouse = new SolidBrush(Color.Red);

        SolidBrush m_brushGenerated = new SolidBrush(Color.Black);

        delegate bool DrawCellDelegate(Point ptcell, Brush br);

        Graphics m_oGraphics;

        public Form1()

        {

            this.Load += new EventHandler(this.Loaded);

        }

        void Loaded(Object o, EventArgs e)

        {

            this.Width = 600;

            this.Height = 400;

            this.btnErase = new Button();

            this.btnErase.Text = "&Erase";

            this.btnErase.Click += new EventHandler(this.btnErase_Click);

            this.Controls.Add(this.btnErase);

            this.BackColor = Color.White;

            btnErase_Click(null, null);

        }

        void btnErase_Click(object sender, System.EventArgs e)

        {

            m_oGraphics = Graphics.FromHwnd(this.Handle);

            m_numCells.Width = this.Width / m_cellSize.Width;

            m_numCells.Height = this.Height / m_cellSize.Height;

            m_cells = new Boolean[m_numCells.Width, m_numCells.Height];

            m_oGraphics.FillRectangle(Brushes.White, new Rectangle(0, 0, this.Width, this.Height));

        }

        Point PointToCell(Point p1)

        {

            Point ptcell = new Point(

            (p1.X - m_Offset.Width) / m_cellSize.Width,

            (p1.Y - m_Offset.Height) / m_cellSize.Height);

            return ptcell;

        }

        protected override void OnMouseDown(MouseEventArgs e)

        {

            if (e.Button == MouseButtons.Left)

            {

                m_MouseDown = true;

                m_PtOld = new Point(e.X, e.Y);

                CheckMouseDown(e);

            }

#if AREA

            else

            {

                AreaFill(PointToCell(new Point(e.X, e.Y)));

            }

#endif

        }

        protected override void OnMouseMove(MouseEventArgs e)

        {

            if (m_MouseDown)

            {

                CheckMouseDown(e);

            }

        }

        protected override void OnMouseUp(MouseEventArgs e)

        {

            m_MouseDown = false;

        }

        void CheckMouseDown(MouseEventArgs e)

        {

            Point ptMouse = new Point(e.X, e.Y);

            Point ptcell = PointToCell(ptMouse);

            if (ptcell.X >= 0 && ptcell.X < m_numCells.Width &&

                ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

            {

                DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, new DrawCellDelegate(DrawACell));

                m_PtOld = ptMouse;

            }

        }

 

        bool DrawACell(Point ptcell, Brush br)

        {

            bool fDidDraw = false;

            if (!m_cells[ptcell.X, ptcell.Y]) // if not drawn already

            {

                m_cells[ptcell.X, ptcell.Y] = true;

                //*

                m_oGraphics.FillRectangle(br,

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                /*/

                 g.DrawRectangle(new Pen(Color.Blue,1),

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                

                  //*/

                fDidDraw = true;

            }

            return fDidDraw;

        }

        void DrawLineOfCells(Point p1, Point p2, DrawCellDelegate drawit)

        {

            // http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

            Brush br =  m_brushMouse;

            int x0 = p1.X;

            int y0 = p1.Y;

            int x1 = p2.X;

            int y1 = p2.Y;

            int x, cx, deltax, xstep,

                y, cy, deltay, ystep,

                 error;

            bool st;

 

            // find largest delta for pixel steps

            st = (Math.Abs(y1 - y0) > Math.Abs(x1 - x0));

 

            // if deltay > deltax then swap x,y

            if (st)

            {

                x0 ^= y0; y0 ^= x0; x0 ^= y0; // swap(x0, y0);

                x1 ^= y1; y1 ^= x1; x1 ^= y1; // swap(x1, y1);

            }

 

            deltax = Math.Abs(x1 - x0);

            deltay = Math.Abs(y1 - y0);

            error = (deltax / 2);

            y = y0;

 

            if (x0 > x1) { xstep = -1; }

            else { xstep = 1; }

 

            if (y0 > y1) { ystep = -1; }

            else { ystep = 1; }

 

            for (x = x0; (x != (x1 + xstep)); x += xstep)

            {

                cx = x; cy = y; // copy of x, copy of y

 

                // if x,y swapped above, swap them back now

                if (st)

                {

                    cx ^= cy; cy ^= cx; cx ^= cy;

                }

                if (drawit(new Point(cx, cy), br))

                {

                    br = m_brushGenerated;

                }

 

                error -= deltay; // converge toward end of line

                if (error < 0)

                { // not done yet

                    y += ystep;

                    error += deltax;

                }

            }

        }

    }

}

</C#Sample>

 

 

 

<VBSample>

Public Class Form1

    Dim m_numCells = New Size(350, 300) ' we'll use an array of cells

    Dim m_cells(,) As Boolean   ' the array of cells: whether they've been drawn or not

    Dim m_cellSize = New Size(8, 8) ' cell size & width

    Dim m_Offset = New Size(0, 0)

    Dim m_MouseDown = False

    Dim WithEvents btnErase As Button

    Dim m_PtOld As Point?

    Dim m_brushGenerated = New SolidBrush(Color.Black)

    Dim m_brushMouse = New SolidBrush(Color.Red)

    Dim m_oGraphics As Graphics

    Delegate Function DrawCellDelegate(ByVal ptCell As Point, ByVal br As Brush) As Boolean

 

    Sub Form_Load() Handles Me.Load

        Me.Width = 600

        Me.Height = 400

        Me.btnErase = New Button()

        Me.btnErase.Text = "&Erase"

        Me.Controls.Add(Me.btnErase)

        Me.BackColor = Color.White

        btnErase_Click()

 

    End Sub

    Sub btnErase_Click() Handles btnErase.Click

        m_oGraphics = Graphics.FromHwnd(Me.Handle)

        m_numCells.Width = Me.Width / m_cellSize.Width

        m_numCells.Height = Me.Height / m_cellSize.Height

        ReDim m_cells(m_numCells.Width, m_numCells.Height)

        m_oGraphics.FillRectangle(Brushes.White, New Rectangle(0, 0, Me.Width, Me.Height))

 

    End Sub

    Function PointToCell(ByVal p1 As Point) As Point

        Dim ptcell = New Point( _

            (p1.X - m_Offset.Width) / m_cellSize.Width, _

            (p1.Y - m_Offset.Height) / m_cellSize.Height)

        Return ptcell

 

    End Function

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)

        If e.Button = Windows.Forms.MouseButtons.Left Then

            m_MouseDown = True

            m_PtOld = New Point(e.X, e.Y)

            CheckMouseDown(e)

#If AREA Then

        Else

            AreaFill(PointToCell(New Point(e.X, e.Y)))

#End If

        End If

    End Sub

    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)

        If m_MouseDown Then

            CheckMouseDown(e)

        End If

    End Sub

    Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)

        m_MouseDown = False

    End Sub

    Sub CheckMouseDown(ByVal e As MouseEventArgs)

        Dim ptMouse = New Point(e.X, e.Y)

        Dim ptcell = PointToCell(ptMouse)

        If (ptcell.X >= 0 And ptcell.X < m_numCells.Width And _

            ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

 

            DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, New DrawCellDelegate(AddressOf DrawACell))

            m_PtOld = ptMouse

        End If

    End Sub

    Function DrawACell(ByVal ptCell As Point, ByVal br As Brush) As Boolean

        Dim fDidDraw = False

        If Not m_cells(ptCell.X, ptCell.Y) Then

            m_cells(ptCell.X, ptCell.Y) = True

            m_oGraphics.FillRectangle(br, _

                    m_Offset.Width + ptCell.X * m_cellSize.Width, _

                    m_Offset.Height + ptCell.Y * m_cellSize.Height, _

                    m_cellSize.Width, _

                    m_cellSize.Height)

            fDidDraw = True

        End If

        Return fDidDraw

    End Function

    Sub DrawLineOfCells(ByVal p0 As Point, ByVal p1 As Point, ByVal drawit As DrawCellDelegate)

        Dim br = m_brushMouse

        Dim x0 = p0.X

        Dim y0 = p0.Y

        Dim x1 = p1.X

        Dim y1 = p1.Y

        Dim fSwapped = False

        Dim dx = Math.Abs(x1 - x0)

        Dim dy = Math.Abs(y1 - y0)

        If dy > dx Then

            fSwapped = True ' swap x0<=>y0, x1<->y1

            x0 = p0.Y

            y0 = p0.X

            x1 = p1.Y

            y1 = p1.X

            dx = Math.Abs(x1 - x0)

            dy = Math.Abs(y1 - y0)

        End If

        Dim err = CInt(dx / 2)

        Dim y = y0

        Dim xstep = 1

        If x0 > x1 Then

            xstep = -1

        End If

        Dim ystep = 1

        If y0 > y1 Then

            ystep = -1

        End If

        Dim x = x0

        While x <> x1 + xstep

            Dim cx = x, cy = y ' copy of x,y

            If fSwapped Then

                cx = y

                cy = x

            End If

            If drawit(New Point(cx, cy), br) Then ' if it wasn't already drawn

                br = m_brushGenerated

            End If

            err -= dy

            If err < 0 Then

                y += ystep

                err += dx

            End If

            x += xstep

        End While

    End Sub

 

 

End Class

 

</VBSample>

Posted by Calvin_Hsia | 3 Comments
Filed under: , ,

Comment/Uncomment code to switch versions quickly without using macros

In a typical day, I write or debug programs in several languages: typically Foxpro, C#, VB, C++ and 32 bit assembly, with an occasional MSIL, IDL and 64 bit ASM thrown in.

 

Sometimes, I like to switch between one version of code and another. This is useful if I want to do side by side comparisons of behavior.

 

One way to do this is with preprocessor macros, like this:

 

#If  SomeValue

                <one version of code>

#else

                <another version>

#endif

 

However, that’s a fair amount of typing.

 

There’s a shortcut that works with C# and C++ style comments.

 

In these languages, a line that starts with “//” is a comment.

 

Also, a block comment (which can span multiple lines) starts with “/*” and ends with “*/”

 

 

 

//*

      int sub foo1() {

            int x = 2;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo1

            return x;

      }

 

/*/

      int sub foo2() {

            int x = 3;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo2

            return x;

      }

// */

 

With a single character change I can switch between foo1 and foo2: just delete the very first “/”.  That changes the single line comment into a block comment. The “*/” of the “/*/” now acts like the end of the comment block.

 

Using an editor that colors the code (like Visual Studio) shows the switch properly

 

 

 

/*

      int sub foo1() {

            int x = 2;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo1

            return x;

      }

 

/*/

      int sub foo2() {

            int x = 3;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo2

            return x;

      }

// */

 

This technique is useful when creating sample code for others to play with, such as in my next blog post.

 

 

 

Unorthodox chopsticks

Several years ago, my wife and I were walking through a local shopping mall. At the time, there was some sort of Asian festival. At a display booth there was a table upon which were two trays, side by side. One was empty, and the other had many beans. The sign challenged visitors to see how many beans could be moved to the empty tray with chopsticks in one minute.  A piece of paper indicated the top score so far: something like 10. I imagined how somebody could have spent 6 seconds per bean…

 

My wife, being quite adept with the tool, was able to get a respectable number of beans across, despite the difficulty of picking up a single slippery bean.

 

Then she challenged me, having used chopsticks all my life, to see if I could beat her score.

 

To her chagrin, I deftly wielded the sticks, one in each hand, horizontally in parallel to scoop up dozens of beans at a time, crushing the day’s high score. Of course this wasn’t the normal way most people use chopsticks, but then, it easily was the best solution to the problem at hand.

 

(I’m sure some of you are thinking that a better solution be to pick up the full tray and pour the beans onto the empty tray, but how hard would it be to lift the tray with chopsticks?)

 

 

 

At a recent social gathering, we were divided into groups to solve some word puzzles: given a fairly long word, how many words can be formed from the letters of the original