vb-Zentrum
Systemfunktionen
http://www.vb-zentrum.de/tip_system.html

© 2023 vb-Zentrum

0001 NT-Betriebssystem?

 

Damit Ihre Applikation auf den verschiedenen Windows-Plattformen lauffähig ist, ist es manchmal notwendig zwischen NT (NT/2000/XP/Vista/usw.) und Nicht-NT (Win9x/ME) Systemen zu unterscheiden, da zum Beispiel einige API-Aufrufe nur auf NT-Plattformen zur Verfügung stehen. Wenn auch die benutzte "GetVersionEx"-Funktion die genaue Betriebssystem-Version ermitteln kann, wollen wir uns in diesem Fall auf die Prüfung von NT-Basis beschränken. Diese Abfrage ist z.B. dann wichtig, wenn wir eine unicodefähige Anwendung schreiben wollen, da Unicode grundsätzlich erst ab Windows NT/2000 zur Verfügung steht:

' Deklaration:
Private Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" _
                         (lpVersionInformation As OSVERSIONINFO) As Long

' Typendefinition:
Private Type OSVERSIONINFO
  dwOSVersionInfoSize As Long
  dwMajorVersion As Long
  dwMinorVersion As Long
  dwBuildNumber As Long
  dwPlatformId As Long
  szCSDVersion As String * 128
End Type

Private Const VER_PLATFORM_WIN32_NT = 2


' Funktion:
' Prüft auf NT-Betriebssysteme: True ab Windows NT/2000 und neuer, sonst False
Public Function isSystemNT() As Boolean
  Dim info As OSVERSIONINFO

  info.dwOSVersionInfoSize = Len(info)
  GetVersionEx info
  isSystemNT = (info.dwPlatformId = VER_PLATFORM_WIN32_NT)
End Function

Autor: ralf schlegel
Stand: 10/2004
Update: 12/2012

Nach oben

0002 vbzMsgBox

 

Jeder nutzt sie - die VB-integrierte MsgBox Funktion! Sie erspart einem das Erstellen einer Form und ist einfach ideal für Benutzer-Informationen, oder interaktive Abfragen. Doch das Einfache hat auch wieder seinen Preis:
Viele Features dieser Box hat Microsoft den VB-Usern leider nicht zur Verfügung gestellt.

Mit dem hier zum Download bereit gestellten Beispiel können Sie die MsgBox endlich im vollen Umfang nutzen:

  • keine Blockade von Timern bei Aufruf der Box
  • Unterstützung der optionalen Hilfe Schaltfläche mit Kontext Nummer
  • optional frei definierbare Button-Beschriftung
  • optional automatisches Schließen nach Zeit
  • durchgängig verwendbar von Windows 98 bis Windows 7
  • 100% kompatibel zur VB-internen MsgBox!

Die Funktionen sind in einem einzigen Modul gekapselt und lassen sich so problemlos in Ihre Anwendungen implementieren, da Sie dort nur den Aufruf Ihrer bisherigen MsgBox durch vbzMsgBox ersetzen, bzw. erweitern müssen.
Der Quellcode ist ausführlichst dokumentiert und sollte somit auch wenig erfahrenen VB-lern keine Probleme bereiten...

   0002_vbzMsgBox.zip

Autor: ralf schlegel
Stand: 08/2009

Nach oben

0003 UAC in Vista und Winows 7

 

Das leidige Thema des UAC (User Account Control, oder zu Deutsch: Benutzerkontensteuerung) beschäftigt uns VB-Programmierer immer wieder; haben wir doch plötzlich nicht mehr das Recht Dateien in unserem Applikationsverzeichnis zu ändern, wenn die Installation unter "..\Program Files\.." erfolgte - und das Registrieren von DLLs oder OCXs zur Laufzeit ist auch nicht mehr so leicht wie früher...
 
Ursache:

Versionen von Windows, die älter als Windows NT sind beziehungsweise nicht davon abstammen (also Windows 95, 98 und ME), waren Einzelbenutzersysteme, in denen der Benutzer die volle Systemkontrolle besaß.
Windowssysteme der NT-Reihe sind dagegen Mehrbenutzersysteme, in denen Benutzerrollen und Benutzerrechte verwaltet werden.
Diese Verwaltung hat Microsoft ab Windows Vista mit dem UAC nochmals verschärft, sodaß wir selbst als angemeldeter Administrator nicht mehr vollen Zugriff auf alle Verzeichnisse und Systemfunktionen haben.
 
