Late Binding und schwache Performance durch GetIDsOfNames

Immer wieder sehe ich Entwickler, die über Late Binding COM Komponenten ansprechen. Das ist an sich nur zu unterstützen, denn DISPIDs können sich schnell mal ändern, wenn sich ein Interface ändert. Gerade bei den rasant an Verbreitung zunehmenden .NET Komponenten, die man mal schnell in die eigene Anwendung per COM einbindet, kann das Binding über die Interfaces und DISPIDs schnell zum Frust werden. Gleiches gilt für Office Komponenten, bei denen man sich nicht zwingend an ein Interface binden will. Zudem ist es der von Microsoft empfohlene Weg die Office-Automation zu benutzen (siehe auch hier).

OK! Also man nimmt Late Binding und verwendet z.B. die aktuellen Wrapper, die netterweise von der ATL zur Verfügung gestellt werden,  z.B. die netten Funktionen aus der CComDispatchDriver Klasse. GetPropertyByName, PutPropertyByName und auch die Funktionen Invoke0, Invoke1 und InvokeN haben entsprechende Überladungen, die die Verwendung von Funktions-/Eigenschaftsnamen direkt erlauben.

Der Nachteil liegt nicht gleich auf der Hand. Immer wenn solch eine Funktion aufgerufen wird, wird nicht durch der IDispatch::Involke ausgeführt, sondern auch ein Aufruf von IDispatch::GetIDsOfNames. Bei einem Out-Of-Process-Server kann dieser Roundtrip einiges an Performance kosten. Dabei ist es so einfach es besser zu machen.

Die Doku von IDispatch::GetIDsOfNames sagt folgendes:  

The member and parameter DISPIDs must remain constant for the lifetime of the object. This allows a client to obtain the DISPIDs once, and cache them for later use.

Und wer den oben genannten KB-Artikel aufmerksam gelesen hat, der hat auch was von DISPID-Caching mitbekommen.

Ich benutze gerne die die Wrapper, die in der MFC automatisch erzeugt werden, wenn man einen Type-Library über den Class View importiert. Hier werden die Funktionen auch in eine nette kleine COleDispatchDriver-Klasse verpackt und man bekommt netterweise ein gutes Exception Handling den Fehlerfall geliefert. Leider werden hier aber auch wieder nur DISPIDs verwendet.
Aber mit einem kleinen Trick, kann man diese Klassen genial einfach für Late Binding nutzen. Ich gehe wie folgt vor:

  • Ich importiere die Type-Library (tlb) mit dem Visual Studio Class View.
  • D.h. ich habe jetzt alle Wrapper mit DISPIDs, Was ich eigentlich für Late Binding vermeiden will.
  • Jetzt passe ich einfach die einzelnen Wrapper Funktionen in der folgenden Art und Weise an, ich ersetze die DISPID durch eine statische Variable:
void CMyWrapper::DoSomething(){
  static CDispIdHolder dispid(m_lpDispatch,L"DoSomething");
  InvokeHelper(dispid, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
  • Die kleine Klasse die ich hier verwende macht nun den Rest und besorgt die DISPID und cached sie damit weil die Variable statisch definiert ist:
class CDispIdHolder
{
public:
 CDispIdHolder(IDispatch *pDispatch,LPCOLESTR pName)
  : m_dispid(DISPID_UNKNOWN)
  {
   HRESULT hr = pDispatch->GetIDsOfNames(
         IID_NULL,
         &const_cast<LPOLESTR>(pName),
         1,
         LOCALE_SYSTEM_DEFAULT,
         &m_dispid);
   if (FAILED(hr))
    AfxThrowOleException(hr);
  }
  operator DISPID() { return m_dispid; }
private:
 DISPID m_dispid;
};

Die Exception, die man verwendet ist natürlich Implementierungsfrage.

❗ Der Performance Gain ist zum Teil beträchtlich, besonders wnen bestimmte Funktionen sehr oft aufgerufen werden müssen!

Anmerkung (für alle die es ganz genau nehmen):
Wenn man die Doku genau liest, heißt es natürlich hier, dass die Implementierung nur für die Lebenszeit des Objektes konstante DISPIDs garantiert. Wenn man allerdings bei Early Binding schon DISPIDs als konstant annimmt, ist meine Methode für Late Binding sicherlich vertretbar.

Vista: Wie man den Kontextmenübefehl „Eingabeaufforderung hier öffnen“ auf die Powershell umbiegen kann

Torsten Schröder hat mir einen netten Kommentar in meinem letzten Blog-Artikel Vista: Wie man den Kontextmenübefehl “Eingabeaufforderung hier öffnen” umbiegen kann gegeben. Er meinte (Zitat):
„noch einen Tick besser fänd ich es, wenn man es Richtung Powershell umbiegt!“

Nun das gefällt mir auch und es ist auch nicht schwer. Mit dem folgenden kleinen Hack in der Registry kann man auch diesen erweiterten Kontextmenübefehl auch die Powershell umbiegen.

Einfach in der Registry unter HKEY_CLASSES_ROOT\Directory\shell\cmd\command
den Eintrag: cmd.exe /s /k pushd „%V“
z.B. in „C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe“ -noexit -command „set-location ‚%V'“ tauschen.

Damit lässt sich nun auch für mich das erweiterte Kontextmenü unter Vista perfekt nutzen:

  • 4NT integriert sich mit eigenen Kontextmenü-Befehlen.
  • und die Powershell ist nun über das erweiterte Kontextmenü des Vista-Explorers durch das Drücken der Umschalt-Taste extrem einfach zu erreichen.

Anmerkung: Wer den Faden verloren hat, alles beginnt mit diesem Tipp:
Zusätzliche Befehle im Explorer-Kontextmenü von Vista  😉

Vista: Wie man den Kontextmenübefehl „Eingabeaufforderung hier öffnen“ umbiegen kann

In meinem Artikel Zusätzliche Befehle im Explorer-Kontextmenü von Vista habe ich ja erwähnt, dass der Menüpunkt Eingabeaufforderung hier öffnen immer CMD.EXE verwendet und nicht den Befehlszeileninterpreter, der durch COMSPEC definiert ist.
Aber mit einem kleinen Hack in der Registry kann man auch diesen Befehl auf den eigenen Befehlszeileninterpreter umbiegen.

Einfach in der Registry unter HKEY_CLASSES_ROOT\Directory\shell\cmd\command
den Eintrag: cmd.exe /s /k pushd „%V“
z.B. in „C:\Program Files\4NT\4NT.EXE“ /s /k pushd „%V“ tauschen.

Anmerkung: Da 4NT normalerweise auch seine eigenen Einträge in den Kontextmenüs des Explorers macht, kann man es natürlich auch als Vorteil ansehen, wenn man bei Bedarf den originalen Befehlszeileninterpreter starten kann. 🙂

Alle reden von Sichertheit in der IT-Branche… Alle?

Bei einem Kunden muss ich aktuell einem Problem auf den Grund gehen, das irgendwie mit Locks in den tiefsten Tiefen des SQL-Server 2005 zu tun hat. Aus diesem Grund habe ich Einblick in den originalen – doch etwas größeren – Datenbestand erhalten. Besagter Kunde ist in seiner Branche ziemlich populär. Es werden in dieser Datenbank 750.000 Kunden verwaltet. Alles aktive Kunden eines bestimmten Produkts einer kleinen geographischen Zone in Deutschland. Qualitativ wirklich aktuelles Material, denn alle diese Datensätze sind wirklich aktive Kunden, d.h. bekommen mindestens einmal im Jahr eine Rechnung.

Um sich nun mit unserer Anwendung als Benutzer Administrator anzumelden benötigte ich noch ein Kennwort. Das hatte mir keiner mitgeteilt (bis jetzt). Aber was soll’s, probieren wir doch mal 😉

  1. Versuch: Der Firmenname
  2. Versuch: Der Produktname
  3. Versuch: admin… Bingo 😮

Ich will nicht davon reden, wieviele Aktivität betrieben wird,  die Datenbanken nach außen abzuschirmen. Auch will ich nicht über die interne Panik klagen, die uns gegenüber immer geschoben wird, dass alles hoch sicher und geheim zu behandeln ist.

Jeder Angestellte, mit etwas Spielwitz kann in etwa 20 Sekunden als Admin an alle Daten…

Nur als Anmerkung: Dieses Sicherheits-Problem ist natürlich mittlerweile behoben… :mrgreen:

Verhindern des Flackerns von Controls wenn ein Fenster-Resize erfolgt

Immer wieder taucht in Foren die Frage aus, wie man das Flackern von Controls verhindern kann. Die Allgemeine Antwort heißt Doublebuffering, d.h. die Ausgabe wird zuerst auf einem nicht sichtbaren Memory DC durchgeführt und anschließend in einem Schlag auf den eigentlichen DC kopiert. Wichtig ist hier, dass auch der Hintergrund im eigentlichen WM_PAINT Handler mit gezeichnet wird und WM_ERASEBKGND gar nichts mehr macht. Der Code-Klassiker hierzu findet sich in Code-Project http://www.codeproject.com/KB/GDI/flickerfree.aspx

Anders liegt die Sache wenn es beim Resize eines Fensters flackert. Hier ist selten Doublebuffering eine Lösung. Meistens liegt hier das Problem darin, dass das Parent Fenster seinen Hintergrund neu zeichnet und anschließend alle Child-Windows auch neu gezeichnet werden müssen.

Aber auch hier ist Abhilfe einfach. Im Parent-Fenster wird einfach der Stil WS_CLIPCHILDREN gesetzt. Das sorgt dafür, dass das Parent Fenster einen DC bekommt bei dem die einzelnen Child-Windows ausgeclippt sind, und somit weder durch WM_ERASEBKGND noch durch den WM_PAINT Handler des Parents überschrieben werden.

Sollten sich die Child-Fenster überlappen müsste man zusätzlich an WS_CLIPSIBLINGS bei allen Kindfenstern als Stil denken (nicht beim Parent).

Wie man sich mit CComDispatchDriver bzw. CComPtr<IDispatch>::InvokeN hereinlegen kann

Eigentlich müsste dieser Artikel eine weitere Überschrift bekommen:
Wie fatal es ist, dass es keine vollständige ATL Dokumentation gibt!

Einige werden CComDispatchDriver kennen. Seit den VS-200x Versionen ist diese Klasse nichts anderes als sie Spezialisierung CComPtr<IDispatch>

Ich hatte vor, eine bestimmte .NET Komponente per late binding in ein C++ Programm zu integrieren.
Kein Problem. CreateInstance, hier ein Invoke0, dort ein Invoke2 und da noch ein InvokeN 😮 … und hier geht auf einmal nichts mehr. Dokumentation in der MSDN: Fehlanzeige!

Bestandsaufnahme: Ich habe einen CComVariant Array aufgebaut und die entsprechenden Daten übergeben. Sieht alles richtig aus. Aber ich erhalte immer nur Fehler (immer E_NOINTERFACE).

  1. OK, nächster Schritt. Das ganze mal direkt mit dem entsprechenden Interface versuchen. Also CComQIPtr eingebaut und direkter Aufruf über die existierende duale Schnittstelle. Komisch, nun geht es.
  2. Nun denn. Das ganze nun mal als Klasse mit dem Classview importiert und einen MFC Wrapper gebaut. Der verwendet ja auch IDispatch. Jetzt wird es mysteriös: Es geht auch!

Dann debuggen wir mal den MFC Wrapper. Schrittweise gehe ich durch den Code im COleDispatchDriver::InvokeHelperV bis Zeile 223 und dort lese ich den folgenden Code und Kommentar:

pArg += dispparams.cArgs - 1; // params go in opposite order

❗ Aha! Hier liegt der Hase im Pfeffer. Ich hatte den CComVariant Array natürlich so aufgebaut, dass das erste Argument auch das erste Element im Array war. InvokeN will aber die selbe Reihenfolge wie IDispatch::Invoke, d.h. in umgekehrter Folge. Also beginnt der Array nun mit dem letzten Argument und endet mit dem ersten und siehe da: Es geht ❗

Schade, dass hier die ATL-Doku so lückenhaft ist. Das hätte mir hier, 2 Stunden Arbeit gespart.
Ich hätte es auch schneller haben können, wenn ich mir aufmerksam die Implementierung von CComPtr<IDispatch>::Invoke2 angesehen hätte (Man achte auf den Array varArgs):

inline HRESULT CComPtr<IDispatch>::Invoke2(__in DISPID dispid, 
       __in VARIANT* pvarParam1, 
       __in VARIANT* pvarParam2, 
       __out_opt VARIANT* pvarRet) throw() 
{ 
 if(pvarParam1 == NULL || pvarParam2 == NULL) 
   return E_INVALIDARG; 
 CComVariant varArgs[2] = { *pvarParam2, *pvarParam1 }; 
 DISPPARAMS dispparams = { &varArgs[0], NULL, 2, 0}; 
 return p->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, 
     DISPATCH_METHOD, &dispparams, pvarRet, NULL, NULL); 
}

Produktvergleich der verschiedenen Visual Studio 2008 Editionen

In diesem Produktvergleich http://msdn2.microsoft.com/en-us/vstudio/products/cc149003.aspx kann man einfach herausfinden welche Komponenten mit den verschiedenen Visual Studio Editionen ausgeliefert werden.

Die häufigste Frage lautet immer wieder in den Foren:
Was benötige ich für eine Edition um ATL+MFC Programme zu schreiben?
Antwort: Die Visual Studio 2008 Standard Edition! Die Express-Edition enthält weder ATL noch MFC.

Wenn die eigene Tochter im Radio zu hören ist…

… dann ist man schon ein wenig stolz :mrgreen:

Katrin war am 25.+26.02.2008  im MDR1 Radio Sachsen mit Beiträgen zu hören.

Hier die Links zum Text:
Wort zum Tag – Sonnabend, 26. Januar 2008
Wort zum Tag – Freitag, 25. Januar 2008

Hier die Liste der Podcasts, einen direkten Link zu den Beiträgen habe ich hier leider nicht gefunden:
MDR1 Radio Sachsen: Wort zum Tag
Anmerkung: Da derMDR hier nur die letzten 14 Tage vorhält wird es nicht mehr lange dauern bis der Beitrag rausfällt.

VS-Tipps & Tricks: Einfaches Navigieren mit Strg+-

Vielleicht ist einigen schon aufgefallen, dass es die Schalter Navigate Backward und Navigate Forward im Standard-Toolbar von Visual Studio gibt. Diese Funktionen werden über die Hotkeys Strg+- (Bindestrich) und Strg+Umschalt+- ausgelöst.

Bei dieser Funktion werden Go-Back-Markierungen in einer Liste vermerkt. Hierbei wird nicht jede Cursorbewegung aufgezeichnet, sondern Bewegungen, die über eine größere Distanz erfolgen.

D.h. man kann mit Strg+- sofort an die Position zurückspringen, die man soeben verlassen hat. Die Umkehrfunktion wird wie gewohnt mit der Umschalt-Taste ausgelöst, Umschalt+Strg+-.

Solche Go-Back-Marken werden in den folgenden Fällen gesetzt.

  • Öffnet man eine neue Datei, dann wird die Position in der aktuellen Datei, als Go-Back-Marke gespeichert. Das Wechseln in eine andere Datei (Strg+Tab) setzt keine Go-Back-Marke (was ich verwunderlich finde).
  • Jede Löschoperation nach einer Cursorbewegung setzt eine Go-Back-Marke.
  • Eine Textsuche (Strg+F) setzt eine Go-Back-Marke an der Fundstelle.
  • Inkrementelle Suche (ob vorwärts oder rückwärts ist egal), trägt eine Go-Back-Marke in die Liste ein und gleichfalls, wenn die inkrementelle Suche beendet wird.
  • Verwendet man GotoLine (Strg+G) oder bewegt den Cursor mehr als 10 Zeilen von der aktuellen Position weg, wird eine Go-Back-Marke an der neuen Position gesetzt. Dies gilt auch wenn dies durch eine Suche ausgelöst wird, die mehr als 10 Zeilen weiterspringt. In diesem Fall wird auch die Startposition als Go-Back-Marke gespeichert.
  • Jeder Klick mit der linken bzw. rechten Maustaste platziert eine Go-Back-Marke an der alten Cursor-Position. Weitere Mausklicks ohne Cursor-Operationen zwischen drin platziert keine neue Go-Back-Marke.
  • Jeder Step-Into beim Debuggen löst auch setzt auch eine Go-Back-Marke.

Sehr nett ist auch die Möglichkeit den gesamten Text von der aktuellen Position bis zurück zur letzten Go-Back-Marke zu selektieren. Dies erfolgt über den Hotkey Strg++ (Pluszeichen).

Ausgesprochen nützlich finde ich diese Funktion auch beim debuggen. Man kann sofort an die Stelle zurückspringen von der man soeben kam, ohne das Call-Stack-Fenster zu verwenden.

BTW: Diese Funktionen habe ich erst durch das versehentliche Auslösen der Kombination Strg++ vor längerer Zeit entdeckt 🙂

Löschen von Verzeichnissen über Laufwerke, die als Verzeichnis bereitgestellt wurden

Ich habe eine zweite SATA Festplatte in meinem Rechner (Windows XP SP2). Festplatten kann man ja auch direkt als neue Verzeichnisse bereitstellen. Schön, da eines meiner Verzeichnisse auf der ersten Festplatte am überquellen ist dachte ich mir:
Stelle einfach dies zweite Festplatte als neues Verzeichnis in meiner Verzeichnisstruktur zur Verfügung.

Gesagt getan und alles prima.

Bis ich heute auf die Idee kam ein wenig aufzuräumen:
Hier eine Datei gelöscht: OK. Dort eine Datei gelöscht: auch OK. Hier das Verzeichnis löschen: Peng „Zugriff wurde verweigert“! 😮
Was ist denn hier los ❓

Der Explorer hat mit meinem so eingemappten Laufwerk ein Problem. Er möchte die Dateien, die zu löschen sind, in den Papierkorb (Recycler Verzeichnis) verschieben. Das gelingt ihm offensichtlich mit Dateien (und die werden kopiert), allerdings gelingt ihm das nicht mit Verzeichnissen.

Direkt Löschen kann man diese Verzeichnisse indem man die Umschalt-Taste festhält m Explorer.
Das Löschen gelingt auch, wenn man die Festplatte über einen Laufwerksbuchstaben zusätzlich einmappt und dann über diese Verknüpfung die Verzeichnisse löscht.

Irgendwie nicht zu verstehen, dass hier der Explorer so kläglich versagt. 🙁
Aber auch dieses Verhalten ist dokumentiert, was ich nach einer Rückfrage bei meinen Mit-MVP Ralf Breuer auch erfuhr:
http://support.microsoft.com/kb/319368

Anmerkung: ❗ Eine Datei, die über das gemappte Verzeichnis C:\xyz gelöscht wird landet übrigens im Papierkorb auf der Platte C:, obwohl sie physikalisch auf einer anderen Platte liegt. Wirre Explorer Welt!