Partager via


LiveRun - a VS plugin to see the output of your program immediately

Say you're demonstrating a compiler at a conference. What's the best way to do it?

Should you just type in code in the code window? Doing this, you're relying on the audience's imagination -- that they form a mental picture of how the program will behave. You're also relying on their trust that your code really does what you say it does.

Or should you execute your code every minute or so, so the program's output window pops up and the audience can see that the code really works? Here it's risky because each time you switch it breaks the flow. And you're relying on the audience to remember what the code each time they look at the output.

I think this is one of those problems that can be solved by technology! I wrote small plugin for Visual Studio 2008. It looks at what the current text buffer contains, compiles it in the background, and displays the output in a topmost window. It does this every two seconds or so. You don't even need to save or recompile to see the output. It only makes sense for standalone console programs that don't take input. Here's a screenshot:

screenshot of LiveRun

 

 

The source code is small and straightforward, and available for download at the link above.

 

There were two "gotcha" moments. The first was to do with multi-threading. I wanted the source code to be compiled in a background thread so it wouldn't interfere with the Visual Studio UI. But to grab the text of the current buffer you have to be in the UI thread, and also to display the output you have to be in the UI thread. I used a System.Timers.Timer, which fires its events in the background thread, and called form.Invoke(...) for any tasks that needed the UI thread.

I also used a "non-AutoReset" timer. I wanted it to get the source code and compile+run+display it, then pause for two seconds, then get the source code and compile+run+display it, then pause for two seconds, and so on. In other words the timer interval has to be two seconds after the end of handling the previous timer event.

''' <summary>

''' OnTimer handles the non-autoreset timer signal. It runs in a background thread. It gets the source

''' code from the current buffer, and compiles it, and displays the output.

''' </summary>

''' <remarks></remarks>

Sub OnTimer() Handles t.Elapsed

    Try

        Dim oldsrc = src

        ' We're in a background thread. But the source can only be obtained from the UI thread...

        ' This delegate will get the source and store it in the "src" field

        f.Invoke(New Action(AddressOf GetSource))

        If src <> oldsrc Then

            Dim oldoutput = output

            ' We want to compile-and-run in the background thread

            output = CompileAndRun(src)

            If output <> "" OrElse oldoutput = "" Then

                ' Displaying the output on-screen must be done in the UI thread.

                ' This delegate gets the content of the "output" field and displays it

                f.Invoke(New Action(AddressOf ShowOutput))

            End If

        End If

    Finally

        t.Start()

    End Try

End Sub

 

The other "gotcha" moment had to do with how to execute the code and capture its output. VB has very nice helper functions surrounding this, in the "My" namespace. My main concern was to recover from exceptions gracefully without leaving any mess. (Note: the code for getting a temporary filename isn't quite correct: the mere fact that you got a temporary unused filename one statement ago does not mean that the filename will still be unused; nor does it mean that the filename with ".vb" appended to it will be unused. But doing it more correctly didn't seem worth the bother; in any case, the exception handling means we'll recover okay from problems.)

Function CompileAndRun(ByVal src As String) As String

    Dim fn_exe = ""

    Dim fn_src = ""

    Dim vbc As System.Diagnostics.Process = Nothing

    Dim exe As System.Diagnostics.Process = Nothing

    Try

        ' Prepare for compilation

        fn_src = My.Computer.FileSystem.GetTempFileName() & ".vb"

        My.Computer.FileSystem.WriteAllText(fn_src, src, False)

        fn_exe = My.Computer.FileSystem.GetTempFileName() & ".exe"

        Dim framework = Environment.ExpandEnvironmentVariables("%windir%\Microsoft.Net\Framework")

        Dim latest_framework = (From d In My.Computer.FileSystem.GetDirectories(framework) Where d Like "*\v*" Select d).Last

        ' Compile it

        vbc = System.Diagnostics.Process.Start(New ProcessStartInfo _

                            With {.CreateNoWindow = True, _

                                  .UseShellExecute = False, _

                                  .FileName = latest_framework & "\vbc.exe", _

                                  .Arguments = String.Format("/out:""{0}"" /target:exe ""{1}""", fn_exe, fn_src)})

        Dim vbc_done = vbc.WaitForExit(3000)

        If Not vbc_done Then Return ""

        If vbc.ExitCode <> 0 Then Return ""

        ' Execute it

        Dim pinfo = New ProcessStartInfo With {.CreateNoWindow = True, _

                                               .UseShellExecute = False, _

                                               .FileName = fn_exe, _

                                               .RedirectStandardOutput = True}

        exe = New System.Diagnostics.Process With {.StartInfo = pinfo}

        exe.Start()

        Dim output = exe.StandardOutput.ReadToEnd

        Dim exe_done = exe.WaitForExit(3000)

        If Not exe_done Then Return ""

        Return output

    Finally

        ' Close the VBC process as neatly as we can

        If vbc IsNot Nothing Then

            If Not vbc.HasExited Then

                Try : vbc.Kill() : Catch ex As Exception : End Try

                Try : vbc.WaitForExit() : Catch ex As Exception : End Try

            End If

            Try : vbc.Close() : Catch ex As Exception : End Try

            vbc = Nothing

        End If

        ' Close the EXE as neatly as we can

        If exe IsNot Nothing Then

            If Not exe.HasExited Then

                Try : exe.Kill() : Catch ex As Exception : End Try

                Try : exe.WaitForExit() : Catch ex As Exception : End Try

            End If

            Try : exe.Close() : Catch ex As Exception : End Try

            exe = Nothing

        End If

        ' Delete leftover files

        Try : My.Computer.FileSystem.DeleteFile(fn_exe) : Catch ex As Exception : End Try

        Try : My.Computer.FileSystem.DeleteFile(fn_src) : Catch ex As Exception : End Try

    End Try

End Function

As always, I love to hear suggestions and bugfixes and code improvements and comments!

Comments

  • Anonymous
    October 22, 2008
    Nice idea, but it would be even cooler if you used a MSUnit project type and displayed the test results (and Console if you had to).  It would encourage folks to write unit tests. I prefer using MSUnit (or any xUnit framework) over console projects to demo code.