Rubrik: Allgemein | VB-Versionen: VB5, VB6 | 01.03.04 |
Erstellen von TypeLib's Dieser Workshop befasst sich mit dem Erstellen von Type-Libraries, sogenannten TypeLib's (TLB). Anhand eines einfachen Beispiels wird gezeigt, wie man beispielsweise den Root-Pfad im Ordner-Auswahl-Dialog individuell festlegen kann, so dass sich Ordner nur unterhalb des angegebenen Root-Verzeichnisses auswählen lassen. | ||
Autor: Ralf Schoen | Bewertung: | Views: 28.638 |
Dieser Workshop befasst sich mit dem Erstellen von Type-Libraries, sogenannten TypeLib's (TLB). Anhand eines einfachen Beispiels wird gezeigt, wie man beispielsweise den Root-Pfad im Ordner-Auswahl-Dialog individuell festlegen kann, so dass sich Ordner nur unterhalb des angegebenen Root-Verzeichnisses auswählen lassen.
API ist nicht alles.....
Wollen wir ein wenig mehr aus VB herausholen und die Interfaces nutzen die uns zur Verfügung stehen, dann sollte man sich mit der Erstellung von Type Libraries auseinandersetzen.
Wofür braucht man diese TypLib´s ?
TypLib´s können Deklarationen, Definitionen, Aufzählungen sowie Schnittstellen-Definitionen enthalten. Sobald wir eine Referenz auf so eine TypLib erstellt haben, können wir diese Informationen in dem Objekt-Betrachter sehen und uns stehen diese Definitionen zur Verfügung.
Um eine TypLib erstellen zu können, brauchen wir einen GUID Generator (UUID.EXE )und das Programm MKTYPLIB.EXE. Diese werden bei der Installation von Visual C++ mitgeliefert. Ein von mir entwickelter GUID Generator liegt dem Beispiel Projekt bei. Auch von Vorteil ist der Einsatz von OLEVIEW. Wer ihn nicht hat, kann ihn sich unter www.microsoft.com/com besorgen.
In diesem Workshop werden wir eine TypLib erstellen, die wir benötigen, um bei BrowseForFolder den Anfangspfad einstellen zu können. (Dies war eine Frage aus dem Forum.)
....We bring the Full Power to our Apps.....
TypLib´s werden in einer Scripting Sprache geschrieben, der IDL (Interface Definiton Language). IDL ist eine sehr mächtige Sprache, aber da wir uns in einer Umgebung von Visual Basic bewegen, können wir hier nicht alles ausnutzen. Doch schon die Basic´s reichen für uns völlig aus. Normalerweise werden TypLib's mit dem Tool MIDL von Microsoft compiliert. Dieses Tool wird jedoch nicht mit VB ausgeliefert. Aber es gibt ja noch das MKTYPLIB. MKTYPLIB ist ein ODL (Object Definition Language) Compiler. Der Unterschied zwischen diesen beiden Compilern liegt darin, das ODL eine Teilmenge von IDL ist und daher auch nicht vollwertig mit IDL.
Der Aufbau einer TypLib....
[ uuid([UUID]), version(1.0), helpstring("vbArchiv SF_ParseDisplayName") ] library [LibraryName] { // Libraries die eingebunden werden sollen importlib("stdole2.tlb");
uuid ist unsere neue GUID die wir nun erstellen müssen. Entweder über UUID oder über das kleine Tool welches ich mitgeliefert habe. Version und helpstring sollte klar sein. Wer sich die stdole2.tlb einmal genauer anschauen möchte, der kann dieses über das Programm OLEVIEW tun. Gefunden wird diese TLB unter TypeLibraries und dann OLE Automation. Über diese TypLib wird auch das IUNKNOWN Interface mit eingebunden, darum brauchen wir uns also nicht mehr zu kümmern.
LibraryName ist der Name unserer Library . Denken Sie sich einen schönen aus
Weiter geht's ...
// Ab hier beginnt der Inferface Block // Interfaces die sich in dieser TypLib befinden interface IShellFolder // Typen Deklaration typedef unsigned char BYTE; typedef long LPITEMIDLIST; typedef long LPTSTR; typedef long ULONG; typedef short USHORT; typedef struct SHITEMID { USHORT cb; BYTE abID[1]; } SHITEMID; typedef struct ITEMIDLIST { SHITEMID mkid; } ITEMIDLIST;
Jetzt kommt das Interface IShellFolder. Wir geben diesem Interface den Namen SF_ParseDisplayName. Normalerweise würde hier nur IShellfolder Interface stehen.
[ uuid(000214e6-0000-0000-c000-000000000046), helpstring("SF_ParseDisplayName Interface"), odl ] interface IShellFolder : IUnknown { [helpstring("ParseDisplayName")] long ParseDisplayName( [in] long hwndOwner, [in] long pbcReserved, [in] LPSTR lpszDisplayName, [in,out] ULONG *pchEaten, [in,out] LPITEMIDLIST *ppidl, [in,out] ULONG *pdwAttributes); }; }
Dies ist die gesamte TypLib die man braucht, um z. B. die Funktion ParseDisplayName im Interface IShellFolder benutzen zu können. Natürlich ist das Interface IShellFolder in Wirklichkeit viel größer, doch für unsere Zwecke reicht es erstmal. Deshalb auch der andere Name.
Viele werden sich jetzt fragen, ja schön, aber wie komme ich an diese Informationen? Über die Uuid wird das Interface identifiziert. Hier muss man ein wenig suchen. MSDN, OLEVIEW oder die Registry werden Abhilfe schaffen. Der Parameter ODL ist für mktyplib notwendig, hat aber weiter keine Bedeutung.
Dann noch ein Blick in die MSDN. Unter IShellFolder:
Inherits from : IUnknown
Ok, haben wir gemacht... interface IShellFolder: IUnknown bedeutet also nichts anderes als definiere mir das Interface IShellFolder welches sich von IUnknown ableitet.
Schauen wir uns die ParseDisplayName an...
HRESULT ParseDisplayName( HWND hwnd, LPBC pbc, LPOLESTR pwszDisplayName, ULONG *pchEaten, LPITEMIDLIST *ppidl, ULONG *pdwAttributes );
Dies sind alle Informationen die wir brauchen um dieses Interface einzubinden. Ob es sich um einen in, out oderin,out handelt steht in der Beschreibung der einzelnen Parameter.
Nun haben wir alles, um die TypLib erstellen zu können. Hierzu verwenden wir MKTYPLIB.EXE ...
Der Parameter /nocpp muss verwendet werden wenn kein C preprozessor installiert ist, was wohl bei den meisten der Fall sein wird. Damit kann man dann auch keine preprozessor Direktiven verwenden.
Mktyplib /nocpp test.odl
Nun sollte eine Meldung erscheinen die uns sagt, das die TypLib erfolgreich erstellt worden ist. Wenn dies der Fall ist, befindet sich im selben Verzeichnis wie die .odl eine neue Datei mit der Extension .tlb. Herzlichen Glückwunsch, soeben haben Sie ihre erste TypLib erstellt.
Wie bindet man nun diese TypLib in VB ein?
Eine TypLib muss auch beim System angemeldet (registriert) werden. Dafür gibt es auch kleine Tools die uns diese abnehmen (z.B. regtlib ist ein solch kleiner Helfer). Unter VB können wir uns das aber sparen. Gehen Sie unter Projekte->Verweise und dann auf den Button "Durchsuchen". Wechseln Sie in das Directory wo sich die TypLib befindet und wählen Sie diese aus. Ab nun steht die TypLib zur Verfügung.
Wenn wir nun einmal den Objekt Browser öffnen, sehen wir, dass uns die Schnittstelle die wir eben erstellt haben zur Verfügung steht.
BrowseForFolder - Root festelegen...
Nun wollen wir auch unsere TypLib einsetzen und dazu schauen wir uns die Struktur von BrowseForFolder einmal genauer an.
Private Declare Function SHBrowseForFolder Lib "shell32" _ Alias "SHBrowseForFolderA" ( _ lpbi As BROWSEINFO) As Long
Die Funktion SHBrowseForFolder verlangt als Übergabe die BROWSEINFO Struktur:
Private Type BROWSEINFO hwndOwner As Long pIDLRoot As Long pszDisplayName As Long lpszTitle As Long ulFlags As Long lpfnCallback As Long lParam As Long iImage As Long End Type
Dort sehen wir, dass es eine pIDLRoot gibt. In der MSDN steht, Pointer auf die Item Identifier List (PIDL) welche auf den Root Folder zeigt. Wenn man hier nichts angibt, dann ist der Startpunkt der Desktop. (Root vom ShellNamespace)
Ok, versuchen wir erst mal, ein wenig mehr über die PIDL zu erfahren und was wir wirklich brauchen um den Root festzulegen. Es gibt eine Funktion SHGetSpecialFolderLocation, die uns eine PIDL zurückliefert.
Private Declare Function SHGetSpecialFolderLocation Lib "shell32.dll" ( _ ByVal hwndOwner As Long, _ ByVal nFolder As Long, _ pidl As ITEMIDLIST) As Long
Unter nFolder gibt man dann die CSIDL an. Eine CSIDL ist eine eindeutige Nummer, die auf jedem System gleich ist. Die Location kann aber eine andere sein. Auf einigen System gibt es halt c:\Windows und auf anderen c:\WinNT. Mit der CSIDL "CSIDL_WINDOW" würde man dann diesen Pfad erhalten.
Hier die CSIDL´s die es so gibt.
CSIDL_FLAG_CREATE = &H8000 CSIDL_ADMINTOOLS = &H30 CSIDL_ALTSTARTUP = &H1D CSIDL_APPDATA = &H1A CSIDL_BITBUCKET = &HA CSIDL_CDBURN_AREA = &H3B CSIDL_COMMON_ADMINTOOLS = &H2F CSIDL_COMMON_ALTSTARTUP = &H1D CSIDL_COMMON_APPDATA = &H23 CSIDL_COMMON_DESKTOPDIRECTORY = &H19 CSIDL_COMMON_DOCUMENTS = &H2E CSIDL_COMMON_FAVORITES = &H1F CSIDL_COMMON_MUSIC = &H35 CSIDL_COMMON_PICTURES = &H36 CSIDL_COMMON_PROGRAMS = &H17 CSIDL_COMMON_STARTMENU = &H16 CSIDL_COMMON_STARTUP = &H18 CSIDL_COMMON_TEMPLATES = &H2D CSIDL_COMMON_VIDEO = &H37 CSIDL_CONTROLS = &H3 CSIDL_COOKIES = &H21 CSIDL_DESKTOP = &H0 CSIDL_DESKTOPDIRECTORY = &H10 CSIDL_DRIVES = &H11 CSIDL_FAVORITES = &H6 CSIDL_FONTS = &H14 CSIDL_HISTORY = &H22 CSIDL_INTERNET = &H1 CSIDL_INTERNET_CACHE = &H20 CSIDL_LOCAL_APPDATA = &H1C CSIDL_MYDOCUMENTS = &HC CSIDL_MYMUSIC = &HD CSIDL_MYPICTURES = &H27 CSIDL_MYVIDEO = &HE CSIDL_NETHOOD = &H13 CSIDL_NETWORK = &H12 CSIDL_PERSONAL = &H5 CSIDL_PRINTERS = &H4 CSIDL_PRINTHOOD = &H1B CSIDL_PROFILE = &H28 CSIDL_PROGRAM_FILES = &H26 CSIDL_PROGRAM_FILES_COMMON = &H2B CSIDL_PROGRAMS = &H2 CSIDL_RECENT = &H8 CSIDL_SENDTO = &H9 CSIDL_STARTMENU = &HB CSIDL_STARTUP = &H7 CSIDL_SYSTEM = &H25 CSIDL_TEMPLATES = &H15 CSIDL_WINDOWS = &H24
Wofür diese einzelnen CSIDL´s stehen und was dabei zu beachten ist, kann man unter http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/enums/csidl.asp nachlesen. Würde man nun diese pidl, die uns durch die Funktion SHGetSpecialFolder zurückgeliefert wurde, bei pIDLRoot übergeben, würde das Startverzeichnis geändert.
Wie man sieht, gibt es eine Möglichkeit die PIDL eines bestimmen Ordners zu bekommen. Aber wie sieht es mit einem Path aus der nicht in der CSIDL Auflistung steht. Also schauen wir uns noch mal die IShellFolder an. Dort gibt es die ParseDisplayName und deren Beschreibung lautet:übersetzt den Directory Namen in eine IDL, und genau das brauchen wir ja auch.
PIDL:
Wenn man sich die Shell Funktionen anschaut, findet man dort eine SHSimpleIDListFromPath. Hey, eine IDL von einem Path? Genau das ist es doch, oder nicht? Warum Simple?
Es gibt zwei Arten von PIDL Eine sogenannte Simple PIDL und eine Komplex PIDL. Der Unterschied liegt darin, das eine Simple PIDL nur auf eine ITEMIDLIST zeigt und die Komplex PIDL auf mehrere. (Man könnte es auch als ein Array betrachten) Und da wir keine Simple brauchen, können wir auch nicht auf diese Funktion zurückgreifen. Näher gehe ich jetzt nicht auf die beiden Unterschiede ein, da es sonst den Workshop ein wenig sprengen würde.
Also kommt jetzt doch die TypLib zum Einsatz.
Nachdem wir oben bereits die ParseDisplayName in unserer TypLib eingebunden haben, wollen wir uns noch kurz die Parameter anschauen:
Hwnd | Hier übergeben wir das Handle unserer Anwendung |
pbc | Hier der Context von dem aus wir suchen wollen. Hier kann auch ein Null-Wert übergeben werden. Odereben eine PIDL |
pwszDisplayName | Hier geben wir den Pfad an, den wir suchen (UNICODE) |
pchEaten | Gibt die Anzahl der Zeichen vom DisplayName zurück. Wenn man diese Info nicht braucht, kann auch ein NULL-Wert übergeben werden) |
ppidl | Hier kommt unsere PIDL zurück (Komplex) (ITEMIDLIST) |
pdwAttributes | Hier können wieder verschiede Attribute gesetzt werden. Infos gibt es in der MSDN |
Ok, dann haben wir ja alles zusammen um diese Funktion endlich mal zu testen.
Zuerst erstellen wir ein Projekt (Standard - EXE), das nur dazu verwendet wird um den BrowseForFolder Dialog aufzurufen. Dann erstellen wir noch eine ActiveX.DLL. Die Eingenschaften der DLL können alle so bleiben wie sie sind.
Fangen wir mit der DLL an. Deklarationen, Konstanten und API Aufrufe die wirbrauchen:
Private Const MAX_PATH = 260 Private Type BROWSEINFO hwndOwner As Long pidlRoot As Long pszDisplayName As Long lpszTitle As Long ulFlags As Long lpfn As Long lParam As Long iImage As Long End Type ' Shell Private Declare Function SHBrowseForFolder Lib "shell32.dll" _ Alias "SHBrowseForFolderA" ( _ lpbi As BROWSEINFO) As Long Private Declare Function SHGetSpecialFolderLocation Lib "shell32.dll" ( _ ByVal hwndOwner As Long, _ ByVal nFolder As Long, _ pidl As Long) As Long Private Declare Function SHGetDesktopFolder Lib "shell32.dll" ( _ ppshf As IShellFolder) As Long Private Declare Function SHGetPathFromIDList Lib "shell32.dll" _ Alias "SHGetPathFromIDListA" ( _ ByVal pidl As Long, _ ByVal pszPath As String) As Long Private Declare Function GetFullPathName Lib "kernel32" _ Alias "GetFullPathNameA" ( _ ByVal lpFileName As String, _ ByVal nBufferLength As Long, _ ByVal lpBuffer As String, _ ByVal lpFilePart As String) As Long ' BROWSEINFO Values Private Const BIF_RETURNONLYFSDIRS = &H1 Private Const BIF_RETURNFSANCESTORS = &H8 ' andere Private Declare Sub CopyMemoryLpToStr Lib "kernel32" _ Alias "RtlMoveMemory" ( _ ByVal lpvDest As String, _ lpvSource As Long, _ ByVal cbCopy As Long) Private Declare Function lstrlenptr Lib "kernel32" _ Alias "lstrlenA" ( _ ByVal lpString As Long) As Long Private Declare Function lstrlen Lib "kernel32" _ Alias "lstrlenA" ( _ ByVal lpString As String) As Long ' Members Private m_OwnerHwnd As Long Private m_sRootDir As String ' hier unsere pidlRoot Private m_pidlRoot As Long
Jetzt kommen einfach nur zwei Properties die wir benutzen um von der Standard-EXE zwei Werte zusetzen:
Public Property Get OwnerHwnd() As Long OwnerHwnd = m_OwnerHwnd End Property Public Property Let OwnerHwnd(ByVal lOwnerHwnd As Long) m_OwnerHwnd = lOwnerHwnd End Property
Public Property Get RootDir() As String RootDir = m_sRootDir End Property Public Property Let RootDir(ByVal sDir As String) m_sRootDir = sDir End Property
Sollte nicht der großen Aufklärung benötigen, was hier passiert. OwnerHwnd ist eben die hwnd unserer Form, die den Dialog aufruft und RootDir ist der Pfad, den wir als Root haben wollen.
Modifizierte BrowseForFolder-Funktion
Nun kommt die BrowseForFolder Function, die wir aufrufen um den Dialog anzuzeigen und die uns den ausgewählten Pfad wieder zurückgibt:
Public Function BrowseForFolder() As String Dim tBrowseInfo As BROWSEINFO ' Titel der angezeigt werden soll Dim sTitle As String ' sollte klar sein Dim pidlRoot As Long ' hier kommt die pidl rein die wir von BFF zurückbekommen Dim pidlFromBFF As Long ' Verzeichnis, das ausgewählt wurde, nicht der komplette Path Dim m_sDisplayName As String ' in Unicode umwandeln sTitle = StrConv("Ordern wählen", vbFromUnicode) ' Platz schaffen m_sDisplayName = String$(MAX_PATH, 0) ' wird übergeben m_OwnerHwnd tBrowseInfo.hwndOwner = m_OwnerHwnd ' hier müssen wir auf StrPtr zurückgreifen ' da wir einen Pointer brauchen tBrowseInfo.pszDisplayName = StrPtr(m_sDisplayName) ' ebenfalls einen Pointer übergeben tBrowseInfo.lpszTitle = StrPtr(sTitle) ' Flags setzen tBrowseInfo.ulFlags = BIF_RETURNONLYFSDIRS Or BIF_RETURNFSANCESTORS tBrowseInfo.iImage = 0 ' hier jetzt schauen, ob wir eine andere pidl brauchen If Len(m_sRootDir) <> 0 Then pidlRoot = GetPidlFromPath(m_sRootDir) Else pidlRoot = 0 End If ' root setzen tBrowseInfo.pidlRoot = pidlRoot ' Dialog aufrufen pidlFromBFF = SHBrowseForFolder(tBrowseInfo) ' wenn man nur den Namen des ausgewählten Verzeichnisses braucht m_sDisplayName = PointerToString(tBrowseInfo.pszDisplayName) ' hier jetzt den Pfad holen, der sich in der zurückgegebenen ' PIDL befindet BrowseForFolder = PathFromPidl(pidlFromBFF) End Function
Die Kommentare im Source sollten für diese Routine ausreichen. Wichtig ist, dass lpzDisplayName und lpszTitle als Pointer übergeben werden müssen. Dann natürlich noch worauf wir alle gewartet haben: Die Funktion GetPidlFromPath(m_sRootDir)
Private Function GetPidlFromPath(sPath As String) As Long Dim folder As IShellFolder Dim ppidl As Long Dim pchEaten As Long Dim pAtt As Long Dim lFilePos As Long Dim sReturn As String Dim lenPath As Long sReturn = String$(MAX_PATH, 0) lenPath = GetFullPathName(sPath, MAX_PATH, sReturn, lFilePos) If lenPath <> 0 Then sPath = Left$(sReturn, lenPath) Set folder = GetDesktopFolder If folder.ParseDisplayName(0&, 0&, StrConv(sPath, vbUnicode), pchEaten, ppidl, pAtt) = 0 Then GetPidlFromPath = ppidl End If End If End Function
Hier sehen wir jetzt, dass unsere TypLib zum Einsatz kommt:
Dim folder as IShellFolder
Jetzt noch ein gültiges Objekt erzeugen:
Set folder = GetDesktopFolder
Und nun können wir auf die ParseDisplayName zugreifen. Hier auch wieder darauf achten, dass der Path als Unicode erwartet wird. Bei Erfolg wird uns eine pidl zurückgeliefert. Das war es auch schon. Diese wird dann der pidlRoot zugewiesen. Hier müssen keine Konvertierungen mehr vorgenommen werden, da es sich hier schon um Pointer handelt. Wir erinnern uns: eine PIDL ist ein Pointer.
Die GetDesktopFolder liefert uns die IShellFolder für den Desktop zurück. Wie wir ja wissen: der Root vom ShellNamespace.
Private Function GetDesktopFolder() As IShellFolder Dim lR As Long lR = SHGetDesktopFolder(GetDesktopFolder) End Function
Dann noch zwei Hilfsfunktionen:
Private Function PointerToString(Pointer As Long) As String Dim lLen As Long Dim sReturn As String lLen = lstrlenptr(Pointer) sReturn = String$(lLen, 0) CopyMemoryLpToStr sReturn, ByVal Pointer, lLen PointerToString = sReturn End Function
Private Function PathFromPidl(ByVal pidl As Long) As String Dim sPath As String Dim lR As Long sPath = String$(MAX_PATH, 0) lR = SHGetPathFromIDList(pidl, sPath) If lR <> 0 Then PathFromPidl = Left$(sPath, lstrlen(sPath)) End If End Function
Jetzt noch der Aufruf aus unserem Standard Projekt. Dort befindet sich nur ein Button über den wir den Dialog aufrufen und eine TextBox in der wir den RootPfad eintragen können. Nicht vergessen, den Verweis auf unsere DLL hinzuzufügen. (Projekt->Verweise)
Option Explicit Private BFF As cBFF Private Sub Command1_Click() Dim s As String BFF.OwnerHwnd = Me.hWnd ' hier den Root Path setzen BFF.RootDir = txtPfad s = BFF.BrowseForFolder MsgBox s End Sub Private Sub Form_Load() Set BFF = New cBFF End Sub
Das war es dann auch schon .......