Da die nachfolgende API-Funktion, "IsUserAnAdmin", nur auf NT-Systemen bekannt ist,
sollte sie mit dem Tipp "0001 NT-Betriebssystem?" verknüpft werden. Sie behebt zwar nicht das Problem, sagt uns aber mit welchen Rechten wir gerade unterwegs sind! Außerdem ist sie UAC kompatibel - d.h. gibt 'False' zurück, wenn unter Vista / Win7 ein Mitglied der Administratorengruppe angemeldet ist, dieses aber vom UAC auf Benutzerrechte zurückgestuft wurde!

Private Declare Function IsUserAnAdmin Lib "shell32.dll" () As Long

' Hat der Benutzer Administrator-Rechte?
Public Function isAdmin() As Boolean
  If isSystemNT Then  ' Siehe Tipp 0001
    isAdmin = CBool(IsUserAnAdmin())
  Else
    ' auf Einzelbenutzersystemen
    ' hat der User immer Adminrechte
    isAdmin = True
  End If
End Function

Lösungsansatz:
Vermeiden Sie das Anlagen und Schreiben von Dateien in Ihrem Anwendungsverzeichnis!
So wie Sie im Allgemeinen zu Beginn des Programms den Anwendungspfad ermitteln (glbAppPath = App.Path & "\") können Sie auch den ab Windows 2000 bereitgestellten Programmdatenpfad zur Laufzeit dynamisch auslesen und für Ihre Zwecke verwenden.
Benutzen Sie dazu die Funktion getSpecialFolder mit dem Parameter CSIDL_COMMON_APPDATA (&H23) in der Form:

glbDataPath = getSpecialFolder(CSIDL_COMMON_APPDATA)

  • ist die Variable glbDataPath leer, so handelt es sich um ein nicht NT-System und die Daten können nach wie vor problemlos im Anwendungsverzeichnis abgelegt werden - wir setzen also glbDataPath = glbAppPath.
  • anderenfalls erweitern Sie den zurückgegebenen Pfad um "\" & App.Title & "\" und legen ihn an, falls er nicht existiert. Hier darf auch bei aktivem UAC frei agiert werden!

Laden und speichern Sie zusätzliche Programmdaten, wie Konfigurationen und INI-Files im Verzeichnis glbDataPath. Egal unter welchem System Ihre Anwendung läuft: die Daten können nun problemlos geschrieben und verändert werden...

Autor: ralf schlegel
Stand: 12/2009

Nach oben

0004 Hat die eigene Anwendung den Fokus? - WM_ACTIVATEAPP

 

 

Mit den Prozeduren Form_GotFocus und Form_LostFocus können wir ermitteln, ob die aktuelle Form gerade aktiv ist (den Fokus hat), oder nicht. Manchmal wäre es jedoch besser zu wissen, ob die gesamte Anwendung den Fokus hat, oder nicht. So lassen sich z.B. aufwendigere Hintergrundarbeiten erledigen, wenn der Benutzer gerade mit einer anderen Anwendung arbeitet.
Hierfür stellt uns das Windows API die Konstante WM_ACTIVATEAPP zur Verfügung, die wir über das Subclassing unserer Hauptform als Message erhalten. Wir benötigen also den folgenden Code in einem neuen Modul (z.B.: appFocus.bas):

Option Explicit

Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
        (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, _
        ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
        (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Const GWL_WNDPROC = -4
Private Const WM_ACTIVATEAPP = &H1C

' Locale Variablen:
Private mainOldProc As Long       ' Pointer auf original Fensterprozedur

Global glbAppActive As Boolean    ' globale Überwachungsvariable

' Subclassing initialisieren:
Public Sub MainHook(ByVal hWnd As Long)
  glbAppActive = True ' Wenn wir starten, sind wir die aktive Anwendung
  mainOldProc = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf winMain)
End Sub

' Subclassing beenden:
Public Sub MainUnHook(ByVal hWnd As Long)
  SetWindowLong hWnd, GWL_WNDPROC, mainOldProc
End Sub

' Unsere Erweiterung der Fensterprozedur:
Private Function winMain(ByVal hWnd&, ByVal uMsg&, ByVal wParam&, ByVal lParam&) As Long
  Select Case uMsg
    Case WM_ACTIVATEAPP
      If wParam Then  ' Anwendung wurde aktiviert
        glbAppActive = True
      Else            ' Anwendung wurde deaktiviert
        glbAppActive = False
      End If
  End Select
  winMain = CallWindowProc(mainOldProc, hWnd, uMsg, wParam, lParam)
End Function

In der Hauptform unserer Anwendung rufen wir dann in der Load Anweisung die Funktion MainHook auf und übergeben ihr das Handle der Form, also "Me.hWnd". In der QueryUnload Anweisung muss nun das Pendant MainUnHook (Me.hWnd) aufgerufen werden, sonst stürzt die Anwendung ab! Das war schon alles. - Die globale Variable glbAppActive kann dann z.B. über einen Timer abgefragt werden.
Bei der Initialisierung des Hooking muß die Variable glbAppActive zunächst 'manuell' auf True gesetzt werden: da wir beim Programmstart schon die aktive Anwendung sind, wird nämlich kein weiteres WM_ACTIVATEAPP Ereignis ausgelöst.

Verfügt Ihre Anwendung bereits über ein Hooking, so ist es noch einfacher:
legen Sie in Ihrem Modul die globale Variable glbAppActive und die Konstante WM_ACTIVATEAPP an und erweitern Sie in Ihrer Hooking Funktion (hier winMain) die Abfrage von uMsg wie oben im Quellcode zu sehen.

   0004_appfocus.zip

Autor: ralf schlegel
Stand: 06/2011

Nach oben

0005 32- oder 64Bit Betriebssystem?

 

VB6 Anwendungen leben weiter! Warum auch nicht: alle Hürden der neuen Betriebssysteme lassen sich bis heute meistern. Beispiele für Unicode oder den Umgang mit dem UAC gibt es genug im Netz. Die neue Herausforderung heißt 64Bit, die nun seit geraumer Zeit zum Auslieferungsstandard von Microsoft geworden ist. Je nach Art und Umfang der Anwendung ist es erforderlich zu wissen, ob wir auf einem 32 oder 64 Bit Betriebssystem unterwegs sind. Auch hier ist die Lösung per API relativ einfach zu realisieren.
Fügen Sie folgenden Code in ein Basic-Modul ein:

' Deklaration:
Public Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String)
As Long
Public Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, _
                        ByVal lpProcName As String)
