Using Active Scripting from within C# without leaking memory

Working for about 3 days to get rid of memory leaks when using Microsoft’s Active Scripting from within a C# .NET 2.0 Windows Forms application, I finally managed to create a working example project that runs without memory leaks.

Download example project (VS 2008) with source and binary files

I used Red Gate’s ANTS Memory Profiler to verify for memory leaks, the best tool around for doing such a task.

The key in resolving (see download project) was to call IActiveScript::Close to free any resources.

I hope, this article will help someone in the future when facing a similar issue.

(References: My initial question on Stack Overflow)

Visual-Basic-Code (VBA) aus Microsoft-Access-Datenbank-Projekten exportieren

Falls Ihr Euren (oder fremden) Programmcode aus einem Access-Datenbank-Projekt (also einer MDB-Datei mit enhaltenem Basic-Code) exportieren wollt, so gibt es ein tolles Script, das mir hier weitergeholfen hat:

„Export All VBA Code“

Dort findet Ihr ein paar 10 Zeilen Code, den Ihr in Eurem Projekt einfügt, geringfügig anpasst (Pfade) und dann alle Module exportiert bekommt, auch den Codebehind von Formularen.

Den Microsoft Scripting Host in C# verwenden

Nachfolgend ein paar Anmerkungen zum den Erlebnissen und Erkenntnissen die ich beim Einbinden des Microsoft Scripting Host in eine C#-Windows Forms-Anwendung hatte.

Ressourcen

Zunächst sind folgende Ressourcen hilfreich:

  • Das Dr. Dobb’s Journal hat einige Artikel dazu veröffentlicht. Fragmente dazu stehen noch im Netz und sind bei Google zu finden.
  • Das Weblog von Eric Lippert enthält gute Infos zum Scripting Host im Allgemeinen.
  • Die Usenet-/Google-Group „microsoft.public.scripting.hosting“ enthält ebenfalls gute Infos.
  • Die eigentliche Schnittstellen-Definition ist z.B. im Platform-SDK-Ordner „c:\Programme\Microsoft SDK\Include\ActivSP.idl“ (so heißt er bei mir) zu finden.

Fehlermeldung „Klasse unterstützt keine Automatisierung“

Diese Fehlermeldung habe ich erhalten, als ich eigene C#-Klassen via IActiveScript.AddNamedItem hinzufügen wollte und diese Klassen nicht public waren oder nicht das ComVisible( true )-Attribut gesetzt hatten.

Als einfache Lösung also einfach schauen, dass die Klassen (inklusive aller Basisklassen) ComVisible( true ) und public sind.

Fehlermeldung „FatalExecutionEngineError“

FatalExecutionEngineError was detected
Message: Die Laufzeit hat einen schwerwiegenden Fehler entdeckt. Fehleradresse: „0x7a0b2d09“ in Thread „0x29c“. Fehlercode: 0xc0000005. Bei diesem Fehler könnte es sich um ein Problem in der CLR oder in den unsicheren oder nicht verifizierbaren Teilen des Benutzercodes handeln. Übliche Ursachen dieses Bugs sind Marshallerfehler für COM-Interop oder PInvoke, die den Stapel beschädigen können.

Diese Fehlermeldung habe ich erhalten, weil ich einen falschen Parameter für die Funktion IActiveScript.AddNamedItem übergeben hatte.

Und zwar habe ich das Flag „SCRIPTITEM_ISSOURCE“ angegeben. Als ich das Flag weggelassen habe, lief alles korrekt.

Dieses Flag gibt laut MSDN-Dokumentation an, dass das hinzugefügte Objekt als Senke für Skript-Ereignisse agieren soll. Scheinbar geht das nicht oder zumindest nicht so wie ich es gedacht habe.

Macht aber nix, brauche ich in meinem Fall nicht. Deshalb habe ich einfach das Flag weggelassen und gut war’s.

Generische Typen in COM verwenden

Klassen die direkt von generischen Basisklassen ableiten sind nicht in COM (also in Skripten im Windows Scripting Host) sichtbar. Es kommen dann seltsame Fehler wie „Method not found“ beim Aufruf von Methoden solcher Klassen.

Deshalb Klassen die von COM aus ansprechbar sein sollen immer von nicht-generischen Basisklassen ableiten.

Was bei mir funktioniert hat ist, wenn solche Klassen generische Schnittstellen implementieren (z.B. IComparable<T>). Die Klasse selbst ist also nicht generisch, nur eine implementierte Schnittstelle. Solch eine Klasse konnte ich erfolgreich von COM aus ansprechen.

Hier einige weiterführende Hinweise zu dem Thema:

  • Meine ursprüngliche Anfrage in den MSDN-Foren
  • „Interoperating Using Generic Types“ in der MSDN Library
  • „COM Interop with Whidbey Generics?“, Artikel von 2004 von Sam Gentile in seinem Weblog

Schnittstellendefinition

Weil ich lange gesucht habe, hier der Download der Schnittstellendefinitionen für folgende Schnittstellen und Enumerationen:

  • IActiveScript
  • IActiveScriptParse
  • IActiveScriptSite
  • IActiveScriptError
  • SCRIPTSTATE
  • SCRIPTTHREADSTATE
  • SCRIPTITEMFLAGS
  • SCRIPTINFOFLAGS

AllocSysString

Hartnäckigkeit zahlt sich oft aus: Heute habe ich ein Speicherleck (englisch „Memory leak“) in Zeta Producer korrigiert, das seit Version 5 drin war:

Beim Aufruf der Microsoft Scripting Engine habe ich beim Aufruf der Funktion IActiveScriptParse::ParseScriptText irrtümlicherweise CStringT::AllocSysString aufgerufen und den so angeforderten Speicher selbst nicht mehr freigegeben. Dies hätte ich jedoch machen müssen, was ich in der korrigierten Version nun mache.

Das hätte ich aber machen sollen! „RTFM“ hat Mario mir da gleich geantwortet als ich es ihm erzählt habe.

Hiermit also nochmals der ultimative Artikel wann wer für die Freigabe von Zeichenfolgen die mittels AllocSysString reserviert wurden verantwortlich ist: „Allocating and Releasing Memory for a BSTR (Visual C++ Concepts)„.