{"id":55001143,"date":"2018-08-01T00:00:00","date_gmt":"2020-05-13T21:11:47","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=1143"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"ActiveX_und_COM_ohne_Registrierung_verwenden","status":"publish","type":"post","link":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/","title":{"rendered":"ActiveX und COM ohne Registrierung verwenden"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg06.met.vgwort.de\/na\/9f209b3e51f346d39ade2d8c334daedc\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>ActiveX-Komponenten m&uuml;ssen, so sie unter VBA ansprechbar sein sollen, im System registriert werden. Dies erfordert administrative Rechte unter Windows, weshalb viele Administratoren solche externen Komponenten leider gar nicht gern sehen. Dass Sie manche ActiveX-Dateien jedoch auch ganz ohne Registrierung verwenden k&ouml;nnen, zeigen die Techniken dieses Beitrags.<\/b><\/p>\n<h2>VBA und die COM-Registry<\/h2>\n<p>Es gibt zun&auml;chst unter VBA zwei M&ouml;glichkeiten, um ein Objekt einer ActiveX-Komponente zu erzeugen. Die eine nutzt die Methode <b>CreateObject<\/b>, der Sie die sogenannte <b>ProgID<\/b> &uuml;bergeben und das Resultat einer Objektvariablen zuweisen. Bei dieser kann es sich um den allgemeinen Typ <b>Object<\/b> handeln. Dennoch lassen sich die Funktionen des Objekts namentlich &uuml;ber den Punkt-Operator ansprechen, obwohl <b>Object<\/b> selbst ja keinerlei Methoden aufweist. Hier verwenden Sie <b>Late Binding<\/b>.<\/p>\n<p><b>Late<\/b> deshalb, weil Windows hier den Methodennamen erst beim Aufruf auswerten muss, um an den Funktionszeiger zu gelangen, der schlie&szlig;lich angesprungen wird. Dieser Vorgang ben&ouml;tigt einige Zeit, weshalb <b>Late Binding<\/b> die Performance erheblich herabsetzt. Daf&uuml;r aber brauchen Sie immerhin keinen Verweis auf die Komponente zu setzen!<\/p>\n<p>Anders bei der zweiten Methode, die sich des Operators <b>New<\/b> bedient. Nach dem Schreiben dieses Ausdrucks in eine VBA-Routine listet <b>IntelliSense<\/b> gleich alle Klassen auf, die sich damit instanziieren lassen. Dabei kann VBA aber nur auf jene zugreifen, die es in den Verweisen des aktuellen VBA-Projekts findet. Demzufolge muss auf die gefragte ActiveX-Komponente ein Verweis gesetzt sein. VBA kennt dann aus dieser <b>Type Library<\/b> den genauen Aufbau des Objekts, weshalb Sie es es einer Objektvariablen des dezidierten Klassentyps zuweisen k&ouml;nnen. Nun haben Sie <b>Early Binding<\/b> vor sich. Technisch bedeutet dies, dass VBA nun bereits beim Kompilieren die Funktionszeiger ermitteln kann und eben diese in das <b>PCode<\/b>-Kompilat einsetzt. Das steigert die Performance ungemein.<\/p>\n<p>Doch beiden Methoden zum Erzeugen des Objekts ist eines gemein: Sie funktionieren nicht ohne die Registrierung der ActiveX-Komponente unter Windows. Der Verweis auf eine Komponente macht es n&ouml;tig, dass deren <b>Type Library<\/b> (Bibliothek) und deren <b>Interfaces<\/b> (Klassen) in den Zweigen <b>HKEY_CLASSES_ROOT<\/b> und den Unterschl&uuml;sseln <b>typelib<\/b>, <b>interface<\/b> und <b>clsid<\/b> eingetragen sind. F&uuml;r <b>CreateObject<\/b> braucht es zus&auml;tzlich Eintr&auml;ge unter <b>HKEY_CLASSES_ROOT<\/b> direkt, wobei die <b>ProgID<\/b> selbst den Schl&uuml;ssel stellt und wiederum auf eine zugeh&ouml;rige <b>clsid<\/b> verweist.<\/p>\n<p>Beispiel: Sie m&ouml;chten den Ordnerpfad zum Windows-Desktop ermitteln. Daf&uuml;r k&ouml;nnen Sie etwa die <b>Shell<\/b>-Bibliothek verwenden. Sie w&auml;hlen diese in der Liste der Verweise unter dem Eintrag <b>Microsoft Shell Controls And Automation<\/b> aus. Die Bibliothek zeigt sich dann im Objektkatalog unter der Rubrik <b>Shell32<\/b>. Mit diesem Code-Schnipsel erhalten Sie dann den Pfad:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>oShell<span style=\"color:blue;\"> As <\/span>Shell32.Shell\r\n<span style=\"color:blue;\">Set<\/span> oShell = <span style=\"color:blue;\">New<\/span> Shell32.Shell\r\n<span style=\"color:blue;\">Debug.Print<\/span> oShell.Namespace(0&).Self.Path<\/pre>\n<p>Ohne VBA-Verweis auf die <b>Shell<\/b>-Bibliothek geht es mit <b>Late Binding<\/b> auch so:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>oShell<span style=\"color:blue;\"> As Object<\/span>\r\n<span style=\"color:blue;\">Set<\/span> oShell = CreateObject(\"Shell.Application\")\r\n<span style=\"color:blue;\">Debug.Print<\/span> oShell.Namespace(0&).Self.Path<\/pre>\n<p>Was passiert hier VBA sucht in der Registry unter <b>HKEY_CLASSES_ROOT<\/b> und dem Schl&uuml;ssel <b>Shell.Application<\/b>.  Dort wertet es den Unterschl&uuml;ssel <b>CLSID<\/b> aus, der die <b>GUID<\/b> <b>{13709620-C279-11CE-A49E-444553540000}<\/b> zur&uuml;ckgibt. Im Folgenden schreitet es zum Zweig <b>HKEY_CLASSES_ROOT\\clsid<\/b> und sucht dort nach dieser <b>GUID<\/b>. Es wird f&uuml;ndig und liest nun den Unterschl&uuml;ssel <b>InProcServer32<\/b> aus, der auf die ActiveX-Datei <b>%SystemRoot%\\system32\\shell32.dll<\/b> verweist. Die kann VBA nun theoretisch schon laden und das Objekt &uuml;ber die <b>GUID<\/b> und die allen COM-Komponenten eigene API-Funktion <b>DLLGetClassObject<\/b> erzeugen. Allerdings wei&szlig; es dann noch nichts &uuml;ber das Objekt selbst. Dazu muss es einen zweiten Unterschl&uuml;ssel <b>Typelib<\/b> auswerten, der die <b>GUID<\/b> zur <b>Shell<\/b>-Bibliothek zur&uuml;ckgibt. Diese Bibliothek muss es nun ebenfalls laden, um dort nach der Klasse mit der zuvor ermittelten <b>CLSID<\/b> zu suchen. Aus <b>HKCR\\TypeLib\\<GUID>\\1.0\\0\\win32<\/b> erf&auml;hrt es, dass die Bibliothek sich konkret unter <b>C:\\Windows\\SysWOW64\\shell32.dll<\/b> befindet. In der geladenen Bibliothek klappert es nun die sogenannten <b>CoClasses<\/b> ab, die in ihr enthalten sind. Das sind all jene, die sich als Objekt neu erzeugen lassen.<\/p>\n<p>Es findet die zur <b>CLSID<\/b> geh&ouml;rige Klasse und erf&auml;hrt nun erst, welches Standard-<b>Interface<\/b> ihr eigen ist. Dabei sollte es sich ab Windows 7 um <b>IShellDispatch5<\/b> oder <b>IShellDispatch6<\/b> handeln. Nun nimmt es sich die Definition dieses Interfaces vor und kennt damit dessen Methoden. Das Interface selbst holt es sich &uuml;ber einen Aufruf der Standard-<b>COM<\/b>-Methode <b>QueryInterface<\/b> auf das zuvor per <b>DLLGetClassObject<\/b> erzeugte Objekt. Erst jetzt kann VBA das Objekt einer Variablen zuweisen.<\/p>\n<p>Sie sehen, dass VBA unter der Haube allerlei zu bew&auml;ltigen hat, wenn es um <b>Late Binding<\/b> geht. Dabei ist der Vorgang in Wirklichkeit noch deutlich komplizierter, als geschildert. Nachvollziehbar: die Performance geht dabei in die Knie. <\/p>\n<p>Doch die beiden Methoden zum Erzeugen von Objekten sind ja eigentlich nicht Thema dieses Beitrags. Die Erl&auml;uterungen sind aber f&uuml;r das Verst&auml;ndnis der weiteren Ausf&uuml;hrungen n&uuml;tzlich.<\/p>\n<h2>ActiveX-Objekte per API erstellen<\/h2>\n<p>Der Sachverhalt ist jener: Es gibt nur wenig, was Sie unter<b> C++<\/b> realisieren k&ouml;nnen, was sich nicht auch unter <b>Visual Basic 6<\/b> oder VBA umsetzen lie&szlig;e! Der Aufwand daf&uuml;r allerdings ist ungleich h&ouml;her. Denn mit normalem VBA-Code kommen Sie nicht weiter. Gerade dann, wenn es um <b>COM<\/b> geht, ben&ouml;tigen Sie in der Regel wenigstens eine Bibliothek, wie die <b>oleexp.tlb<\/b>, welche die Definition der grundlegenden <b>Interfaces<\/b> enth&auml;lt. Wir stellten diese Bibliothek bereits vor (<b>Shortlink 1096<\/b>) und werden weitere Beitr&auml;ge zum Umgang mit ihr ver&ouml;ffentlichen. Hier aber geht es ja gerade darum, die Zahl der Verweise im VBA-Projekt minimal zu halten, weshalb in der vorliegenden L&ouml;sung auf externe Bibliotheken komplett verzichtet wurde.<\/p>\n<p>Zum Einsatz kommt daf&uuml;r eine Handvoll standardisierter Windows-API-Funktionen und &#8230; etwas <b>Assembler-Code<\/b>! Ja, Sie h&ouml;ren richtig! Tats&auml;chlich kann mit einigen Tricks auch unter VBA Maschinen-Code ausgef&uuml;hrt werden. Sehr komfortabel ist das indessen nicht, aber diese Code-Teile sind in der L&ouml;sung auch nicht sonderlich umfangreich.<\/p>\n<p>Untergebracht ist alles im Modul <b>mdlCOMLoaderAsm<\/b> der Demodatenbank <b>Regless.accdb<\/b>. Die Routine zum Erzeugen eines Objekts nennt sich darin <b>GetCOMInstance<\/b>. Sie &uuml;bergeben ihr als Parameter einerseits den Pfad zur ActiveX-Datei &#8211; die nicht registriert sein muss! &#8211; und andererseits die <b>ProgID<\/b> des gew&uuml;nschten Objekts. Alternativ k&ouml;nnen Sie statt der <b>ProgID<\/b> auch direkt die <b>CLSID<\/b> der Komponente angeben, wenn Sie diese kennen. <\/p>\n<p>Wollten wir hier die genaue Funktionsweise der komplexen Prozedur erl&auml;utern, so k&ouml;nnte damit sicher die gesamte Ausgabe gef&uuml;llt werden. Deshalb beschr&auml;nken wir uns nur auf die prinzipiellen Techniken. Ansonsten verwenden Sie das Modul einfach unbesehen und importieren es bei Bedarf in Ihre eigenen Datenbankprojekte. Es weist keine weiteren Abh&auml;ngigkeiten auf und lief bisher in der vorliegenden Version in unseren eigenen Projekten anstandslos. Die Anwendung erfolgt also nach diesem Schema:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>O<span style=\"color:blue;\"> As Object<\/span>\r\n<span style=\"color:blue;\">Dim <\/span>sFile<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Dim <\/span>sProgIDCLSID<span style=\"color:blue;\"> As String<\/span>\r\nsFile = \"c:\\Windows\\SysWOW64\\shell32.dll\"\r\nsProgIDCLSID = \"Shell\"\r\n<span style=\"color:blue;\">Set<\/span> O = GetCOMInstance(sFile, sProgIDCLSID)\r\n<span style=\"color:blue;\">Debug.Print<\/span> Typename(O)   ''''-&gt; IShellDispatch6\r\nO.TueDiesUndDas ...<\/pre>\n<p>Die Objektvariable <b>O<\/b> soll das Objekt aufnehmen, welches wir instanziieren m&ouml;chten. In <b>sFile<\/b> steht der volle Pfad zur ActiveX-Datei die das Objekt beherbergt. <b>sProgIDCLSID<\/b> speichert die <b>ProgID<\/b> oder wahlweise die CLSID der Komponente. Hier verwenden wir die <b>shell32.dll<\/b> und in ihr die Objektklasse <b>Shell<\/b>. Damit erhalten wir &uuml;ber den Aufruf von <b>GetCOMInstance<\/b> dasselbe Objekt, wie mit dem eingangs vorgestellten Code. <b>Typename<\/b> gibt korrekt die <b>Interface<\/b>-Klasse <b>IShellDispatch6<\/b> zur&uuml;ck. Statt <b>TueDiesUndDas<\/b> k&ouml;nnten Sie nun eben den Pfad zum Desktop ermitteln:<\/p>\n<pre><span style=\"color:blue;\">Debug.Print<\/span> O.Namespace(0&).Self.Path\r\n<span style=\"color:blue;\">Set<\/span> O = Nothing<\/pre>\n<p>Die Routine l&auml;uft schneller ab, wenn Sie statt der <b>ProgID<\/b> die <b>CLSID<\/b> einsetzen:<\/p>\n<pre>sProgIDCLSID = \"{13709620-C279-11CE-A49E-444553540000}\"<\/pre>\n<p>Sie finden diese in der Registry im Zweig <b>HKCR\\Shell.Application\\CLSID<\/b>. Die Funktion muss dann nicht erst umst&auml;ndlich die <b>ProgID<\/b> in die ben&ouml;tigte <b>CLSID<\/b> umwandeln. Des Weiteren k&ouml;nnen Sie den vollen Pfad zur Datei auch abk&uuml;rzen:<\/p>\n<pre>sFile = \"shell32.dll\"<\/pre>\n<p>Das funktioniert in folgenden F&auml;llen: Die Datei befindet sich entweder im Verzeichnis der Datenbank, oder im Suchpfad von Windows. Der besteht aus den Windows-Verzeichnissen und allen, die in der Umgebungsvariablen <b>PATH<\/b> abgelegt sind.<\/p>\n<p>Dass das Ganze funktioniert, k&ouml;nnen Sie &uuml;brigens mit der Funktion <b>GetDesktopPath<\/b> im Modul <b>mdlTest<\/b> der Demodatenbank nachvollziehen.<\/p>\n<p>Einen dritten optionalen Parameter von <b>GetCOMInstance<\/b> haben wir noch unterschlagen: Steht <b>ClearCache<\/b> auf <b>True<\/b>, dann ermittelt die Routine aus einer <b>ProgID<\/b> die <b>CLSID<\/b> immer neu.<\/p>\n<p>Wird <b>False<\/b> &uuml;bergeben (Standard), so speichert das Modul die ermittelte <b>CLSID<\/b> beim ersten Mal ab und verwendet sie fortan bei jedem Aufruf der Funktion mit derselben <b>ProgID<\/b>.<\/p>\n<p>Das beschleunigt die Sache dann deutlich. (Allerdings werden Sie die Instanziierung von Objekten auch nicht etwa fortlaufend in einer Schleife ausf&uuml;hren, so dass der Performance-Gewinn wohl nicht sonderlich zu Buche schl&auml;gt.)<\/p>\n<h2>GetCOMInstance-Funktion im Detail<\/h2>\n<p>Die erste Aktion besteht darin, die ActiveX-Datei zu laden. Das &uuml;bernimmt an sich die API-Funktion <b>LoadLibrary<\/b>, welcher Sie den Pfad &uuml;bergeben und ein <b>Handle<\/b> auf das Modul erhalten. Das aber ist nicht immer notwendig, denn die Datei kann sich bereits im Prozessraum von Access befinden. Bei der <b>shell32.dll<\/b> etwa ist dies der Fall. Access ben&ouml;tigt sie selbst f&uuml;r seine internen Funktionen. Dann aber reicht die API-Funktion <b>GetModuleHandle<\/b> aus:<\/p>\n<pre>hModule = GetModuleHandle(sFile)<\/pre>\n<p>Bei API-Funktionen ereignet sich in der Regel kein VBA-Fehler, wenn falsche Parameter &uuml;bergeben wurden. Stattdessen definiert der R&uuml;ckgabewert den Erfolg. Hier verh&auml;lt es sich so, dass die in <b>hModule<\/b> (Long-Wert) gespeicherte R&uuml;ckgabe <b>0<\/b> ist, wenn die Funktion fehlschlug. Dann kommt <b>LoadLibrary<\/b> zum Zug:<\/p>\n<pre><span style=\"color:blue;\">If <\/span>hModule = 0<span style=\"color:blue;\"> Then<\/span> hModule = LoadLibrary(sFile)<\/pre>\n<p>Findet die die Funktion auch hier die Datei nicht, weil deren Pfad nicht stimmt oder sie sich nicht im Windows-Suchpfad befindet, so kommt es zum letzten Versuch:<\/p>\n<pre><span style=\"color:blue;\">If <\/span>hModule = 0<span style=\"color:blue;\"> Then<\/span> \r\n   hModule = LoadLibrary(CurrentProject.Path & \"\\\" & sFile)\r\nEnd if<\/pre>\n<p>Hier wird das Datenbankverzeichnis auf Existenz der Datei befragt. Schlagen alle Methoden fehl, so l&ouml;st die Routine einen VBA-Fehler aus und verl&auml;sst die Prozedur:<\/p>\n<pre><span style=\"color:blue;\">If <\/span>hModule = 0<span style=\"color:blue;\"> Then<\/span>\r\n   Err.Raise vbObjectError, , sFile & \" module not loaded\"\r\n   <span style=\"color:blue;\">Exit Function<\/span>\r\n<span style=\"color:blue;\">End If<\/span><\/pre>\n<p>Wir gehen davon aus, dass die Datei erfolgreich geladen wurde. Nun geht es an den n&auml;chsten Test, der untersucht, ob die Datei ActiveX-kompatibel ist und die die API-Funktion <b>DllGetClassObject<\/b> exportiert:<\/p>\n<pre>pFunc = GetProcAddress(hModule, \"DllGetClassObject\")\r\n<span style=\"color:blue;\">If <\/span>pFunc = 0<span style=\"color:blue;\"> Then<\/span>\r\n     Err.Raise vbObjectError, , _\r\n         sFile & \" does not export DllGetClassObject\"\r\n     FreeLibrary hModule\r\n     <span style=\"color:blue;\">Exit Function<\/span>\r\n<span style=\"color:blue;\">End If<\/span><\/pre>\n<p>Der API-Funktion <b>GetProcAddress<\/b> &uuml;bergeben Sie das zuvor erhalten <b>Handle<\/b> (<b>hModule<\/b>) und den Namen einer Funktion. In der Long-Variablen <b>pFunc<\/b> ist nun der Funktionszeiger gespeichert. Steht hier eine Null, so unterst&uuml;tzt die Datei diese Funktion nicht und es kommt ebenfalls zu einer Fehlermeldung und zum Verlassen der Routine.<\/p>\n<p>Anhand des Funktionszeigers kann im Folgenden die Funktion <b>DllGetClassObject<\/b> im Prinzip aufgerufen werden. Das allerdings kann VBA im Unterschied zu C++ keinesfalls! Hier kommt eine Hilfsfunktion <b>CallPointerASM<\/b> zum Einsatz, die Ihnen garantiert kryptisch vorkommen wird, weil sie auf Umwegen <b>Assembler<\/b>-Code generiert, der von der API-Funktion <b>CallWindowProc<\/b> angesprungen wird. Sie ist so deklariert:<\/p>\n<pre><span style=\"color:blue;\">Private Function <\/span>CallPointerASM( _\r\n     ByVal fnc<span style=\"color:blue;\"> As Long<\/span>, ParamArray Params())<span style=\"color:blue;\"> As Long<\/span>\r\n     ...\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p>Mit dem Parameter <b>fnc<\/b> setzen Sie den Funktionszeiger und in <b>Params()<\/b> eine beliebige Anzahl von zu &uuml;bergebenden Parametern, da diese Variable vom Typ <b>ParamArray<\/b> ist. Die Prozedur erzeugt nun mit API-Funktionen, wie <b>VirtualAlloc<\/b>, einen Speicherblock im <b>RAM<\/b> und bringt dort die Parameter unter. Genauer <b>pusht<\/b> es diese auf den virtuellen <b>Stack<\/b>. Abschlie&szlig;end gibt es diesen Speicherblock auch gleich wieder frei (<b>VirtualFree<\/b>). Zuvor jedoch wird die API-Funktion <b>CallWindowProc<\/b> zweckentfremdet. Eigentlich ist sie daf&uuml;r gedacht, um die Standard-Routine eines Windows-Fensters anzuspringen. M&ouml;glicherweise kennen Sie sie deshalb schon von <b>Subclassing<\/b>-Routinen, wo <b>Windows-Messages<\/b> an eine per <b>AddressOf<\/b> erhaltene VBA-Modulfunktion geleitet werden und von dort aus weiter an die urspr&uuml;ngliche Funktionsadresse &uuml;ber <b>CallWindowProc<\/b>, damit das Windows-Fenster weiter funktioniert, wie gedacht. Tats&auml;chlich aber wei&szlig; Windows nicht, ob die Funktionsadresse die eines Fensters ist, oder irgendetwas Anderes. Wir verwenden stattdessen nun den eben erzeugten Speicherblock als Adresse, welcher den <b>Assembler<\/b>-Code enth&auml;lt. Windows f&uuml;hrt nun unmittelbar etwa die Funktion <b>DllGetClassObject<\/b> aus!<\/p>\n<p>Es kann sein, dass Sie &auml;hnlichen Code im Netz finden, wenn Sie nach <b>CallPointer VB<\/b> googeln. Die meisten L&ouml;sungen verwenden dabei einfach ein Byte-Array, in dem der <b>Assembler<\/b>-Code abgespeichert wird. An <b>CallWindowProc<\/b> wird dann die Adresse des Byte-Arrays geleitet. Das hat fr&uuml;her auch funktioniert, seit <b>VBA 7<\/b> (<b>Office 2010<\/b>) jedoch nicht, weil nun aus Sicherheitsgr&uuml;nden der allen Variablen zugrundeliegende RAM-Speicher nicht mehr mit dem Attribut <b>PAGE_EXECUTE_READWRITE<\/b> ausgestattet ist, um Ungemach zu verhindern. Deshalb wurde die Routine <b>CallPointerASM <\/b>komplett neu entwickelt.<\/p>\n<p>Zur&uuml;ck zur Prozedur <b>GetCOMInstance<\/b>! Bevor die API-Funktion <b>DllGetClassObject <\/b>in ihr angesprungen werden kann, muss erst noch die <b>CLSID<\/b> bereitstehen. Denn die API-Funktion hat folgende prototypische Definition:<\/p>\n<pre>Declare Function DllGetClassObject( _\r\n&nbsp;&nbsp;&nbsp;&nbsp;Byval CLSID<span style=\"color:blue;\"> As <\/span>GUID, _\r\n&nbsp;&nbsp;&nbsp;&nbsp;ByVal IID<span style=\"color:blue;\"> As <\/span>GUID, _\r\n&nbsp;&nbsp;&nbsp;&nbsp;pResultingInterface<span style=\"color:blue;\"> As Long<\/span>)<span style=\"color:blue;\"> As Long<\/span><\/pre>\n<p>Sie ben&ouml;tigt also die <b>CLSID<\/b> des Objekts, welches man erhalten m&ouml;chte. Au&szlig;erdem muss die <b>IID<\/b> eine <b>GUID<\/b> aufweisen, die zu einem <b>Interface<\/b> geh&ouml;rt, welches die Funktion zur&uuml;ckgeben soll. F&uuml;r ein Objekt reicht hier das Standard-<b>Interface<\/b> <b>IUnknown<\/b> aus, f&uuml;r die Instanziierung muss aber das <b>Interface<\/b> <b>IClassFactory<\/b> her, weil erst dieses die endg&uuml;ltige Funktion <b>CreateInstance<\/b> beherbergt.<\/p>\n<p>Die Aufgabe, aus einer <b>ProgID<\/b> eine <b>CLSID<\/b> zu machen ohne dass die Klasse registriert ist (!), ist allerdings alles andere, als trivial. Die Hilfsroutine <b>ScanCoClasses<\/b> &uuml;bernimmt sie. Ihre Darstellung sprengte trotz der nur etwa 40 Zeilen endg&uuml;ltig den Rahmen dieses Beitrags. Sie simuliert &uuml;ber benutzerdefinierte Typen Interfaces der Abteilung <b>ITypelib<\/b> und <b>ITypeInfo<\/b>, deren Methoden sie abermals &uuml;ber <b>CallPointerASM<\/b> aufruft. Dabei ermittelt sie alle <b>CoClasses<\/b> der ActiveX-Datei und deren zugeh&ouml;rigen <b>CLSIDs<\/b>, sowie <b>ProgIDs<\/b>. Nachdem diese in einem Array abgespeichert sind, kann die Hauptroutine dieses durchlaufen und mit der &uuml;bergebenen <b>ProgID<\/b> vergleichen. Trifft ein Element zu, so haben Sie die <b>CLSID<\/b> zur <b>ProgID<\/b>. <b>DllGetClassObject<\/b> kann nun ausgef&uuml;hrt und eine Instanz des gew&uuml;nschten Objekts &uuml;ber die <b>CreateInstance<\/b>-Methode von <b>IClassFactory<\/b> erhalten werden.<\/p>\n<p>Das ist alles nicht so einfach. Gl&uuml;ckwunsch, wenn Sie das Kapitel bis zu diesem Punkt durchgehalten haben! Es ist tats&auml;chlich nur f&uuml;r die Hartgesottenen unter Ihnen gedacht. F&uuml;r den praktischen Einsatz des Moduls ist dieses Wissen nicht notwendig.<\/p>\n<h2>Beispiel ActiveX-Datei zum &auml;hnlichkeitsvergleich<\/h2>\n<p>Im Download zum Beitrag finden Sie eine ActiveX-Datei <b>mSimilarity.dll<\/b>, die unter anderem Strings auf &auml;hnlichkeit untersuchen kann und aus eigener Feder stammt. Das ist ein Erfordernis, das gerade in Datenbanken oft ben&ouml;tigt wird. Aus einer Adressentabelle filtern Sie etwa jene Namen heraus, die bei falscher Schreibweise eines Suchbegriffs die &auml;hnlichsten Treffer enthalten. Eine &auml;hnliche Komponente kam im Beitrag <b>Fehlertolerantes Suchen<\/b> (<b>Shortlink 733<\/b>) bereits zum Einsatz. Dort ist auch der Umgang mit ihren Funktionen ersch&ouml;pfend beschrieben. Die aktuelle DLL ist eine Weiterentwicklung mit mehr Methoden und verbesserter Performance. Eine bessere Alternative werden Sie wahrscheinlich vergeblich im Netz suchen&#8230; Und, bevor es wieder zu Nachfragen per E-Mail kommt: Ja, die DLL ist komplett <b>Freeware<\/b> und darf ohne weitere Hinweise in eigenen Projekten eingesetzt und weitergegeben werden, ob privat oder kommerziell. Eine kleine Angabe zum Autor <b>mossSOFT<\/b> an der einen oder anderen Stelle kann jedoch nicht schaden.<\/p>\n<p>Sie registrieren die DLL &uuml;ber die begleitende Datei <b>register_mSimilarity.bat<\/b>, welche Sie im Administrator-Kontext ausf&uuml;hren. Bei Weitergabe Ihres Datenbankprojekts entfiele dieser Schritt, weil wir Klassen der DLL <b>regless<\/b> instanziieren werden. F&uuml;r die Demodatenbank ist die Registrierung allerdings n&ouml;tig, weil deren Routinen teilweise ihren Bibliotheksverweis ansprechen. <\/p>\n<p>Hier ein Code-Schnipsel, welches die &auml;hnlichkeitsmethode <b>JaroWinkler<\/b> demonstriert:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>C<span style=\"color:blue;\"> As <\/span>CSimilarity\r\n<span style=\"color:blue;\">Dim <\/span>S1<span style=\"color:blue;\"> As String<\/span>, S2<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Set<\/span> C = <span style=\"color:blue;\">New<\/span> CSimilarity\r\nS1 = \"Trowitzsch\"\r\nS2 = \"Trowitsch\"\r\n<span style=\"color:blue;\">Debug.Print<\/span> C.JaroWinkler(S1,S2)   ''''-&gt; 92<\/pre>\n<p>Die gefragte Klasse in der DLL nennt sich <b>CSimilarity<\/b>. Eine Objektinstanz auf sie landet in der Variablen <b>C<\/b>. Zwei Begriffe in <b>S1<\/b> und <b>S2<\/b> weisen kleine Unterschiede in der Schreibweise auf. (Die erste ist korrekt!) <b>JaroWinkler<\/b> vergleicht beide und gibt <b>92<\/b> als &auml;hnlichkeitsfaktor aus. <b>100<\/b> w&auml;re der Wert f&uuml;r komplette &uuml;bereinstimmung. Als Limit f&uuml;r hinreichende &auml;hnlichkeit nehmen Sie &uuml;brigens einen Wert zwischen <b>70<\/b> und <b>80<\/b>. Alles darunter ist eher fragw&uuml;rdig.<\/p>\n<p>Die Routine in Listing 1 testet nun die Performance der Methoden. Neben <b>JaroWinkler<\/b> zieht sie zus&auml;tzlich die Alternative <b>Ratcliff<\/b> heran. Die Variable <b>n<\/b> speichert jeweils den &auml;hnlichkeitswert. Beide Methoden werden in einer Schleife 1 Million Mal aufgerufen. Die daf&uuml;r ben&ouml;tigte Zeit messen der <b>VBA.Timer<\/b> und die Variable <b>T<\/b>. Das Ergebnis auf meinem <b>I7-5820<\/b>-Rechner:<\/p>\n<pre><span style=\"color:blue;\">Sub <\/span>TestSimilarity1()\r\n     <span style=\"color:blue;\">Dim <\/span>C<span style=\"color:blue;\"> As <\/span><span style=\"color:blue;\">New<\/span> CSimilarity\r\n     <span style=\"color:blue;\">Dim <\/span>I<span style=\"color:blue;\"> As Long<\/span>, n<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>S1<span style=\"color:blue;\"> As String<\/span>, S2<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>T<span style=\"color:blue;\"> As Single<\/span>\r\n     S1 = \"Trowitzsch\": S2 = \"Trowitsch\"\r\n     n = C.JaroWinkler(S1, S2)\r\n     n = C.Ratcliff(S1, S2)\r\n     DoEvents\r\n     T = VBA.Timer\r\n     For I = 1 To 1000000\r\n         n = C.JaroWinkler(S1, S2)\r\n     <span style=\"color:blue;\">Next<\/span> I\r\n     <span style=\"color:blue;\">Debug.Print<\/span> VBA.Timer - T, n\r\n     T = VBA.Timer\r\n     For I = 1 To 1000000\r\n         n = C.Ratcliff(S1, S2)\r\n     <span style=\"color:blue;\">Next<\/span> I\r\n     <span style=\"color:blue;\">Debug.Print<\/span> VBA.Timer - T, n\r\n     <span style=\"color:blue;\">Set<\/span> C = Nothing\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><!--30percent--><\/p>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Dies ist der Standard-Code bei registrierter ActiveX-DLL<\/span><\/b><\/p>\n<pre>0,84375 s       92 \r\n0,9414063 s     91<\/pre>\n<p><b>JaroWinkler<\/b> ist also geringf&uuml;gig schneller, als <b>Ratcliff<\/b>. Die zur&uuml;ckgegebenen &auml;hnlichkeitswerte sind bei beiden fast identisch. Die Performance ist auch nicht schlecht: Jede Schleife auf die Funktionen wird in weniger als einer Sekunde durchlaufen. Sie k&ouml;nnen damit also eine Million Strings pro Sekunde vergleichen. Die Dauer, die VBA f&uuml;r die Schleife selbst ben&ouml;tigt, spielt &uuml;brigens keine Rolle. Ein Test auf eine Quasi-Null-Schleife in der folgenden Form gibt hier <b>0,0078 ms<\/b> f&uuml;r die Ausf&uuml;hrungszeit aus. Das sind eine Milliarde Durchl&auml;ufe pro Sekunde! VBA ist also alles andere als langsam:<\/p>\n<pre>For i = 1 To 1000000\r\n     n = n\r\n<span style=\"color:blue;\">Next<\/span> i<\/pre>\n<p>Nun ersetzen wir in einer weiteren Routine die Instanziierung des Klassenobjekts per <b>New<\/b> durch die <b>regless<\/b>-Variante per <b>GetCOMInstance<\/b>, wie in Listing 2. Sie &uuml;bergeben hier in <b>sFile<\/b> den Pfad zur Datei <b>mSimilarity.dll<\/b> im Datenbankverzeichnis<b> <\/b>und verwenden die <b>ProgID<\/b> <b>CSimilarity<\/b>. Den <b>CLSID<\/b>-Cache des Moduls <b>mdlCOMLoaderASM<\/b> leeren Sie mit &uuml;bergabe von <b>True<\/b> an den dritten Parameter <b>ClearCache<\/b>. Der Rest der Prozedur ist identisch mit dem aus <b>TestSimilarity1<\/b>. Wenig erstaunlich: die Performance ist auch nicht schlechter!<\/p>\n<pre><span style=\"color:blue;\">Sub <\/span>TestSimilarity2()\r\n     <span style=\"color:blue;\">Dim <\/span>sFile<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>C<span style=\"color:blue;\"> As <\/span>CSimilarity\r\n     <span style=\"color:blue;\">Dim <\/span>sCLSIDProgID<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>I<span style=\"color:blue;\"> As Long<\/span>, n<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>S1<span style=\"color:blue;\"> As String<\/span>, S2<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>T<span style=\"color:blue;\"> As Single<\/span>\r\n     S1 = \"Trowitzsch\": S2 = \"Trowitsch\"\r\n     sCLSIDProgID = \"CSimilarity\"\r\n     sFile = CurrentProject.Path & \"\\mSimilarity.dll\"\r\n     <span style=\"color:blue;\">Set<\/span> C = GetCOMInstance(sFile, sCLSIDProgID, <span style=\"color:blue;\">True<\/span>)\r\n     ''''= CSimilarity\r\n     n = C.JaroWinkler(S1, S2)\r\n     n = C.Ratcliff(S1, S2)\r\n     DoEvents\r\n     T = VBA.Timer\r\n     For I = 1 To 1000000\r\n         n = C.JaroWinkler(S1, S2)\r\n     <span style=\"color:blue;\">Next<\/span> I\r\n     <span style=\"color:blue;\">Debug.Print<\/span> VBA.Timer - T, n\r\n     T = VBA.Timer\r\n     For I = 1 To 1000000\r\n         n = C.Ratcliff(S1, S2)\r\n     <span style=\"color:blue;\">Next<\/span> I\r\n     <span style=\"color:blue;\">Debug.Print<\/span> VBA.Timer - T, n\r\n     <span style=\"color:blue;\">Set<\/span> C = Nothing\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Und das ist die regless-Variante mit GetCOMInstance<\/span><\/b><\/p>\n<p>Das liegt daran, dass erstens die Instanzierung des Objekts aus der Messung herausgelassen wurde &#8211; daf&uuml;r messen wir gesondert etwa <b>90 ms<\/b> &#8211; und zweitens die Objektvariable <b>C<\/b> als <b>CSimilarity<\/b>-Typ deklariert ist, wodurch implizit <b>Early Binding<\/b> auf die Verweisbibliothek <b>mSimilarity<\/b> zum Einsatz kommt.<\/p>\n<p>Ohne Verweis k&ouml;nnen Sie <b>C<\/b> nur noch <b>As Object<\/b> deklarieren, wodurch der Ausf&uuml;hrungsmodus nach <b>Late Binding<\/b> wechselt. Die hier nicht gelistete Routine <b>TestSimilarity3<\/b> vollzieht dies. F&uuml;r die Performance messen wir nun folgende Werte:<\/p>\n<pre>3,929688 s     92\r\n4,199219 s     91<\/pre>\n<p>Tja, die ist nun um Gr&ouml;&szlig;enordnungen geringer, als bei <b>Early Binding<\/b>. Aber immerhin lassen sich auch damit etwa <b>250.000<\/b> Strings pro Sekunde vergleichen, was wohl noch hinzunehmen ist.<\/p>\n<p>Es gibt jedoch noch eine alternative Methode, um die Funktionen der DLL aufzurufen und das <b>Late Binding<\/b> zu umgehen. Die allerdings ist wieder &auml;hnlich kompliziert, wie die Routine <b>GetCOMInstance<\/b> &#8230;<\/p>\n<h2>Direkter Methodenaufruf in ActiveX-Objekten<\/h2>\n<p>Das Modul <b>mdlCOMLoaderASM<\/b> enth&auml;lt noch eine zus&auml;tzliche Prozedur <b>CallCOMFunction<\/b>. Ihr &uuml;bergeben Sie ein Objekt, die Adresse einer Methode und die ihr zu &uuml;bergebenden Funktionsparameter. Als Ergebnis (Variant) erhalten Sie entweder nichts bei einer <b>Sub<\/b>-Methode oder d&iacute;e R&uuml;ckgabe der Methode bei einer <b>Funktion<\/b>. Listing 3 bildet die Prozedur ab.<\/p>\n<pre><span style=\"color:blue;\">Function <\/span>CallCOMFunction(ByVal InterfacePointer<span style=\"color:blue;\"> As Long<\/span>, ByVal VTableOffset<span style=\"color:blue;\"> As Long<\/span>, _\r\n                                 ByVal fuReturnType<span style=\"color:blue;\"> As <\/span>eCALLRETURNTUYPE, _\r\n                                 ByVal Convention<span style=\"color:blue;\"> As <\/span>eCALLCONVENTION, _\r\n                                 ParamArray fuParameters()<span style=\"color:blue;\"> As Variant<\/span>)<span style=\"color:blue;\"> As Variant<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>I<span style=\"color:blue;\"> As Long<\/span>, n<span style=\"color:blue;\"> As Long<\/span>, ret<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>vParams()<span style=\"color:blue;\"> As Variant<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>vRtn<span style=\"color:blue;\"> As Variant<\/span>\r\n     \r\n     <span style=\"color:blue;\">If <\/span>InterfacePointer = 0& Or VTableOffset &lt; 0&<span style=\"color:blue;\"> Then<\/span> <span style=\"color:blue;\">Exit Function<\/span>\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> (fuReturnType And &HFFFF0000) = 0&<span style=\"color:blue;\"> Then<\/span> <span style=\"color:blue;\">Exit Function<\/span>\r\n     vParams() = fuParameters()\r\n     n = <span style=\"color:blue;\">UBound<\/span>(vParams) + 1&\r\n     <span style=\"color:blue;\">If <\/span>n &gt; 0&<span style=\"color:blue;\"> Then<\/span>\r\n         For I = 0& To n - 1&\r\n             vParamPtr(I) = VarPtr(vParams(I))\r\n             vParamType(I) = VarType(vParams(I))\r\n         <span style=\"color:blue;\">Next<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     ret = DispCallFunc(InterfacePointer, VTableOffset, Convention, _\r\n             fuReturnType, n, vParamType(0), vParamPtr(0), vRtn)\r\n     <span style=\"color:blue;\">If <\/span>ret = 0&<span style=\"color:blue;\"> Then<\/span>    ''''OK\r\n         CallCOMFunction = vRtn\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         <span style=\"color:blue;\">Debug.Print<\/span> \"Error h\" & Hex(ret) & \" at vtableoffset \"; VTableOffset\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Der Aufruf einer Methode eines Objekts erfolgt hier &uuml;ber die API-Funktion DispCallFunc<\/span><\/b><\/p>\n<p>Im Kern bedient sie sich der API-Funktion <b>DispCallFunc<\/b>. Die erwartet einen Zeiger auf das Objekt (<b>InterfacePointer<\/b>), den sogenannten <b>VTable-Offset<\/b> der gew&uuml;nschten Methode, die Aufrufkonvention der DLL und die Parameter in zwei Arrays, wobei das erste die Werte abbildet, das zweite deren Datentypen. Sie verstehen nur Bahnhof Das ist nicht verwunderlich, weil wir es hier wieder mit Interna von Objekten und deren <b>IDispatch<\/b>-Interface zu tun haben.<\/p>\n<p>Ein Objekt stellt imgrunde nur einen <b>Benutzerdefinierten Typ<\/b> dar. In diesem steht ganz zu Beginn eine Tabelle mit den Funktionszeigern auf die Methoden (<b>VTable<\/b>). Sie erhalten einen Speicherverweis auf diese Tabelle &uuml;ber die VBA-Funktion <b>ObjPtr()<\/b> auf das Objekt. In dieser Tabelle stehen jedoch nicht etwa die Namen der Methoden, sondern lediglich aneinandergereiht Long-Werte, die auf die eigentlichen Funktionen zeigen. Der Wert dieser Zeiger selbst ist f&uuml;r die API-Funktion <b>DispCallFunc<\/b> unerheblich. Sie liest sie n&auml;mlich selbst aus. Was sie daf&uuml;r aber braucht, ist die genaue Position einer Methode in der Tabelle. Und das ist der besagte <b>Offset<\/b>. Ist also etwa die Methode <b>JaroWinkler<\/b> der 16. Methodeneintrag in der Tabelle, so &uuml;bergeben Sie im Parameter <b>VTableOffset<\/b> den Wert 4*16 = 64. Mit diesem Wissen kann dann die API-Funktion den Code der Methode direkt anspringen und umgeht damit die Aufl&ouml;sung des Namens <b>JaroWinkler<\/b>. Dass dies schneller vonstattengeht, versteht sich von selbst.<\/p>\n<p>Nun fragt sich, woher man zu einer Methode den <b>VTable-Offset<\/b> bekommt. Leider sind Sie hier auf ein Zusatz-Tool angewiesen, das wir erst sp&auml;ter ausf&uuml;hrlicher beschreiben. Denn der VBA-Objektkatalog l&auml;sst uns dabei im Regen stehen.<\/p>\n<p>Die Reihenfolge der Methoden, wie er sie auf der rechten Seite zu einer Klasse anzeigt, ist alphabetisch sortiert, nicht aber nach der Reihenfolge der Methoden in der <b>VTable<\/b> des <b>Klassen-Interfaces<\/b>! Zudem unterschl&auml;gt er die jedem Klassenobjekt eigenen Basis-<b>Interfaces<\/b> <b>IUnknown<\/b> und <b>IDispatch<\/b>, die in der Methodenreihenfolge ebenfalls zu ber&uuml;cksichtigen sind und dort ganz oben stehen.<\/p>\n<p>Wie auch immer, die <b>Enumeration<\/b> <b>eFuSimilarity<\/b> im Modul <b>mdlTest<\/b> zeigt alle Methoden der Klasse <b>CSimilarity<\/b> in korrekter Reihenfolge, so dass der <b>VTable-Offset <\/b>&uuml;ber sie ermittelt werden kann. Erstellt ist sie mithilfe eines Zusatz-Tools, obwohl man sie auch &uuml;ber VBA-Routinen und die Bibliothek <b>oleexp<\/b> anlegen k&ouml;nnte. Den Aufwand betrieben wir aber nicht. Die <b>Enumeration<\/b> sieht nun so aus:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>Enum eFuSimilarity\r\n     eFuQueryInterface = 0& ''''IUnknown ...\r\n     eFuAddRef\r\n     eFuRelease\r\n     eFuGetTypeInfoCount    ''''IDispatch ...\r\n     eFuGetTypeInfo\r\n     eFuGetIDsOfNames\r\n     eFuInvoke\r\n     eFuDummy   ''''_CSimilarity ...\r\n     eFuPreparedStrings\r\n     eFuKoelnPhonetic\r\n     eFuAbout\r\n     eFuMCWPA\r\n     eFumossSimilar\r\n     eFumossPhonetic\r\n     eFuConsonantVowelSim\r\n     eFuLevenshtein\r\n     eFuJaroWinkler\r\n     eFuRatcliff\r\n     eFuSplitMult\r\n     eFuAutoCorrectDE\r\n     eFuHyphen\r\n     eFuHyphenator\r\n     eFuInitDictionary\r\nEnd Enum<\/pre>\n<p>Nebenbei: Wenn Sie in einer <b>Enumeration<\/b> die Wertangaben weglassen, so nimmt VBA f&uuml;r sie aufsteigende Long-Werte, beginnend mit 0, gem&auml;&szlig; der Position an.<\/p>\n<p>Wenn Sie durchz&auml;hlen, dann ergibt sich f&uuml;r <b>eJaroWinkler<\/b> der Wert 16. Diesen Wert multiplizieren Sie mit 4, um den V<b>Table-Offset<\/b> zu erhalten, weil ein Long-Wert f&uuml;r einen Funktionszeiger eben 4 Bytes einnimmt. <\/p>\n<p>Au&szlig;er dem Aufruf der API-Funktion <b>DispCallFunc<\/b> besteht der Hauptteil der Prozedur <b>CallCOMFunction<\/b> in der Transformierung der variablen Parameter in die zwei Parameter-Arrays. In <b>vParamPtr<\/b> gelangen die Parameterwerte, in <b>vParamType<\/b> die zugeh&ouml;rigen Datentypen, die mit <b>VarType<\/b> ermittelt werden. Die Werte speichern sich dabei allerdings als Zeiger &uuml;ber die VBA-Funktion <b>VarPtr<\/b>, welche zu beliebigen Variablen deren RAM-Speicheradresse zur&uuml;ckgibt. Viele API-Funktionen ben&ouml;tigen diese indirekte Angabe.<\/p>\n<p>Die Test-Routine, welche die neue Methodenaufruffunktion im Verein mit <b>regless<\/b>-Instanziierung verwendet, zeigt Listing 4. Die Instanziierung des <b>CSimilarity<\/b>-Objekts <b>C<\/b> geschieht hier direkt &uuml;ber die <b>CLSID<\/b>. Bevor es dann an die Schleifen mit den Methodenaufrufen geht, kommt noch eine Spezialfunktion zum Einsatz. <b>CallCOMFunction<\/b> erwartet n&auml;mlich im ersten Parameter nicht einen Zeiger auf das Objekt (<b>CoClass<\/b>) selbst, sondern auf dessen die eigentliche Funktionalit&auml;t enthaltendes <b>Interface<\/b>. Und das ist in der ActiveX-Datei das Interface <b>[_CSimilarity]<\/b>, welches durch die <b>GUID<\/b> in der Konstanten <b>cIID<\/b> oben definiert ist. Die muss zun&auml;chst mit der API-Funktion <b>CLSIDFromString<\/b> in eine bin&auml;re Form umgewandelt werden und landet im <b>GUID<\/b>-Array <b>aGUID<\/b>.<\/p>\n<pre><span style=\"color:blue;\">Sub <\/span>TestSimilarity6()\r\n     <span style=\"color:blue;\">Dim <\/span>sFile<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>C<span style=\"color:blue;\"> As Object<\/span>, lObject<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>I<span style=\"color:blue;\"> As Long<\/span>, n<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>S1<span style=\"color:blue;\"> As String<\/span>, S2<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>pS1<span style=\"color:blue;\"> As Long<\/span>, pS2<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>T<span style=\"color:blue;\"> As Single<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lReserved<span style=\"color:blue;\"> As Boolean<\/span>, pReserved<span style=\"color:blue;\"> As Long<\/span>, aGUID(3)<span style=\"color:blue;\"> As Long<\/span>\r\n     Const cIID = \"{FF3D5692-BE68-4206-A9FB-5ACD29F66076}\"\r\n     ''''= [_CSimilarity]-Interface\r\n     S1 = \"Trowitzsch\": S2 = \"Trowitsch\"\r\n     pS1 = StrPtr(S1): pS2 = StrPtr(S2)\r\n     lReserved = False: pReserved = VarPtr(lReserved)\r\n     \r\n     sFile = CurrentProject.Path & \"\\mSimilarity.dll\"\r\n     <span style=\"color:blue;\">Set<\/span> C = GetCOMInstance(sFile, _\r\n&nbsp;&nbsp;&nbsp;&nbsp;\"{D1589C88-D646-4148-89DA-BC4DABE8E089}\")\r\n     CLSIDFromString StrPtr(cIID), aGUID(0)\r\n     If 0 = CallCOMFunction(ObjPtr(C), 0&, vbLong, 4&, _\r\n         VarPtr(aGUID(0)), VarPtr(lObject)) Then\r\n         DoEvents\r\n         T = VBA.Timer\r\n         For I = 1 To 1000000\r\n             <span style=\"color:blue;\">Call<\/span> CallCOMFunction(lObject, eFuJaroWinkler * 4&, _\r\n                 CR_INTEGER, CC_STDCALL, _\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    pS1, pS2, pReserved, pReserved, VarPtr(n))\r\n         <span style=\"color:blue;\">Next<\/span> I\r\n         <span style=\"color:blue;\">Debug.Print<\/span> VBA.Timer - T, n\r\n         T = VBA.Timer\r\n         For I = 1 To 1000000\r\n             <span style=\"color:blue;\">Call<\/span> CallCOMFunction(lObject, eFuRatcliff * 4&, _\r\n                 CR_INTEGER, CC_STDCALL, _\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    pS1, pS2, pReserved, VarPtr(n))\r\n         <span style=\"color:blue;\">Next<\/span> I\r\n         <span style=\"color:blue;\">Debug.Print<\/span> VBA.Timer - T, n\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> C = Nothing\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Die Objektmethoden werden &uuml;ber CallCOMFunction aufgerufen.<\/span><\/b><\/p>\n<p>Nun wird <b>CallCOMFunction<\/b> zum ersten Mal aufgerufen. Als Zeiger auf das Interface wird per VBA-<b>ObjPtr<\/b> die <b>CoClass<\/b> spezifiziert. Als <b>VTableOffset<\/b> wird <b>0<\/b> &uuml;bergeben. Das ist somit der erste Eintrag in der <b>Enumeration<\/b> oben, der auf die Methode <b>QueryInterface<\/b> verweist. Diese allen Objekten und Interfaces eigene Methode erlaubt es, an den Zeiger auf ein zugrundeliegendes <b>Interface<\/b> zu gelangen. Und der landet in der Long-Variablen <b>lObject<\/b> als Parameter, welche wieder nicht direkt als Variable, sondern per <b>VarPtr<\/b> als Zeiger &uuml;bergeben wird. Denn meist erwartet <b>CallCOMFunction<\/b> Zeiger auf Variablen. Das ist immer dann der Fall, wenn auf eine Variable <b>ByRef<\/b> verwiesen wird. Die Variablenzeiger erhalten Sie mit diesen VBA-Funktionen:<\/p>\n<p><b>CallCOMFunction<\/b> gibt &uuml;brigens selbst nicht den R&uuml;ckgabewert einer Funktion aus. Der ist als Variable im letzten Parameter zu finden. Stattdessen ist der R&uuml;ckgabewert (<b>HRESULT<\/b> in C++) ein Hinweis darauf, ob der Methodenaufruf klappte. Das ist in der Regel der Fall, wenn die Funktion <b>0<\/b> zur&uuml;ckgibt.<\/p>\n<p>In <b>lObject<\/b> steht nun der Zeiger auf das <b>Interface<\/b> von <b>CSimilarity<\/b>. Er wird nun f&uuml;r die weiteren Aufrufe von <b>CallCOMFunktion<\/b> in den Schleifen benutzt. Der <b>VTable-Offset<\/b> wird dabei aus den Enumerationskonstanten entnommen, wie <b>eJaroWinkler<\/b> oder <b>eRatcliff<\/b>. Im dritten Parameter erwartet die Funktion die Angabe des Datentyps der R&uuml;ckgabe der Methode. <b>CR_INTEGER<\/b> bedeutet, dass beide Methoden jeweils einen Integer-Wert zur&uuml;ckgeben. Der n&auml;chste Parameter bezeichnet die Aufrufkonvention in der DLL. Das ist bei allen ActiveX-Dateien die Konvention <b>StdCall<\/b>, w&auml;hrend etwa C-DLLs oft die Konvention <b>CDECL<\/b> benutzen, mit der VBA nichts anfangen kann.<\/p>\n<p>Dann folgen alle Parameter, die Sie den Methoden &uuml;bergeben m&ouml;chten. Die beiden Vergleich-Strings <b>S1<\/b> und <b>S2<\/b> kommen hier in Gestalt der Zeigervariablen <b>pS1<\/b> und <b>pS2<\/b> herein, die schon oben &uuml;ber <b>StrPtr<\/b> belegt wurden. <b>pReserved<\/b> ist ein Wert f&uuml;r die optionalen Steuerparameter der Methoden, der auf <b>0<\/b> beziehungsweise <b>False<\/b> gesetzt ist. Und im letzten Parameter &uuml;bergeben wir einen Zeiger auf die Integer-Variable <b>n<\/b>, die schlie&szlig;lich das Ergebnis des Methodenaufrufs entgegennimmt.<\/p>\n<p>Fassen wir zusammen: Das Klassenobjekt wird registrierungslos instanziiert. Die Methoden der Klasse werden ohne Kenntnis der Bibliothek &uuml;ber <b>VTable-Offsets<\/b> angesprochen. Damit k&ouml;nnen selbst Objekte benutzt werden, zu denen es gar keine T<b>ype Library<\/b> gibt! Ein Beispiel, welches diesen Umstand demonstriert, folgt im Absatz zur Windows-Fortschrittsanzeige. Wie verh&auml;lt es sich mit der Performance dieser Test-Routine Wir messen folgende Werte:<\/p>\n<pre>3,101563 s     92\r\n2,378906 s     91<\/pre>\n<p>Die R&uuml;ckgabewerte sind korrekt und die Ausf&uuml;hrungsdauer ist etwas besser, als beim der Routine mit <b>Late Binding<\/b>. Erstaunlich ist indessen, dass dieses Mal <b>Ratcliff <\/b>besser abschneidet, als <b>JaroWinkler<\/b>. Das aber ist nicht den Methoden der DLL selbst geschuldet, sondern liegt an unserer Funktion <b>CallCOMFunction<\/b>. Denn die muss bei jedem Aufruf erst alle Parameter in die Parameter-Arrays &uuml;bertragen und dabei jeweils den Datentyp ermitteln. Da <b>Ratcliff<\/b> einen Parameter weniger erfordert, l&auml;uft die Array-Schleife einmal seltener ab, was sich in der gesteigerten Performance wiederspiegelt. <b>CallCOMFunction<\/b> ist also desto schneller, je weniger Parameter an eine Objektmethode geleitet werden m&uuml;ssen.<\/p>\n<h2>Beispiel Windows-Fortschrittsanzeige<\/h2>\n<p>Sie kennen den Dialog zur Fortschrittsanzeige, der etwa beim Kopieren gro&szlig;er Dateimengen auf den Plan tritt. Eigentlich w&auml;re es schick, wenn Sie diesen auch in ihren eigenen Projekten verwenden k&ouml;nnten, um l&auml;nger andauernde Vorg&auml;nge zu verdeutlichen. Denn der Windows-Dialog hat gegen&uuml;ber Fortschrittsanzeigen im Eigenbau einen Vorteil: Sein Style ist unter jedem Betriebssystem anders. Unter <b>Windows XP<\/b> sieht er etwa anders aus, als unter <b>Windows 10<\/b>. Dennoch ist in beiden F&auml;llen daf&uuml;r das gleiche <b>Shell<\/b>-Objekt verantwortlich. Der Dialog bleibt damit immer modern. Au&szlig;erdem passt er sich automatisch der Sprache des Betriebssystems an.<\/p>\n<p>Dieser Dialog verbirgt sich in der Datei <b>shell32.dll<\/b>, auf die wir eingangs bereits einen Verweis setzten. Dennoch werden Sie in der Type Library von <b>shell32<\/b> vergeblich nach diesem Dialogobjekt suchen. Es ist dort n&auml;mlich nicht integriert worden. Warum Microsoft diesen Dialog versteckt hat, ist unklar. Auch der Registry werden Sie in der Regel nicht f&uuml;ndig, falls nicht Drittprogramme diese modifizierten. Eine Dokumentation zu diesem Dialog gibt es aber auf dem <b>MSDN<\/b> unter dem Stichwort <b>IProgressDialog<\/b>. Dort sind all seine Methoden beschrieben und auch der Ort angegeben, unter dem Sie die technische Beschreibung einsehen k&ouml;nnen, n&auml;mlich in der C-Header-Datei <b>Shlobj.h<\/b>. Die finden Sie ebenfalls im Netz als Bestandteil des <b>Windows-SDK<\/b>. Was wir f&uuml;r die Instanziierung dieses Klassenobjekts suchen, ist die <b>CLSID<\/b> dieser Klasse (<b>CLSID_ProgressDialog<\/b>). Leider steht die nicht in besagter <b>Header<\/b>-Datei, sondern in einer ihren <b>Include<\/b>-Dateien, der <b>shlguid.h<\/b>. In dieser Datei sind praktisch alle <b>GUIDs<\/b> zu allen <b>Shell<\/b>-Objekten und <b>Interfaces<\/b> definiert. Sie finden dort die <b>GUID<\/b> <b>{F8383852-FCD3-11D1-A6B9-006097DF5BD4}<\/b> f&uuml;r die <b>CoClass<\/b> <b>ProgressDialog<\/b>.<\/p>\n<p>Damit k&ouml;nnen Sie das Objekt &uuml;ber unsere Routine <b>GetCOMInstance<\/b> erzeugen. Doch bevor Sie sich selbst die M&uuml;he machen, verwenden Sie lieber das Klassenmodul <b>clsProgressDlg<\/b> in der Demodatenbank. Wir wenden uns zun&auml;chst dem praktischen Einsatz dieser Klasse zu. Die Prozedur in Listing 5 demonstriert die Anwendung.<\/p>\n<pre><span style=\"color:blue;\">Sub <\/span>TestDialog()\r\n     <span style=\"color:blue;\">Dim <\/span>C<span style=\"color:blue;\"> As <\/span>clsProgressDlg\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> C = <span style=\"color:blue;\">New<\/span> clsProgressDlg\r\n     <span style=\"color:blue;\">If <\/span>C.IsReady<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">With<\/span> C\r\n             .SetTitle \"AiU: Windows-Fortschrittsdialog\"\r\n             .SetCancelMsg \"Abbrechen, Abbrechen, ...\"\r\n             .StartProgressDialog Application.hWndAccessApp, _\r\n                                  PROGDLG_NORMAL Or PROGDLG_NOTIME\r\n             DoEvents\r\n             .SetLine 0, \"Windows enth&auml;lt einen programmierbaren Fortschrittsdialog\"\r\n             For i = 20 To 75 Step 5\r\n                 .SetProgress i, 100\r\n                 DoEvents\r\n                 Sleep 500\r\n             <span style=\"color:blue;\">Next<\/span> i\r\n             .SetLine 0, \"Jetzt geht''''s aber nicht weiter und Sie k&ouml;nnen abbrechen...!\"\r\n             .SetProgress 80, 100\r\n             For i = 0 To 200\r\n                 DoEvents\r\n                 <span style=\"color:blue;\">If <\/span>.HasUserCancelled<span style=\"color:blue;\"> Then<\/span>\r\n                     <span style=\"color:blue;\">MsgBox<\/span> \"Dialog abgebrochen\"\r\n                     Sleep 1500\r\n                     <span style=\"color:blue;\">Exit For<\/span>\r\n                 <span style=\"color:blue;\">End If<\/span>\r\n                 Sleep 50\r\n             <span style=\"color:blue;\">Next<\/span> i\r\n             .StopProgressDialog\r\n         End <span style=\"color:blue;\">With<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> C = Nothing\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Diese Test-Routine l&auml;sst die Windows-Fortschrittsanzeige erscheinen und wieder verschwinden<\/span><\/b><\/p>\n<p><b>IsReady<\/b> fragt erst ab, ob die Instanziierung, welche im Ereignis <b>Initialize<\/b> der Klasse automatisch geschieht, erfolgreich war. Der <b>With<\/b>-Block auf das in der Objektvariablen <b>C<\/b> gespeicherte Objekt <b>clsProgressDialog<\/b> macht die folgenden Schritte &uuml;bersichtlicher. Hier setzt die Routine einige Eigenschaften des Dialogs. <b>SetTitle<\/b> ist die &uuml;berschrift in der Titelzeile. <b>SetCancelMsg<\/b> ist ein Text, der erscheinen wird, wenn der Dialog durch Klick auf die entsprechende Schaltfl&auml;che abgebrochen wird.<\/p>\n<p>Danach kann er an das eigentliche Erzeugen und Anzeigen des Dialogs gehen, was die Methode <b>StartProgressDialog<\/b> erreicht. Ihr &uuml;bergeben Sie ein Fenster-<b>Handle<\/b> von Access als Eigner des Dialogs und einige Konstanten, die das Erscheinungsbild des Dialogs steuern. Er ist darin n&auml;mlich ziemlich variabel. (Zur Bedeutung der Konstanten konsultieren Sie die <b>MSDN<\/b> zum <b>Interface IProgressDialog<\/b>.) Der Dialog erscheint nun nach kurzer Bedenkzeit auf dem Bildschirm. Warum das unabh&auml;ngig von der Performance des Rechners immer zwischen ein und zwei Sekunden dauert, k&ouml;nnen wir Ihnen leider nicht sagen. Einmal erschienen k&ouml;nnen Sie jederzeit mit <b>SetLine<\/b> die Headline (<b>0<\/b>) im Dialog steuern. &uuml;bergeben Sie <b>SetLine<\/b> im ersten Parameter <b>1<\/b>, so zeigt sich im Dialog eine weitere Zeile darunter. Dieses Feature kommt hier nicht zum Einsatz. Den Fortschrittsbalken setzen Sie mit <b>SetProgress<\/b>, wobei hier ein Integer-Wert f&uuml;r den aktuellen Fortschritt &uuml;bergeben wird und zus&auml;tzlich der Maximalwert (<b>100<\/b>). Im Beispiel geschieht das in einer Schleife, die den Z&auml;hler jeweils um <b>5<\/b> erh&ouml;ht. Damit das nicht rasend schnell passiert ist per <b>API<\/b> <b>Sleep<\/b> eine Pause von <b>500 ms <\/b>eingebaut.<\/p>\n<p>Der Dialog schlie&szlig;t sich erst beim Absetzen von <b>StopProgressDialog<\/b>. Da das Objekt keinerlei Ereignisse auswirft, k&ouml;nnen Sie auf die Aktion <b>Abbrechen<\/b> auch nicht direkt reagieren. Daf&uuml;r hat das Objekt eine Eigenschaft <b>HasUserCancelled<\/b> eingebaut, die Sie jederzeit abfragen k&ouml;nnen. Das geschieht hier in einer Schleife, nachdem der Fortschritt <b>80<\/b> Prozent erreicht hat. Wurde <b>Abbrechen<\/b> bet&auml;tigt, gibt die Eigenschaft <b>True<\/b> aus und die Routine verzweigt in Zeilen, die anhand einer Meldung &uuml;ber den Abbruch informiert und die Schleife au&szlig;erdem verl&auml;sst. Der Dialog schlie&szlig;t sich erst damit, weil es nun zur Zeile mit <b>StopProgressDialog<\/b> kommt.<\/p>\n<p>Nicht alle m&ouml;glichen Methoden des Dialogs sind in diesem Beispiel untergebracht. Der Dialog kann etwa auch Animationen abspielen, wie diese bekannten fliegenden Dokumente, doch der Aufwand f&uuml;r die Realisierung dessen w&auml;re f&uuml;r diesen Beitrag, in dem es schlie&szlig;lich nicht zuv&ouml;rderst um den Fortschrittsdialog geht, zu hoch.<\/p>\n<p>Wie die Angelegenheit schlie&szlig;lich aussieht, zeigen die folgenden Abbildungen. Bild 1 ist ein Screenshot nach dem ersten Erscheinen des Dialogs. Bild 2 zeigt diesen, nachdem der Fortschritt <b>80<\/b> Prozent erreicht hat. Klicken Sie schon davor auf <b>Abbrechen<\/b>, so zeigt sich der Dialog wie in Bild 3, und zwar f&uuml;r die im Code eingestellte Zeit von 1,5 Sekunden. Ansonsten poppt die <b>Msgbox<\/b> wie in Bild 4 auf. Da sie modal ist, h&auml;lt der Code an dieser Stelle an. Erst mit Klick auf <b>OK<\/b> geht es weiter und der <b>ProgressDialog<\/b> im Hintergrund schlie&szlig;t sich. &uuml;brigens kann es sein, dass die <b>MsgBox <\/b>auf den ersten Blick nicht in Erscheinung tritt. Das liegt dann daran, dass Sie sie sich hinter dem Fortschritts-Dialog befindet. Verschieben Sie diesen dann, um an sie zu gelangen. Der Dialog ist <b>modal<\/b> in Bezug auf das Elternfenster, also Access. Kein Access-Fenster kann ihn damit &uuml;berdecken, wohl aber andere Anwendungen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2018_04\/ProgressDlg1.png\" alt=\"So &ouml;ffnet sich der VBA-gesteuerte Windows-ProgressDialog\" width=\"425\" height=\"192,6109\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: So &ouml;ffnet sich der VBA-gesteuerte Windows-ProgressDialog<\/span><\/b><\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2018_04\/ProgressDlg2.png\" alt=\"Den Text im Dialog k&ouml;nnen Sie jederzeit neu setzen\" width=\"425\" height=\"192,6109\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Den Text im Dialog k&ouml;nnen Sie jederzeit neu setzen<\/span><\/b><\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2018_04\/ProgressDlg3.png\" alt=\"Angepasster Text nach Klick auf den Abbrechen-Button\" width=\"425\" height=\"192,6109\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Angepasster Text nach Klick auf den Abbrechen-Button<\/span><\/b><\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2018_04\/ProgressDlg4.png\" alt=\"Die Abbrechen-Aktion kann von VBA abgefangen werden\" width=\"425\" height=\"325,8333\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Die Abbrechen-Aktion kann von VBA abgefangen werden<\/span><\/b><\/p>\n<p>Das Klassenmodul ist in den Grundz&uuml;gen schnell beschrieben. Bereits im automatisch ausgel&ouml;sten Ereignis <b>Initialize<\/b> wird das Objekt angelegt:<\/p>\n<pre><span style=\"color:blue;\">Set<\/span> oDialog = GetCOMInstance(\"shell32.dll\", cCLSID)<\/pre>\n<p><b>cCLSID<\/b> ist im Modulkopf deklariert und enth&auml;lt die <b>CLSID<\/b> f&uuml;r die <b>CoClass<\/b> <b>ProgressDialog<\/b>. <b>oDialog<\/b> ist modulweit g&uuml;ltig, ebenso, wie <b>lObject<\/b>. Das ist der Zeiger auf das <b>Interface<\/b> <b>IProgressDialog<\/b>, der nun &auml;hnlich &uuml;ber <b>QueryInterface<\/b> ermittelt wird, wie dies bei der Klasse <b>CSimilarity<\/b> bereits erl&auml;utert wurde.<\/p>\n<p>Die Methoden der Klasse <b>clsProgressDlg<\/b> werden dann direkt an die des Objekts <b>oDialog<\/b> weitergeleitet. Dazu wird wieder die Hilfsfunktion <b>CallCOMFunction<\/b> bem&uuml;ht. F&uuml;r die Funktion <b>SetTitle<\/b> etwa sieht die Prozedur so aus:<\/p>\n<pre><span style=\"color:blue;\">Sub <\/span>SetTitle(ByVal sTitle<span style=\"color:blue;\"> As String<\/span>)\r\n     varRtn = CallCOMFunction(lObject, 5 * 4&, _\r\n              CR_LONG, CC_STDCALL, StrPtr(sTitle))\r\n     fuIsError \"SetTitle\"\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Hier wird die f&uuml;nfte Methode der <b>VTable<\/b> des Interfaces aufgerufen. Man h&auml;tte auch hier eine <b>Enumeration<\/b> wie bei <b>CSimilarity<\/b> einsetzen k&ouml;nnen, die Prozeduren im Klassenmodul sind jedoch exakt in der Reihenfolge eingebaut, wie die Methoden in <b>IProgressDialog<\/b>, so dass sich das er&uuml;brigt. Der <b>VTable<\/b>-Offset wird bei jeder n&auml;chsten Prozedur einfach um eins erh&ouml;ht.<\/p>\n<p>Sie sehen, dass hier wieder der Titel-String <b>sTitle<\/b> f&uuml;r die &uuml;bergabe an die Hilfsfunktion mit <b>StrPtr<\/b> behandelt wird. Ein abschlie&szlig;ender Parameter f&uuml;r die R&uuml;ckgabe fehlt hier, weil die Methode ja auch kein Ergebnis zur&uuml;ckgibt. Die R&uuml;ckgabe von <b>CallCOMFunction<\/b> aber wird ausgewertet, indem Sie zun&auml;chst in die Modulvariable <b>varRtn<\/b> gespeichert wird. Und die nimmt sich die Prozedur <b>fuIsError<\/b> im Modul vor:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>fuIsError(sMethod<span style=\"color:blue;\"> As String<\/span>)\r\n     <span style=\"color:blue;\">If <\/span>varRtn &lt;&gt; 0&<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">Debug.Print<\/span> \"Error in \" & _\r\n                      sMethod & \": \" & Hex(varRtn)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Ist der R&uuml;ckgabewert ungleich Null, so bedeutet das einen Fehler beim Aufruf von <b>DispCallFunc<\/b>. Der wird dann in das VBA-Direktfenster geschrieben und k&ouml;nnte so aussehen:<\/p>\n<pre>Error in SetTitle: 80020010<\/pre>\n<p>Unter dieser hexadezimalen Fehlernummer k&ouml;nnen Sie auf den <b>MSDN<\/b>-Seiten nachsehen, um welchen Fehler es sich im Text handelt: <b>Aufgerufener ist ung&uuml;ltig<\/b> oder <b>Invalid Callee<\/b>. Das bedeutet, dass einer der Parameter nicht korrekt ist oder nicht korrekt &uuml;bergeben wurde, etwa, weil der String nicht als Zeiger per <b>StrPtr<\/b> eingesetzt wurde.<\/p>\n<p>Alle weiteren Prozeduren im Klassenmodul rufen diese Fehlerbehandlung auf. Das ist beim Entwickeln hilfreich, weil sich garantiert Fehler bei der &uuml;bergabe der Parameter oder der <b>VTable-Offsets<\/b> einschleichen werden. Bevor die Klasse <b>clsProgressDlg<\/b> anstandslos funktionierte, waren auch bei uns einige Irrt&uuml;mer zu bereinigen&#8230;<\/p>\n<p>Wie &uuml;berhaupt kamen wir auf die <b>VTable<\/b>-Reihenfolge von <b>IProgressDialog<\/b> Auch hier war es wieder n&ouml;tig, die <b>Header<\/b>-Dateien des <b>Windows-SDK<\/b> zurate zu ziehen. Die Reihenfolge ergibt sich aus der Definition des <b>Interfaces<\/b> <b>IProgressDialog<\/b> in <b>shlobj.h<\/b>. Ein bisschen Kenntnis in der <b>C<\/b>-Programmierung kann da nicht schaden.<\/p>\n<p>Zusammengefasst k&ouml;nnen Sie mit dem Modul <b>mdlCOMLoaderASM<\/b> also nicht nur Objekte erfassen, zu denen es eine Bibliothek gibt, sondern auch solche, die Microsoft nur auf seinen Entwicklerseiten dokumentiert hat.<\/p>\n<h2>Was nicht geht!<\/h2>\n<p>Wenn Sie nur die <b>ProgID<\/b> eines Objekts kennen, so setzt das voraus, dass die ActiveX-DLL eine <b>Type Library<\/b> enth&auml;lt. Das ist zwar meist der Fall, darf aber nicht vorausgesetzt werden. T<b>ype Libraries<\/b> werden &uuml;brigens als Ressource in ActiveX-Dateien untergebracht.<\/p>\n<p>Manchmal, jedoch selten, schl&auml;gt die Instanziierung mit <b>GetCOMInstance<\/b> auch fehl. Das liegt dann meist an <b>CoClasses<\/b>, die ein eigent&uuml;mliches Gemisch von Interfaces aufweisen. In diesem Fall wurde allerdings auch VBA-<b>CreateObject<\/b> scheitern.<\/p>\n<p>Und was &#8211; leider! &#8211; nicht oder noch nicht geht, ist die Anzeige von <b>ActiveX-Controls<\/b>. Sie k&ouml;nnen zwar etwa ein <b>Treeview<\/b> so instanziieren:<\/p>\n<pre><span style=\"color:blue;\">Set<\/span> oTree = GetCOMInstance(\"mscomctl.ocx\", \"Treeview\")<\/pre>\n<p>Dabei k&ouml;nnten Sie eine Version der <b>mscomctl<\/b> verwenden, die Sie in das Datenbankverzeichnis legen, um so dem Versions-Kuddelmuddel vorzubeugen, das Microsoft mit diesen <b>Common Controls<\/b> zuweilen veranstaltet. Doch mit dem erzeugten Objekt <b>oTree<\/b> k&ouml;nnen Sie nicht viel anfangen. Das <b>Treeview<\/b>-Objekt anschlie&szlig;end in einem Formular anzuzeigen, ist mir bisher nur unter Zuhilfenahme der Bibliothek <b>oleexp<\/b> gelungen und auch dann nur, wenn das <b>Treeview<\/b> auf einem <b>MSForms-Frame<\/b> aufgebaut wird.<\/p>\n<p>Das f&uuml;r <b>ActiveX-Controls<\/b> eigentlich zust&auml;ndige Steuerelement <b>CustomControl<\/b> von Access verweigert sich jeglicher Manipulation, weil es zur Laufzeit kein Fenster-Handle besitzt. Vielleicht finden wir hierf&uuml;r noch irgendwann eine L&ouml;sung. Das w&auml;re ein gro&szlig;er Fortschritt, weil Sie dann ActiveX-Steuerelemente verwenden k&ouml;nnten, ohne diese zuvor mit Administratorrechten registriert haben zu m&uuml;ssen.<\/p>\n<h2>Hilfs-Tool Typelib Browser<\/h2>\n<p>F&uuml;r die Arbeit mit dem Modul <b>mdlCOMLoaderASM<\/b> ben&ouml;tigen Sie einige Informationen &uuml;ber die Objekte einer ActiveX-Datei, die der Objektkatalog von VBA nicht ausgibt. Das betrifft vor allem die <b>GUIDs<\/b> zu <b>CoClasses<\/b> und deren <b>Interfaces<\/b>. Au&szlig;erdem m&uuml;ssen Sie f&uuml;r die Funktion <b>CallCOMFunction<\/b> etwas &uuml;ber die <b>VTable<\/b>, die Reihenfolge der Methoden, eines Objekts wissen. <\/p>\n<p>Daf&uuml;r gibt es mehrere Tools. Wir entschieden uns f&uuml;r das coole Programm <b>Typelib Browser<\/b> von <b>Jos&eacute; Roca<\/b>, der es eigentlich f&uuml;r <b>PowerBasic<\/b>-Entwickler geschaffen hat. Im Verzeichnis <b>TLBBrowser_JoseRoca<\/b> im Download finden Sie die Anwendung <b>TLB_205.exe<\/b>.<\/p>\n<p>Starten Sie diese und laden Sie &uuml;ber das Men&uuml; <b>File|Open <\/b>unsere ActiveX-Datei <b>mSimilarity.dll<\/b>. Das Tool liest nun alle Informationen &uuml;ber die Objekte aus. Sie k&ouml;nnen &uuml;brigens auch die Datei <b>mSimilarity.tlb<\/b> nehmen, die &uuml;ber Extraktion aus der ActiveX-Datei mit einem Ressourcen-Editor gewonnen wurde. Die Anwendung zeigt nun die Grundinformationen an, wie in Bild 5. Die <b>CLSID<\/b> der Klasse <b>CSimilarity<\/b> wird ebenso ausgegeben, wie die <b>GUID<\/b> des zugrundeliegenden Interfaces <b>_CSimilarity<\/b>, die Sie sp&auml;ter f&uuml;r <b>QueryInterface<\/b>-Methode brauchen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2018_04\/TLB1.png\" alt=\"Der TypeLib Browser von Jos&eacute; Roca liefert alle ben&ouml;tigten Daten f&uuml;r den Einsatz unserer registrierungslosen VBA-Routinen.\" width=\"700\" height=\"354,4586\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Der TypeLib Browser von Jos&eacute; Roca liefert alle ben&ouml;tigten Daten f&uuml;r den Einsatz unserer registrierungslosen VBA-Routinen.<\/span><\/b><\/p>\n<p>Klicken Sie nun im Men&uuml; auf <b>Code|Offsets<\/b>. Die Anzeige wechselt nun in die Darstellung der <b>VTable-Offsets <\/b>zu den Methoden, wie in Bild 6. Hier haben Sie alle Methoden so im Blick, wie Sie sie f&uuml;r die &uuml;bergabe an die Funktion <b>CallCOMFunction<\/b> ben&ouml;tigen. Sie k&ouml;nnen die Anzeige auch als Text abspeichern, indem Sie auf den Men&uuml;eintrag <b>File|Save As<\/b> gehen und damit eine <b>PowerBasic-Include<\/b>-Datei erzeugen. Ebenso k&ouml;nnen Sie Text in der Anzeige markieren und per <b>STRG-C<\/b> in die Zwischenablage verfrachten. In einem VBA-Modul f&uuml;gen Sie ihn wieder ein und machen aus der Liste einfach eine <b>Enumeration<\/b>, indem Sie die die Zahlenwerte entfernen und mit <b>Suchen und Ersetzen<\/b> den String <b>%VTO_mSimilarity_CSimilarity<\/b> etwa durch <b>e<\/b> ersetzen. Das Ganze fassen dann Sie in <b>Enum eSimilarity<\/b> und <b>End Enum<\/b> ein.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2018_04\/TLB2.png\" alt=\"Au&szlig;er den GUIDs f&uuml;r die Klassenobjekte m&uuml;ssen Sie die Reihenfolge ihrer Methoden in der sogenannten VTable kennen.\" width=\"700\" height=\"354,4586\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Au&szlig;er den GUIDs f&uuml;r die Klassenobjekte m&uuml;ssen Sie die Reihenfolge ihrer Methoden in der sogenannten VTable kennen.<\/span><\/b><\/p>\n<p>Wir entschieden uns f&uuml;r dieses Tool, weil <b>PowerBasic<\/b> VBA am n&auml;chsten kommt und <b>C++<\/b>-Tools weniger geeignet sind. Wenn Sie die Anwendung weiter erforschen, so werden Sie erstaunt sein, was sich unter der Haube von <b>COM<\/b>-Objekten noch so alles finden l&auml;sst. VBA versteckt all dies, was f&uuml;r den normalen Entwickler sicher n&uuml;tzlich ist, dem Nerd aber zuviel vorenth&auml;lt. Sie k&ouml;nnen mit dem Tool auch alle weiteren im System registrieren <b>Type Libraries<\/b> auslesen, wenn Sie im Toolbar auf den Button <b>TypeLibs<\/b> klicken. Das f&uuml;hrt zur Anzeige einer Liste aller Bibliotheken. Ein Doppelklick auf einen Eintrag &ouml;ffnet dann eine Datei f&uuml;r die weitere Inspektion.<\/p>\n<h2>Vorsicht ist geboten!<\/h2>\n<p>Mit den im Beitrag vorliegenden Routinen haben wir es mit Code zu tun, den so auch Hacker f&uuml;r allerlei gemeine Dinge nutzen k&ouml;nnten. Tats&auml;chlich l&auml;sst sich &uuml;ber die Prozedur <b>CallPointerASM<\/b> auch ein Virus programmieren, so man wei&szlig;, wie es geht. Microsoft hatte das wohl im Sinn, als es etwa Byte-Arrays als ausf&uuml;hrbaren RAM-Speicher mit <b>Office 2010<\/b> sperrte. Die Demo funktioniert bisher jedoch unter allen Access-Versionen bis <b>Access 2016<\/b> und die Routinen sind durchaus auch in kommerziellen Datenbankprojekten im Einsatz. Es ist jedoch nicht sichergestellt, dass mit zuk&uuml;nftigen Access-Versionen noch alles funktioniert, falls Microsoft sich weiterer Versch&auml;rfungen der Makro-Viren-Sicherheit annimmt. Und alle Kombinationen von Betriebssystemen mit Access-Versionen haben wir auch noch nicht getestet.<\/p>\n<h3>Downloads zu diesem Beitrag<\/h3>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>mSimilarity.dll<\/p>\n<p>mSimilarity.tlb<\/p>\n<p>progressdialog.h<\/p>\n<p>register_mSimilarity.bat<\/p>\n<p>Regless.accdb<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/655D9475-9981-4370-A099-6C54DA5B56CE\/aiu_1143.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>ActiveX-Komponenten m&uuml;ssen, so sie unter VBA ansprechbar sein sollen, im System registriert werden. Dies erfordert administrative Rechte unter Windows, weshalb viele Administratoren solche externen Komponenten leider gar nicht gern sehen. Dass Sie manche ActiveX-Dateien jedoch auch ganz ohne Registrierung verwenden k&ouml;nnen, zeigen die Techniken dieses Beitrags.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[662018,66042018,44000027],"tags":[],"class_list":["post-55001143","post","type-post","status-publish","format-standard","hentry","category-662018","category-66042018","category-Loesungen"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v20.9 (Yoast SEO v27.5) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>ActiveX und COM ohne Registrierung verwenden - Access im Unternehmen<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"ActiveX und COM ohne Registrierung verwenden\" \/>\n<meta property=\"og:description\" content=\"ActiveX-Komponenten m&uuml;ssen, so sie unter VBA ansprechbar sein sollen, im System registriert werden. Dies erfordert administrative Rechte unter Windows, weshalb viele Administratoren solche externen Komponenten leider gar nicht gern sehen. Dass Sie manche ActiveX-Dateien jedoch auch ganz ohne Registrierung verwenden k&ouml;nnen, zeigen die Techniken dieses Beitrags.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/\" \/>\n<meta property=\"og:site_name\" content=\"Access im Unternehmen\" \/>\n<meta property=\"article:published_time\" content=\"2020-05-13T21:11:47+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/vg06.met.vgwort.de\/na\/9f209b3e51f346d39ade2d8c334daedc\" \/>\n<meta name=\"author\" content=\"Andr\u00e9 Minhorst\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andr\u00e9 Minhorst\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"37\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/\"},\"author\":{\"name\":\"Andr\u00e9 Minhorst\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\"},\"headline\":\"ActiveX und COM ohne Registrierung verwenden\",\"datePublished\":\"2020-05-13T21:11:47+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/\"},\"wordCount\":6491,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg06.met.vgwort.de\\\/na\\\/9f209b3e51f346d39ade2d8c334daedc\",\"articleSection\":[\"2018\",\"4\\\/2018\",\"L\u00f6sungen\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/\",\"name\":\"ActiveX und COM ohne Registrierung verwenden - Access im Unternehmen\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg06.met.vgwort.de\\\/na\\\/9f209b3e51f346d39ade2d8c334daedc\",\"datePublished\":\"2020-05-13T21:11:47+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#primaryimage\",\"url\":\"http:\\\/\\\/vg06.met.vgwort.de\\\/na\\\/9f209b3e51f346d39ade2d8c334daedc\",\"contentUrl\":\"http:\\\/\\\/vg06.met.vgwort.de\\\/na\\\/9f209b3e51f346d39ade2d8c334daedc\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/ActiveX_und_COM_ohne_Registrierung_verwenden\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"ActiveX und COM ohne Registrierung verwenden\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"name\":\"Access im Unternehmen\",\"description\":\"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access\",\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/access-im-unternehmen.de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\",\"name\":\"Andr\u00e9 Minhorst Verlag\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"contentUrl\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"width\":370,\"height\":111,\"caption\":\"Andr\u00e9 Minhorst Verlag\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\",\"name\":\"Andr\u00e9 Minhorst\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"caption\":\"Andr\u00e9 Minhorst\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"ActiveX und COM ohne Registrierung verwenden - Access im Unternehmen","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/","og_locale":"de_DE","og_type":"article","og_title":"ActiveX und COM ohne Registrierung verwenden","og_description":"ActiveX-Komponenten m&uuml;ssen, so sie unter VBA ansprechbar sein sollen, im System registriert werden. Dies erfordert administrative Rechte unter Windows, weshalb viele Administratoren solche externen Komponenten leider gar nicht gern sehen. Dass Sie manche ActiveX-Dateien jedoch auch ganz ohne Registrierung verwenden k&ouml;nnen, zeigen die Techniken dieses Beitrags.","og_url":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/","og_site_name":"Access im Unternehmen","article_published_time":"2020-05-13T21:11:47+00:00","og_image":[{"url":"http:\/\/vg06.met.vgwort.de\/na\/9f209b3e51f346d39ade2d8c334daedc","type":"","width":"","height":""}],"author":"Andr\u00e9 Minhorst","twitter_card":"summary_large_image","twitter_misc":{"Verfasst von":"Andr\u00e9 Minhorst","Gesch\u00e4tzte Lesezeit":"37\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#article","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/"},"author":{"name":"Andr\u00e9 Minhorst","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f"},"headline":"ActiveX und COM ohne Registrierung verwenden","datePublished":"2020-05-13T21:11:47+00:00","mainEntityOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/"},"wordCount":6491,"commentCount":0,"publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#primaryimage"},"thumbnailUrl":"http:\/\/vg06.met.vgwort.de\/na\/9f209b3e51f346d39ade2d8c334daedc","articleSection":["2018","4\/2018","L\u00f6sungen"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/","url":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/","name":"ActiveX und COM ohne Registrierung verwenden - Access im Unternehmen","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#primaryimage"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#primaryimage"},"thumbnailUrl":"http:\/\/vg06.met.vgwort.de\/na\/9f209b3e51f346d39ade2d8c334daedc","datePublished":"2020-05-13T21:11:47+00:00","breadcrumb":{"@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#primaryimage","url":"http:\/\/vg06.met.vgwort.de\/na\/9f209b3e51f346d39ade2d8c334daedc","contentUrl":"http:\/\/vg06.met.vgwort.de\/na\/9f209b3e51f346d39ade2d8c334daedc"},{"@type":"BreadcrumbList","@id":"https:\/\/access-im-unternehmen.de\/ActiveX_und_COM_ohne_Registrierung_verwenden\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/access-im-unternehmen.de\/"},{"@type":"ListItem","position":2,"name":"ActiveX und COM ohne Registrierung verwenden"}]},{"@type":"WebSite","@id":"https:\/\/access-im-unternehmen.de\/#website","url":"https:\/\/access-im-unternehmen.de\/","name":"Access im Unternehmen","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access","publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/access-im-unternehmen.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/access-im-unternehmen.de\/#organization","name":"Andr\u00e9 Minhorst Verlag","url":"https:\/\/access-im-unternehmen.de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/","url":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","contentUrl":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","width":370,"height":111,"caption":"Andr\u00e9 Minhorst Verlag"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f","name":"Andr\u00e9 Minhorst","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","caption":"Andr\u00e9 Minhorst"}}]}},"_links":{"self":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55001143","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/comments?post=55001143"}],"version-history":[{"count":0,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55001143\/revisions"}],"wp:attachment":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/media?parent=55001143"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/categories?post=55001143"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/tags?post=55001143"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}