Leitfaden zum Implementieren von In-Process-Erweiterungen

Prozessinterne Erweiterungen werden in alle Prozesse geladen, die sie auslösen. Beispielsweise kann eine Shell-Namespaceerweiterung in jeden Prozess geladen werden, der direkt oder indirekt auf den Shell-Namespace zugreift. Der Shell-Namespace wird von vielen Shell-Vorgängen verwendet, z. B. der Anzeige eines Dialogfelds für eine gemeinsame Datei, dem Starten eines Dokuments über die zugehörige Anwendung oder dem Abrufen des Symbols, das zur Darstellung einer Datei verwendet wird. Da In-Process-Erweiterungen in beliebige Prozesse geladen werden können, muss darauf geachtet werden, dass sie sich nicht negativ auf die Hostanwendung oder andere prozessinterne Erweiterungen auswirken.

Besonders hervorzuheben ist die Common Language Runtime (CLR), die auch als verwalteter Code oder als .NET Framework bezeichnet wird. Microsoft empfiehlt, keine verwalteten In-Prozess-Erweiterungen in Windows Explorer oder Windows Internet Explorer zu schreiben, und betrachtet sie nicht als unterstütztes Szenario.

In diesem Thema werden Faktoren erläutert, die berücksichtigt werden müssen, wenn Sie bestimmen, ob eine andere Laufzeit als die CLR für die Verwendung durch Prozesserweiterungen geeignet ist. Beispiele für andere Runtimes sind Java, Visual Basic, JavaScript/ECMAScript, Delphi und die C/C++-Laufzeitbibliothek. Dieses Thema enthält auch einige Gründe dafür, dass verwalteter Code in Prozesserweiterungen nicht unterstützt wird.

Versionskonflikte

Ein Versionskonflikt kann durch eine Runtime entstehen, die das Laden mehrerer Laufzeitversionen innerhalb eines einzelnen Prozesses nicht unterstützt. Versionen der CLR vor Version 4.0 fallen in diese Kategorie. Wenn das Laden einer Version einer Runtime das Laden anderer Versionen derselben Runtime ausschließt, kann dies zu einem Konflikt führen, wenn die Hostanwendung oder eine andere prozessinterne Erweiterung eine in Konflikt stehende Version verwendet. Im Fall eines Versionskonflikts mit einer anderen prozessinternen Erweiterung kann der Konflikt schwierig zu reproduzieren sein, da der Fehler die richtigen Erweiterungen erfordert, die in Konflikt stehen, und der Fehlermodus von der Reihenfolge abhängt, in der die in Konflikt stehenden Erweiterungen geladen werden.

Betrachten Sie eine prozessinterne Erweiterung, die mit einer Version der CLR vor Version 4.0 geschrieben wurde. Bei jeder Anwendung auf dem Computer, die ein Dialogfeld Datei öffnen verwendet, kann möglicherweise der verwaltete Code des Dialogfelds und die zugehörige CLR-Abhängigkeit in den Prozess der Anwendung geladen werden. Die Anwendung oder Erweiterung, die zuerst eine Pre-4.0-Version der CLR in den Prozess der Anwendung lädt, schränkt ein, welche Versionen der CLR anschließend von diesem Prozess verwendet werden können. Wenn eine verwaltete Anwendung mit einem Dialogfeld Öffnen auf einer in Konflikt stehenden Version der CLR basiert, kann die Erweiterung möglicherweise nicht ordnungsgemäß ausgeführt werden und zu Fehlern in der Anwendung führen. Wenn die Erweiterung hingegen die erste ist, die in einem Prozess geladen wird und eine in Konflikt stehende Version von verwaltetem Code danach versucht, zu starten (möglicherweise lädt eine verwaltete Anwendung oder eine ausgeführte Anwendung die CLR bei Bedarf), schlägt der Vorgang fehl. Für den Benutzer scheint es, dass einige Features der Anwendung zufällig nicht mehr funktionieren, oder die Anwendung stürzt auf mysteriöse Weise ab.

Beachten Sie, dass Versionen der CLR gleich oder höher als Version 4.0 im Allgemeinen nicht anfällig für das Versionsverwaltungsproblem sind, da sie für die Koexistenz untereinander und mit den meisten Versionen vor 4.0 der CLR konzipiert sind (mit Ausnahme von Version 1.0, die nicht mit anderen Versionen koexistieren kann). Es können jedoch andere Probleme als Versionskonflikte auftreten, wie im weiteren Verlauf dieses Themas erläutert.

Leistungsprobleme

Leistungsprobleme können bei Laufzeiten auftreten, die eine erhebliche Leistungseinbuße verursachen, wenn sie in einen Prozess geladen werden. Die Leistungseinbuße kann in Form von Arbeitsspeicherauslastung, CPU-Auslastung, verstrichener Zeit oder sogar Adressraumverbrauch sein. CLR, JavaScript/ECMAScript und Java sind bekannt als Laufzeiten mit hoher Auswirkung. Da In-Prozess-Erweiterungen in viele Prozesse geladen werden können und dies häufig in leistungsabhängigen Momenten geschieht (z. B. bei der Vorbereitung eines Menüs, das dem Benutzer angezeigt werden soll), können sich Laufzeiten mit hoher Auswirkung negativ auf die Reaktionsfähigkeit insgesamt auswirken.