As Long
Public Declare Function IsWow64Process Lib "kernel32" (ByVal hProcess As Long, _
                       
ByRef Wow64Process As Long) As Long
Public Declare Function GetCurrentProcess Lib "kernel32" () As Long

' Globale Variable:
Public
is64BitWin As Boolean

In der Programminitialisierungs-Routine Ihrer Anwendung fügen Sie nun folgende Zeilen ein:

If GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process") Then
  IsWow64Process GetCurrentProcess, ret
  is64BitWin = (ret <> 0)
End If

Die Variable "is64BitWin" können Sie nun an beliebiger Stelle im Programm abfragen und entsprechend reagieren.

Zum Verständnis:
die Funktion "GetProcAddress" ermittelt die Aufrufadresse einer als String übergebenen, öffentlichen Funktion (hier "IsWow64Process") aus einer geladenen DLL (hier "kernel32.dll"). Existiert diese Funktion nicht, so wird "0" zurückgegeben - der Aufruf innerhalb der If-Anweisung wird nicht ausgeführt und is64BitWin bleibt False. So bleibt das Ganze abwärtskompatibel zu älteren Windows Versionen.
Ist die Funktion "IsWow64Process" bekannt, so kann sie aufgerufen werden. Der Rückgabewert "ret" ist ungleich "0", wenn die Anwendung im 32Bit Modus (x86) auf einem 64Bit Betriebssystem läuft.

Autor: ralf schlegel
Stand: 09/2012

Nach oben

0006 Die eigene Anwendung "sauber" beenden

 

Kennen Sie das: Sie haben eine aufwendige Applikation geschrieben, in der IDE getestet und alles scheint zu funktionieren. Nach dem Kompilieren läuft die Anwendung auch als EXE, bis sie beendet wird und nun das:

Nach kurzer Zeit meldet sich der Windows Problemreport und meckert Ihre Anwendung an!

Sie öffnen den Taskmanager, wechsel auf die Registerkarte "Prozesse" und starten Ihre Anwendung erneut. Ihre Anwendung wird im Taskmanager sichtbar. Nun beenden Sie Ihre Anwendung und...
...sie bleibt im Taskmanger sichtbar! Nach einiger Zeit erscheint dann wieder der Problemreport.
In der IDE tritt der Fehler aber nicht auf - was läuft hier falsch?

Möglichkeit A:

Der Grund liegt mit höchster Wahrscheinlichkeit am "unsauberen" Einsatz von API Funktionen!
Nutzen Sie z.B. die API-Funktion "hMod = LoadLibrary(DllName)" und geben das erhaltene Handle nicht mit "FreeLibrary(hMod)" wieder frei, so haben Sie eine Ursache gefunden!
Gleiches kann auch bei Öffnen von Dateien über das API passieren - um nur 2 Möglichkeiten zu nennen...
Was die IDE hier noch locker wegsteckt, führt in der EXE zu o.g. Fehlverhalten.
Wenn Sie den Fehler nicht ermitteln können (er kann auch in einem eingebundenen Control liegen, das Sie von Dritten erhalten haben), so gibt es dennoch eine Möglichkeit das Programm sauber zu beenden - und zwar wiederum mit Hilfe des API und der Funktion ExitProcess.
Diese Funktion macht in der EXE das, was die IDE beim Beenden des Testprogramms macht: sie schließt alle zum Prozess gehörigen Dateien. Zusätzlich trennt sie alle offenen DLL-Verbindungen und Threads und sorgt so für ein sauberes Ende ohne Datenverlust.

