Code zu Anwendungen in .NET

Einbetten von Win32-Ressourcendateien in Visual-Basic-Projekten

Während Visual Studio 2005 für C#-Projekte das Einbetten von Win32-Ressourcendateien über die Projekteigenschaften unterstützt (siehe Embedding multiple icons into .NET Executables), ist dies für Visual-Basic-Projekte nicht vorgesehen. Win32-Ressourcendateien können verwendet werden, um beispielsweise mehrere Symbole in das Programm einzubetten. Das im nachstehenden Listing wiedergegebene Makro SelectWin32Resource schafft Abhilfe, indem es dem Benutzer ermöglicht, dem gerade geöffneten Projekt eine Win32-Ressourcendatei zuzuordnen. Win32-Ressourcendateien können mit Visual Studio erstellt und bearbeitet und Projekten hinzugefügt werden. Sie tragen die Dateinamenserweiterung .res. Beim Kompilieren mit dem Compiler vbc kann die Ressourcendatei im Parameter /win32resource (-win32resource) angegeben werden.

Option Strict On
Option Explicit On

Imports System
Imports EnvDTE
Imports System.Windows.Forms

Public Module Macros
    Friend Class DTEMainWindow
        Implements IWin32Window

        Public ReadOnly Property Handle() As IntPtr _
            Implements IWin32Window.Handle

            Get
                Return New IntPtr(DTE.MainWindow.HWnd)
            End Get
        End Property
    End Class

    Public Sub SelectWin32Resource()
        If DTE.ActiveDocument Is Nothing Then
            MsgBox( _
                "In order to assign a Win32 resource file to a " & _
                "project, at least one file belonging to the project " & _
                "must be opened.", _
                MsgBoxStyle.Exclamation _
            )
            Return
        End If
        Dim CurrentProject As Project = _
            DTE.ActiveDocument.ProjectItem.ContainingProject
        Dim Win32ResourceProperty As [Property] = _
            CurrentProject.Properties.Item("Win32ResourceFile")
        Dim CurrentValue As String = _
            DirectCast(Win32ResourceProperty.Value, String)
        Dim Prompt As String
        If Len(CurrentValue) = 0 Then
            Prompt = _
                "Currently there is no Win32 resource file associated " & _
                "with the project.  Do you want to assign one to the " & _
                "project?"
        Else
            Prompt = _
                "Currently the Win32 resource file " & _
                ControlChars.Quote & CurrentValue & ControlChars.Quote & _
                " is associated with the project.  Do you want to " & _
                "assign another one to the project?"
        End If
        If MsgBox(Prompt, MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
            Using FileBrowser As New OpenFileDialog()
                FileBrowser.Filter = "Win32 resource files (*.res)|*.res"
                Dim VSMainWindow As New DTEMainWindow()
                If _
                    FileBrowser.ShowDialog(VSMainWindow) = DialogResult.OK _
                Then
                    Win32ResourceProperty.Value = FileBrowser.FileName
                End If
            End Using
        End If
    End Sub
End Module
Das Makro SelectWin32Resource zum Zuweisen einer Win32-Ressourcendatei zu einem Projekt.

Erkennen laufender Instanzen einer Anwendung

Einige Anwendungen dürfen nur ein Mal zur gleichen Zeit ausgeführt werden. In Classic Visual Basic konnte die Eigenschaft App.PrevInstance verwendet werden, um zu überprüfen, ob die Anwendung bereits ausgeführt wird. Im .NET Framework findet sich jedoch keinen adäquater Ersatz für diese Eigenschaft. Die folgende Funktionsprozedur PrevInstance ersetzt die aus Classic Visual Basic bekannte Möglichkeit. Damit das Beispiel funktioniert, muß der Namensraum System.Diagnostics importiert werden:

''' <summary>
'''   Ermittelt, ob bereits eine Instanz des Programms geöffnet ist und gibt
'''   <c>Process</c>-Objekt zurück, das die erste gefundene andere Instanz
'''   der Anwendung repräsentiert.
''' </summary>
''' <returns>
'''   Gibt ein <c>Process</c>-Objekt zurück, das die erste gefundene andere
'''   Instanz der Anwendung repräsentiert.
''' </returns>
Public Shared Function PrevInstance() As Process
    Dim c As Process = Process.GetCurrentProcess()

    ' Durchlaufen aller Prozesse mit gleichem Namen.
    For Each p As Process In Process.GetProcessesByName(c.ProcessName)

          ' Aktuellen Prozeß nicht beachten.
          If p.Id <> c.Id Then

             ' Es kann mehrere Prozesse gleichen Namens geben, die von
             ' unterschiedlichen Programmen stammen.
             If p.MainModule.FileName = c.MainModule.FileName Then

                 ' Prozeß der ersten gefundenen anderen Instanz
                 ' zurückgeben.
                 Return p
             End If
         End If
     Next p

     ' Keine andere Instanz gefunden.
     Return Nothing
End Function
Existiert bereits eine andere Instanz des Programms?

Nachteilig an der oben angegebenen Lösung ist, daß Prozeßnamen auf einem System nicht notwendigerweise eindeutig sein müssen. Um dieses Problem zu umgehen, wird zusätzlich zum Prozeßnamen auch der Ort verglichen, an dem die Anwendung abgelegt ist. Wird ein und die selbe Anwendung jedoch mehrfach aus unterschiedlichen Verzeichnissen gestartet, können die Instanzen einander nicht erkennen.

Weiters gilt es zu entscheiden, ob mehrere Benutzer gleichzeitig die Anwendung ausführen dürfen. Zu diesem Zweck bietet sich die Verwendung eines Mutex, das ist ein Mittel zu Synchronisation von Prozessen, an. Im zweiten Parameter des Konstruktors der Klasse Mutex wird der Name des Mutex übergeben. Dieser Name sollte eindeutig sein, weshalb sich die Verwendung eines GUIDs als gute Wahl erweist.

Der Präfix Local\ gibt an, daß der Mutex nur für den aktuellen Benutzer gilt. Damit kann gesteuert werden, ob mehrere Benutzer in der Lage sein sollen, jeweils eine Instanz der Anwendung auszuführen, oder auf dem gesamten System nur eine Instanz existieren darf. Damit folgender Code funktioniert, ist es erforderlich, den Namensraum System.Threading zu importieren:

Public Module Program
    Private m_Mutex As Mutex

    Public Sub Main()
        Dim Owned As Boolean
        m_Mutex = New Mutex(True, "Local\11C92606-65D9-4df2-9AEA-B6A4DA91BCE2", Owned)
        If Owned Then
            Application.Run(New MainForm())
            m_Mutex.ReleaseMutex()
        Else
            MsgBox("Anwendung wird bereits ausgeführt!")
        End If
    End Sub
End Module
Verhindern doppelter Anwendungsinstanzen mit einem Mutex.

Unterscheiden zwischen Kunde und Entwicklungsumgebung

Während des Entwickelns von Anwendungen häuft sich meist eine Menge an Code an, der nur bei verschiedenen Testdurchläufen kompiliert und ausgeführt werden soll. Wie zwischen Ausführung im Debugmodus und dem Releasemodus unterschieden werden kann, wird im folgenden Listing gezeigt. Damit das Beispiel funktioniert, muß die Konstante DEBUG definiert sein. Dies erreicht man in der Entwicklungsumgebung von Visual Studio .NET durch Aktivieren der Option DEBUG-Konstante definieren unter KonfigurationseigenschaftenErstellen:

#If DEBUG Then
    Console.WriteLine("Debugmodus.")
#Else
    Console.WriteLine("Releasemodus.")
#End If
Unterscheidung zwischen Debug- und Releasemodus.

Alternativ kann man überprüfen, ob ein Debugger vorhanden ist. Wenn dies der Fall ist, dann wird es sich um den Entwicklungsrechner handeln, andernfalls um die Umgebung des Kunden. Auskunft über das Vorhandensein eines Debuggers gibt System.Diagnostics.Debugger.IsAttached. Beim Entwickeln von Steuerelementen kommt es vor, daß sich diese in der Entwurfsansicht der Entwicklungsumgebung anders verhalten sollen, als im fertigen Produkt, wenn sie auf einem Formular plaziert werden. Diese Unterscheidung kann man anhand von Me.DesignMode durchführen.

Ermitteln des Pfades der Anwendungsdatei

Unter Classic Visual Basic war das Ermitteln des Anwendungspfades, also des Verzeichnisses, in dem sich die ausgeführte Datei befindet, sehr einfach mittels App.Path möglich. In .NET ist dies nicht so einfach. Vielfach wird auf Application.StartupPath aus dem Namensraum System.Windows.Forms zurückgegriffen. Diese Methode kann aber beim Start von Anwendungen über Dateiverknüpfungen unter älteren Windows-Versionen angeblich falsche Werte zurückgeben. Außerdem zahlt es sich bei Konsolenanwendungen bzw. Klassenbibliotheken nicht aus, die doch recht große Datei System.Windows.Forms.dll zu laden.

Eine einfache Lösung, die auch in Klassenbibliotheken den gewünschten Pfad zurückgibt, findet sich über Reflection. Dabei wird der Pfad der ausführenden Assembly ermittelt und zurückgegeben. Damit folgender Code funktioniert, müssen die Namensräume System.IO und System.Reflection importiert werden. Um den Pfad der ausgeführten Assembly zu ermitteln, reicht es aus, GetEntryAssembly durch GetExecutingAssembly zu ersetzen:

Private ReadOnly Property ApplicationPath() As String
    Get
        Return _
            Path.GetDirectoryName([Assembly].GetEntryAssembly().Location)
    End Get
End Property
Ermitteln des Anwendungspfades.