Eine Laufzeit mit hohen Auswirkungen, die erhebliche Ressourcen verbraucht, kann einen Fehler im Hostprozess oder einer anderen prozessinternen Erweiterung verursachen. Beispielsweise kann eine Laufzeit mit hoher Auswirkung, die Hunderte von Megabyte Adressraum für ihren Heap verbraucht, dazu führen, dass die Hostanwendung kein großes Dataset laden kann. Da prozessinterne Erweiterungen in mehrere Prozesse geladen werden können, kann sich ein hoher Ressourcenverbrauch in einer einzelnen Erweiterung schnell zu einem hohen Ressourcenverbrauch im gesamten System vervielfachen.

Wenn eine Runtime geladen bleibt oder andernfalls weiterhin Ressourcen verbraucht, auch wenn die Erweiterung, die diese Runtime verwendet, entladen wurde, ist diese Runtime nicht für die Verwendung in einer Erweiterung geeignet.

Spezifische Probleme für die .NET Framework

In den folgenden Abschnitten werden Beispiele für Probleme erläutert, die bei der Verwendung von verwaltetem Code für Erweiterungen gefunden wurden. Es handelt sich nicht um eine vollständige Liste aller möglichen Probleme, die möglicherweise auftreten. Die hier erläuterten Probleme sind sowohl Gründe dafür, dass verwalteter Code in Erweiterungen nicht unterstützt wird, als auch Punkte, die bei der Auswertung der Verwendung anderer Runtimes zu berücksichtigen sind.

Re-Entrancy

Wenn die CLR einen Singlethread-Apartmentthread (STA)-Thread blockiert, z. B. aufgrund einer Monitor.Enter-, WaitHandle.WaitOne- oder angenommenen Sperranweisung , tritt die CLR in ihrer Standardkonfiguration während der Wartezeit in eine geschachtelte Nachrichtenschleife ein. Viele Erweiterungsmethoden sind an der Verarbeitung von Nachrichten verboten, und diese unvorhersehbare und unerwartete Wiederholung kann zu einem anomalen Verhalten führen, das schwer zu reproduzieren und zu diagnostizieren ist.

Das Multithread-Apartment

Die CLR erstellt Runtime-Aufrufbare Wrapper für COM-Objekte (Component Object Model). Diese runtime Callable Wrapper werden später vom Finalizer der CLR zerstört, der Teil des Multithreaded-Apartments (MTA) ist. Das Verschieben des Proxys von der STA in den MTA erfordert Marshalling, aber nicht alle von Erweiterungen verwendeten Schnittstellen können ge marshallt werden.

Lebensdauer nicht deterministischer Objekte

Die CLR verfügt über schwächere Garantien für die Objektlebensdauer als nativen Code. Viele Erweiterungen haben Anforderungen an die Verweisanzahl für Objekte und Schnittstellen, und das von der CLR verwendete Garbage Collection-Modell kann diese Anforderungen nicht erfüllen.

  • Wenn ein CLR-Objekt einen Verweis auf ein COM-Objekt abruft, wird der COM-Objektverweis, der vom Runtime Callable Wrapper gehalten wird, erst freigegeben, wenn der Runtime Callable Wrapper garbage-collection ist. Nicht deterministisches Releaseverhalten kann mit einigen Schnittstellenverträgen in Konflikt geraten. Beispielsweise erfordert die IPersistPropertyBag::Load-Methode , dass kein Verweis auf den Eigenschaftenbehälter vom Objekt beibehalten wird, wenn die Load-Methode zurückgibt.
  • Wenn ein CLR-Objektverweis auf systemeigenen Code zurückgegeben wird, gibt der Runtime Callable Wrapper seinen Verweis auf das CLR-Objekt auf, wenn der endgültige Aufruf des Runtime Callable Wrappers von Release erfolgt, aber das zugrunde liegende CLR-Objekt erst abgeschlossen wird, wenn es mit Garbage Collection gesammelt wird. Nichtdeterministische Finalisierung kann mit einigen Schnittstellenverträgen in Konflikt geraten. Beispielsweise sind Miniaturansichtshandler erforderlich, um alle Ressourcen sofort freizugeben, wenn ihre Referenzanzahl auf 0 sinkt.

Akzeptable Verwendungen von verwaltetem Code und anderen Runtimen

Es ist akzeptabel, verwalteten Code und andere Runtimes zum Implementieren von Out-of-Process-Erweiterungen zu verwenden. Beispiele für Out-of-Process-Shell-Erweiterungen sind die folgenden:

  • Vorschauhandler
  • Befehlszeilenbasierte Aktionen, z. B. solche, die unter Unterschlüsseln desShellverbens\\ registriert sind.
  • COM-Objekte, die auf einem lokalen Server implementiert sind, für Shell-Erweiterungspunkte, die die Aktivierung außerhalb des Prozesses ermöglichen.

Einige Erweiterungen können entweder als In-Process- oder Out-of-Process-Erweiterungen implementiert werden. Sie können diese Erweiterungen als Out-of-Process-Erweiterungen implementieren, wenn sie diese Anforderungen für prozessinterne Erweiterungen nicht erfüllen. Die folgende Liste zeigt Beispiele für Erweiterungen, die entweder als In-Process- oder Out-of-Process-Erweiterungen implementiert werden können:

  • IExecuteCommand, der einem DelegateExecute-Eintrag zugeordnet ist, der unter einemShellverb-Befehlsunterschlüssel\\ registriert ist.
  • IDropTarget, der der CLSID zugeordnet ist, die unter einemShellverb\\DropTarget-Unterschlüssel registriert ist.
  • IExplorerCommandState, die einem CommandStateHandler-Eintrag zugeordnet ist, der unter einemShellverb-Unterschlüssel\ registriert ist.