Deklarieren Sie die folgenden 3 API Funktionen und erweitern Sie die Enderoutine Ihrer Applikation:

Public Declare Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long)
Public Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, _
                                                           lpExitCode As Long) As Long
Public Declare Function GetCurrentProcess Lib "kernel32" () As Long


If App.LogMode Then     ' do this only in compiled code
  ExitProcess GetExitCodeProcess(GetCurrentProcess, 0)
End If

Nach dieser Anweisung darf kein Programmcode mehr folgen.
Die Abfrage auf App.LogMode ergibt in der IDE immer '0' und sorgt so dafür, dass der nachfolgende Code nur in der compilierten Fassung abgearbeitet wird, anderenfalls wird die IDE auch beendet, da sie zu diesem Zeitpunkt noch der "CurrentProcess" ist.

Möglichkeit B:

Sie machen keine Fehler bei der Programmierung? Nun - dann sind Sie das Opfer der Microsoft ComCtl32.DLL!
Die ist nämlich in der Version 6 nicht mit VB6 kompatibel wenn ihr Projekt folgende Funktionen beinhaltet:

  1. Sie setzen User Controls ein
  2. Sie verwenden Visual Styles mit Hilfe des API "InitCommonControls"

Microsoft hält hierfür folgende Lösung bereit: "Visual Basic 6.0 unterstützt kein Theming und keine Visual Styles..."

Das ist uns dann doch etwas zu einfach! Auch hier können Sie ihren Quellcode um Möglichkeit A erweitern, denn das Problem liegt an nicht getrennten Verbindungen zur shell32.dll, oder Sie laden die shell32.dll vor der Initialisierung der Controls und halten das Handle bis zum Beenden Ihrer Anwendung offen, zum Beispiel so:

Public Declare Sub InitCommonControls Lib "comctl32.dll" ()
Public Declare Function LoadLibraryA Lib "kernel32" (ByVal lpFileName As String) As Long
Public Declare Function FreeLibrary Lib "kernel32" ( ByVal hModule As Long) As Long

Public hShell As Long

' in der Hauptform (oder alternativ in der Sub Main Routine):
Private Sub Form_Initialize()
  hShell = LoadLibraryA("shell32.dll")
  InitCommonControls
End Sub

Private Sub Form_Unload(Cancel As Integer)
  ' Ihr Code:
  ' .
  ' .
  FreeLibrary hShell
End Sub

Autor: ralf schlegel
Stand: 01/2013

Nach oben

0007 Fremdanwendung mit Adminrechten starten

 

Wenn Sie aus Ihrer eigenen Applikation heraus eine Anwendung starten wollen, so kann es vorkommen, dass diese zum reibungslosen Ablauf über Administratorrechte verfügen muss. Spätestens seit Windows Vista führt dies immer dann zum Problem, wenn der UAC (User Access Control) eingeschaltet ist, da man dann - selbst als angemeldeter Administrator - nicht über die erforderlichen Rechte verfügt um z.B. den Registrierungseditor ("regedit") aufzurufen.

Neben der VBA internen Shell Funktion, kennen Sie vielleicht die Alternative des Windows API, "ShellExecute", mit der sich u. A. eine Anwendung starten läßt. In der Parameterübergabe zu dieser Funktion gibt es das so genannte "Operation-Verb" - ein String, der die Art des Programmstarts beeinflussen kann. Neben den Schlüsselwörtern "Open", "Edit" und "Print" gibt es auch das Schlüsselwort "RunAs", das uns so auf einfachste Weise in den Administratormodus bringt!

Hier ein simples Beispiel für den Aufruf des Registrierungseditors:

Private Declare Function ShellExecuteA Lib "shell32" (ByVal hWnd As Long, _
        ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, _
        ByVal lpDirectory As String, ByVal nShowCmd As VbAppWinStyle) As Long

ShellExecuteA 0, "RunAs", "regedit.exe", vbNullString, vbNullString, vbNormalFocus

Oder Sie möchten zur Laufzeit nachträglich ein OCX oder eine DLL registrieren; ein klassischer Fall für ein Updateprogramm.

ShellExecuteA 0, "RunAs", "regsvr32", """" & ocxName & """", vbNullString, vbNormalFocus

wobei "ocxName" eine Stringvariable darstellt, die den kompletten Pfad und Namen der Datei beinhaltet. 

Autor: ralf schlegel
Stand: 01/2013

Nach oben