2011-01-04 15 views
18

Ich habe eine Anwendung, die Office-Interop-Baugruppen verwendet. Ich kenne den Runtime Callable Wrapper (RCW), der von der Runtime verwaltet wird. Aber ich bin mir nicht sicher, wie der Referenzzähler inkrementiert wird. MSDN sagt,RCW & Referenzzählung bei Verwendung von COM-Interop in C#

RCW nur ​​eine Referenz auf das Objekt gewickelt COM hält unabhängig von der Anzahl der verwalteten Kunden nennen es.

Wenn ich es richtig verstanden habe, auf dem folgenden Beispiel

using Microsoft.Office.Interop.Word; 

static void Foo(Application wrd) 
{ 
    /* .... */ 
} 

static void Main(string[] args) 
{ 
    var wrd = new Application(); 
    Foo(wrd); 
    /* .... */ 
} 

Ich bin das Bestehen der Instanz wrd an eine andere Methode. Dies erhöht jedoch nicht die interne Referenzzahl. Ich frage mich also, in welchen Szenarien die Referenzzählung inkrementiert wird. Kann jemand auf ein Szenario hinweisen, in dem der Referenzzähler erhöht wird?

Auch ich lese ein Blog, das sagt, vermeiden Sie doppelte Punkte beim Programmieren mit COM-Objekten. Etwas wie, wrd.ActiveDocument.ActiveWindow. Der Autor behauptet, dass der Compiler separate Variablen erstellt, die die Werte enthalten, die den Referenzzähler inkrementieren. IMHO, das ist falsch und das erste Beispiel beweist dies. Ist das korrekt?

Jede Hilfe wäre großartig!

+0

Marshall.AddRef & Marshall.Release für das COM-Objekt neue Referenzzähler zurück. Nicht sicher, wie genau es ist, aber zumindest können Sie den Anspruch des Autors überprüfen. – Arseny

Antwort

40

Ich habe diese Frage auch untersucht, arbeite an einer COM/.Net-Interop-zentrischen Anwendung, bekämpfe Lecks, hängt und stürzt ab.

Kurze Antwort: Jedes Mal, wenn das COM-Objekt von der COM-Umgebung an .NET übergeben wird.

Lange Antwort:

  1. Für jedes COM-Objekt gibt es ein RCW Objekt [Test 1] [Ref 4]
  2. Referenzzähler jedes Mal erhöht wird das Objekt aus COM-Objekt angefordert wird (Aufruf Eigenschaft oder Methode für COM-Objekt, das COM-Objekt zurückgibt, wird die zurückgegebene COM-Objekt-Referenzzählung um eins inkrementiert. [Test 1]
  3. Referenzzählung wird nicht erhöht, indem auf andere COM-Schnittstellen des Objekts geworfen oder die RCW-Referenz verschoben wird. Test 2]
  4. Referenc e Zählung wird jedes Mal ein Objekt erhöht als Parameter in Fall von COM angehoben geben wird [Ref 1]

Auf einer seitlichen Anmerkung: Sie sollten IMMER COM Release-Objekte, sobald Sie sie nicht mehr benötigen. Wenn diese Arbeit dem GC überlassen wird, kann dies zu Lecks, unerwartetem Verhalten und Ereignis-Deadlocks führen. Das ist zehnmal wichtiger, wenn Sie auf das Objekt zugreifen, nicht auf den STA-Thread, auf dem es erstellt wurde. [Ref 2] [Ref 3] [Schmerzhafte persönliche Erfahrung]

Ich hoffe ich habe alle Fälle abgedeckt, aber COM ist ein harter Keks. Prost.

Test 1 - Referenzzähler

private void Test1(_Application outlookApp) 
{ 
    var explorer1 = outlookApp.ActiveExplorer(); 
    var count1 = Marshal.ReleaseComObject(explorer1); 
    MessageBox.Show("Count 1:" + count1); 

    var explorer2 = outlookApp.ActiveExplorer(); 
    var explorer3 = outlookApp.ActiveExplorer(); 
    var explorer4 = outlookApp.ActiveExplorer(); 

    var equals = explorer2 == explorer3 && ReferenceEquals(explorer2, explorer4); 
    var count2 = Marshal.ReleaseComObject(explorer4); 
    MessageBox.Show("Count 2:" + count2 + ", Equals: " + equals); 
} 
Output: 
Count 1: 4 
Count 2: 6, Equals: True 

Test 2 - Zählung cont Referenz.

private static void Test2(_Application outlookApp) 
{ 
    var explorer1 = outlookApp.ActiveExplorer(); 
    var count1 = Marshal.ReleaseComObject(explorer1); 
    MessageBox.Show("Count 1:" + count1); 

    var explorer2 = outlookApp.ActiveExplorer(); 

    var explorer3 = explorer2 as _Explorer; 
    var explorer4 = (ExplorerEvents_10_Event)explorer2; 
    var explorerObject = (object)explorer2; 
    var explorer5 = (Explorer)explorerObject; 

    var equals = explorer2 == explorer3 && ReferenceEquals(explorer2, explorer5); 
    var count2 = Marshal.ReleaseComObject(explorer4); 
    MessageBox.Show("Count 2:" + count2 + ", Equals: " + equals); 
} 
Output: 
Count 1: 4 
Count 2: 4, Equals: True 

Quellen Relais ich zusätzlich zu meiner Erfahrung auf und Prüfung:

1. Johannes Passing's - RCW Reference Counting Rules != COM Reference Counting Rules

2. Eran Sandler - Runtime Callable Wrapper Internals and common pitfalls

3. Eran Sandler - Marshal.ReleaseComObject and CPU Spinning

4. MSDN - Runtime Callable Wrapper

+0

vielen Dank. Ich werde deine Tests überprüfen und zurückkommen. –

+0

Diese Tests berücksichtigen nicht die Tatsache, dass tatsächlich zwei Referenzzähler vorhanden sind, der echte native des COM-Objekts (der nur einmal um CLR erhöht werden sollte) und der verwaltete Referenzzähler innerhalb des RCW, der ebenso wie die Zahl zählt Manchmal wurde das Objekt von der nativen Welt übergeben (dh wie oft Sie 'ReleaseComObject' aufrufen wollten). Ihr Test misst nur Letzteres, was nur eine Teilantwort auf die Frage ist. – hypersw

-2

Sie müssen Marshal.ReleaseComObject auf Ihrer WRD-Variable aufrufen, um Ihren Verweis auf das Wort Anwendung freizugeben.

Wenn Word nicht sichtbar ist und Sie Ihre Anwendung schließen, wird die EXE auch entladen, es sei denn, Sie haben es für den Benutzer sichtbar gemacht.

+0

danke, aber das beantwortet die Frage nicht. Ich bin bewusst über 'Marshal.ReleaseComObject' und' Marshal.FinalReleaseComObject' Methoden. Meine Frage war mehr, wie die interne Referenzzahl funktioniert und an welchem ​​Punkt sie inkrementiert wird. –

1

Sie sollten keine spezielle Behandlung benötigen. Die Laufzeitumgebung enthält nur einen Verweis auf das COM-Objekt. Der Grund dafür ist, dass der GC alle verwalteten Verweise verfolgt. Wenn der RCW den Gültigkeitsbereich verlässt und erfasst wird, wird die COM-Referenz freigegeben. Wenn Sie eine verwaltete Referenz übergeben, verfolgt der GC diese für Sie. Dies ist einer der größten Vorteile einer GC-basierten Laufzeitumgebung gegenüber dem alten AddRef/Release-Schema.

Sie müssen Marshal.ReleaseComObject nicht manuell aufrufen, es sei denn, Sie möchten eine deterministische Version.

+0

Frage ist nicht über die Verwendung von Marshal.ReleaseComObject oder etwas Ähnliches. Ich habe versucht zu verstehen, wie das Referenzzählen funktioniert und an welchem ​​Punkt es inkrementiert wird. –

+1

Genau. Wenn Sie noch einmal lesen, werden Sie feststellen, dass ich genau das sage. Es gibt nur eine COM-Referenz. Es wird niemals erhöht. Die Verfolgung des RCW erfolgt durch den GC. – codekaizen

3

Ich habe den Code für das RCW nicht gesehen - nicht einmal sicher, dass es Teil des SSCLI ist - aber ich musste ein ähnliches System für die Verfolgung der Lebensdauer von COM-Objekten in SlimDX implementieren und musste ein wenig Nachforschungen anstellen in das RCW. Das ist, woran ich mich erinnere, hoffentlich ist es ziemlich genau, aber nehmen Sie es mit einem Hauch von Salz.

Wenn das System zum ersten Mal einen COM-Schnittstellenzeiger sieht, geht es einfach in einen Cache, um festzustellen, ob es einen RCW für diesen Schnittstellenzeiger gibt. Vermutlich würde der Cache schwache Referenzen verwenden, um die Finalisierung und Sammlung der RCW nicht zu verhindern.

Wenn es einen Live-Wrapper für diesen Zeiger gibt, gibt das System den Wrapper zurück - wenn die Schnittstelle in einer Weise erhalten wurde, die die Referenzzahl der Schnittstelle inkrementiert, würde das RCW-System zu diesem Zeitpunkt vermutlich Release() aufrufen. Es hat einen Live-Wrapper gefunden, daher weiß er, dass der Wrapper eine einzelne Referenz ist und genau eine Referenz beibehalten möchte. Wenn im Cache kein Live Wrapper vorhanden ist, erstellt er einen neuen Wrapper und gibt ihn zurück.

Der Wrapper ruft Release auf dem/den zugrunde liegenden COM-Schnittstellenzeiger (n) vom Finalizer auf.

Der Wrapper befindet sich zwischen Ihnen und dem COM-Objekt und verarbeitet alle Parameter Marshalling. Dies ermöglicht es auch, das rohe Ergebnis jeder Schnittstellenmethode zu verwenden, die selbst ein anderer Schnittstellenzeiger ist, und diesen Zeiger durch das RCW-Caching-System laufen zu lassen, um zu sehen, ob es existiert, bevor Sie den umschlossenen Schnittstellenzeiger zurückgeben.

Leider habe ich kein gutes Verständnis davon, wie das RCW-System Proxy-Objekt-Generierung für das Senden von Sachen über Anwendungsdomänen oder Thread-Wohnungen behandelt; Es war kein Aspekt des Systems, das ich für SlimDX kopieren musste.

0

Die accepted solution ist gültig, aber hier sind einige zusätzliche Hintergrundinformationen.

Ein RCW enthält intern ein oder mehrere native COM-Objektschnittstellenreferenzen für sein COM-Objekt.

Wenn ein RCW das zugrundeliegende COM-Objekt entweder aufgrund von Garbage Collected oder aufgrund von Marshal.ReleaseComObject(), das auf es abgerufen wird, freigibt, gibt es alle seine intern gehaltenen COM-Objektschnittstellen frei.

Es gibt tatsächlich viele Referenzzählungen hier - eine Bestimmung, wann .NET RCW die zugrunde liegenden COM-Objektschnittstellen freigeben sollte, und dann hat jede dieser Raw-COM-Schnittstellen ihre eigene Referenzzahl wie in regulären COM.

Hier Code roh COM IUnknown Schnittstelle Referenzzähler zu erhalten:

int getIUnknownReferenceCount(object comobject) 
{ 
    var iUnknown = Marshal.GetIUnknownForObject(comObject); 
    return Marshal.Release(iUnknown); 
} 

Und Sie können das gleiche für die anderen COM-Schnittstellen des Objekts erhalten mit Marshal.GetComInterfaceForObject().

Zusätzlich zu den in accepted solution aufgelisteten Möglichkeiten können wir auch die Anzahl der .NET RCW-Referenzen künstlich erhöhen, indem wir etwas wie Marshal.GetObjectForIUnknown() aufrufen.

Hier ist Beispielcode Verwendung dieser Technik Herstellung eines bestimmten COM-Objekt des RCW Referenzzähler zu erhalten:

int comObjectReferenceCount(object comObject) 
{ 
    var iUnknown = Marshal.GetIUnknownForObject(comObject); 
    Marshal.GetObjectForIUnknown(iUnknown); 
    Marshal.Release(iUnknown); 
    return Marshal.ReleaseComObject(comObject); 
} 
Verwandte Themen