myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE
   » Gadget für Windows
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
» Accessoires

Ressourcen
» .NET-Glossar
» guide to C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Gemeinschaft » .NET-Komponenten und C#-Snippets » Allgemeiner COM-Wrapper für Späte Bindung
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | An Freund senden | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Allgemeiner COM-Wrapper für Späte Bindung

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
Rainbird Rainbird ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2834.jpg


Dabei seit: 28.05.2005
Beiträge: 3.716
Entwicklungsumgebung: Visual Studio 2010
Herkunft: Mauer


Rainbird ist offline MSN-Passport-Profil von Rainbird anzeigen

Allgemeiner COM-Wrapper für Späte Bindung

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Beschreibung:

Wer verschiedene Office-Versionen mit dem Selben Stück Code automatisieren will, muss in den meisten Fällen zu Reflection greifen. Leider ist Reflection recht umständlich. Außerdem wird der Code sehr unleserlich und ist besonders für Entwickler mit weniger Erfahrung ein Buch mit sieben Siegeln.
Auch die determisitische Speicherverwaltung von COM-Anwendungen wie Office hat schon manchem einen bösen Streich gespielt. Im schlimmsten Fall werden die Office-Anwendungen nicht mehr sauber geschlossen und verbleiben als Müll im Hauptspeicher.

Deshalb habe ich einen allgemeinen Wrapper geschrieben, der den Umgang mit spätgebundenen COM-Objekten in C# einfacher und intuitiver gestaltet.

C#-Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Rainbird.Tools.ComInterop
{
    /// <summary>
    /// Allgemeiner Wrapper, um spätgebunden mit COM-Objekten (bzw. ActiveX-Komponenten) zu arbeiten.
    /// </summary>
    public class ComObject : IDisposable
    {
        // COM-Objekt
        private object _realComObject = null;

        /// <summary>
        /// Übernimmt einen vorhanden Verweis auf ein COM-Objekt.
        /// </summary>
        /// <param name="injectedObject">Vorhandenes COM-Objekt</param>
        public ComObject(object injectedObject)
        {
            // Wenn kein Objekt angegeben wurde ...
            if (injectedObject==null)
                // Ausnahme werfen
                throw new ArgumentNullException("injectedObject");

            // Wenn das angegebene Objekt kein COM-Objekt ist ...
            if (!injectedObject.GetType().IsCOMObject)
                // Ausnahme werfen
                throw new ArgumentException("Das angegebene Objekt ist kein COM-Objekt!","injectedObject");

            // Verweis übernehmen
            _realComObject = injectedObject;
        }

        /// <summary>
        /// Erzeugt ein neues COM-Objekt anhand einer COM ProgId.
        /// </summary>
        /// <param name="progId"></param>
        public ComObject(string progId)
        {
            // Wenn keine ProgId angegeben wurde ...
            if (string.IsNullOrEmpty(progId))
                // Ausnahme werfen
                throw new ArgumentException();

            // Typinformationen über die ProgId ermitteln
            Type comType = Type.GetTypeFromProgID(progId);

            // Wenn keine Typeninformationene gefunden wurden ...
            if (comType == null)
                // Ausnahme werfen
                throw new TypeLoadException(string.Format("Fehler beim Laden der Typinformationen zu ProgId '{0}'.", progId));

            // Instanz erzeugen
            _realComObject = Activator.CreateInstance(comType);
        }

        /// <summary>
        /// Ruft eine Funktion auf, die als Rückgabewert ein COM-Objekt (also keinen primitiven Datentyp) zurückgibt.
        /// </summary>
        /// <param name="functionName">Funktionsname</param>
        /// <param name="parameters">Parameter</param>
        /// <returns>Rückgabeobjekt</returns>
        public ComObject InvokeObjectReturningFunction(string functionName, params object[] parameters)
        {
            // Methode aufrufen
            object result = _realComObject.GetType().InvokeMember(functionName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);

            // Wenn ein Objekt zurückgegeben wurde ...
            if (result != null)
                // Rückgabeobjekt in Wrapper einpacken und zurückgeben
                return new ComObject(result);

            // Nichts zurückgeben
            return null;
        }

        /// <summary>
        /// Ruft eine Funktion auf.
        /// </summary>
        /// <param name="functionName">Funktionsname</param>
        /// <param name="parameters">Parameter</param>
        /// <returns>Rückgabewert</returns>
        public object InvokeFunction(string functionName, params object[] parameters)
        {
            // Methode aufrufen und Rückgabewert zurückgeben
            return _realComObject.GetType().InvokeMember(functionName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
        }

        /// <summary>
        /// Ruft eine Prozedur auf.
        /// </summary>
        /// <param name="procedureName">Prozedurname</param>
        /// <param name="parameters">Parameter</param>
        public void InvokeProcedure(string procedureName, params object[] parameters)
        {
            // Methode aufrufen
            _realComObject.GetType().InvokeMember(procedureName, BindingFlags.InvokeMethod | BindingFlags.OptionalParamBinding, null, _realComObject, parameters);
        }

        /// <summary>
        /// Ruft den Wert einer Eigenschaft ab.
        /// </summary>
        /// <param name="propertyName">Eigenschaftsname</param>
        /// <returns>Rückgabewert</returns>
        public object GetProperty(string propertyName)
        {
            // Methode aufrufen und Rückgabewert zurückgeben
            return _realComObject.GetType().InvokeMember(propertyName, BindingFlags.GetProperty | BindingFlags.OptionalParamBinding, null, _realComObject, new object[0]);
        }

        /// <summary>
        /// Legt den Wert einer Eigenschaft fest.
        /// </summary>
        /// <param name="propertyName">Eigenschaftsname</param>
        /// <param name="parameters">Parameter</param>
        public void SetProperty(string propertyName, object value)
        {
            // Methode aufrufen
            _realComObject.GetType().InvokeMember(propertyName, BindingFlags.OptionalParamBinding | BindingFlags.SetProperty, null, _realComObject, new object[1] { value });
        }

        /// <summary>
        /// Ruft den Wert einer Eigenschaft ab (für Eigenschaften, die Objekte zurückgeben).
        /// </summary>
        /// <param name="propertyName">Eigenschaftsname</param>
        /// <returns>Rückgabeobjekt</returns>
        public ComObject GetObjectReturningProperty(string propertyName)
        {
            // Methode aufrufen und Rückgabewert zurückgeben
            object result=_realComObject.GetType().InvokeMember(propertyName, BindingFlags.GetProperty | BindingFlags.OptionalParamBinding, null, _realComObject, new object[0]);

            // Wenn ein Objekt zurückgegeben wurde ...
            if (result != null)
                // Rückgabeobjekt in Wrapper einpacken und zurückgeben
                return new ComObject(result);

            // Nichts zurückgeben
            return null;
        }

        #region IDisposable Member

        /// <summary>
        /// Verwendete Ressourcen freigeben-
        /// </summary>
        public void Dispose()
        {
            // Wenn das COM-Objekt nocht existiert ...
            if (_realComObject != null)
            {
                // COM-Objekt freigeben und entsorgen
                Marshal.ReleaseComObject(_realComObject);
                _realComObject = null;
            }
        }

        #endregion
    }
}

Wenn man z.B. Microsoft Word (Version 2000-2007) automatisch starten, dann "Hello World!" auf ein neues Dokument schreiben lassen will und Word dann auch wieder sauber beenden möchte, geht das mit dem allgemeinen COM-Wrapper in etwa so:

C#-Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Rainbird.Tools.ComInterop;

namespace Tester
{
    class Program
    {
        static void Main(string[] args)
        {
            // Word.Application-Objekt erzeugen
            ComObject word = new ComObject("Word.Application");

            // Sichtbar machen
            word.SetProperty("Visible",true);

            // Dokument-Auflistung abrufen
            ComObject documents = word.GetObjectReturningProperty("Documents");

            // Neues Dokument erzeugen
            ComObject document = documents.InvokeObjectReturningFunction("Add");

            // Verweise auf Dokument und Dokumentauflistung entsorgen
            document.Dispose();
            documents.Dispose();

            // Selection-Objekt der Word-Instanz abrufen
            ComObject selection = word.GetObjectReturningProperty("Selection");

            // "Hello World!" in aktuelles Dokument schreiben
            selection.InvokeFunction("TypeText", "Hello World!");

            // Selection-Objekt entsorgen
            selection.Dispose();

            // Auf Drücken von Enter warten
            Console.WriteLine("Drücken Sie Enter, um die Erzeugte Word-Instanz sauber zu schließen.");
            Console.ReadLine();

            // Word beenden ohne zu speichern
            word.InvokeProcedure("Quit",0);

            // Word-Instanz entsoregn
            word.Dispose();
        }
    }
}

Wer das Word-Objektmodell ein bischen kennt, sollte sich im obigen Beispiel sofort zu Hause fühlen.

Natürlich funktioniert der Wrapper nicht nur mit Office, sondern mit beliebigen COM-Objekten.

Was noch fehlt, ist das Konsumieren von Ereignissen. Wenn ich dazu komme, rüste ich das bei Zeiten nach.

Auch das Dispose-Pattern ist noch nicht ganz vollständig implementiert. Danke nochmal für die Hinweise diesbezüglich.

Schlagwörter: COMInterop, COM, Interop, Marshal.ReleaseComObject, Reflection, Word, Office
08.10.2009 01:34 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MacWale MacWale ist männlich
myCSharp.de-Mitglied

Dabei seit: 22.05.2008
Beiträge: 37
Entwicklungsumgebung: Visual Studio 2008
Herkunft: Deutschland


MacWale ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hey super klasse...

ich schreibe gerade ein Addin für ein Programm. Ich hatte eine Vorlage in VB.NET, welche auch super funktioniert! Ich entwickel nun das ganze in C# nach, sieht soweit ganz gut aus, bis auf ein paar Funktionen von dem System.__ComObject, z.Bsp. folgende:

Code:
1:
2:
3:
4:
5:
6:
7:
Public Function RequestData_(ByVal ptDateFrom As String, 

ByVal ptDateTil As String, ByRef ptDataString As Variant, 

ByRef plNumberOfRecords As Long, ByRef ptError As Variant, Optional

ByVal ptTypeFilter As String = "") As Boolean

Aufrufen tue ich die Funktion wie folgt:

C#-Code:
return (bool)MyComObject.InvokeFunction("RequestData_", new Object[] { ptDateFrom, ptDateUntil, ptDataString, plNumberOfRecords, ptError ,""});

Lt. Beschreibung sind die Parameter als ByRef definiert, in denen Werte zurückgegeben werden, welche ich für den weiteren Programmablauf benötige. Wie komme ich an diese Werte??

Vielen Dank im Vorraus!

Gruß
MacWale

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von MacWale am 25.10.2009 12:41.

25.10.2009 11:48 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Rainbird Rainbird ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2834.jpg


Dabei seit: 28.05.2005
Beiträge: 3.716
Entwicklungsumgebung: Visual Studio 2010
Herkunft: Mauer

Themenstarter Thema begonnen von Rainbird

Rainbird ist offline MSN-Passport-Profil von Rainbird anzeigen

Objektarray als Variable anlegen

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo MacWale,

du musst das Object-Array vorher als Variable anlegen. Dann kannst Du nach der Ausführungt von InvokeMember die Werte der ref-Parameter wieder aus dem Array rauslesen.

Siehe hier:  out, ref and InvokeMember !!!
28.10.2009 07:49 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegt mehr als ein Jahr.
Roschi
myCSharp.de-Mitglied

Dabei seit: 21.01.2011
Beiträge: 6


Roschi ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo

erstmal danke für deinen Wrapper, er hat mich schon sehr weit gebracht !!

Allerdings habe ich noch ein Problem. ich arbeite gerade an meinem 1. Projekt mit ComObjekten.

ich möchte auf Bilder in einem Wordfile zugreifen

C#-Code:
// Word.Application-Objekt erzeugen
            ComObject word = new ComObject("Word.Application");

            // Sichtbar machen
            word.SetProperty("Visible", true);

            // Dokument-Auflistung abrufen
            ComObject documents = word.GetObjectReturningProperty("Documents");

            //  Dokument öffnen
                      ComObject document = documents.InvokeObjectReturningFunction("Open", @"D:\xy.doc");

            ComObject Shapes = document.GetObjectReturningProperty("Shapes");

            int PicCount = (int)Shapes.GetProperty("Count");


            for (int i = PicCount; i > 0; i--)
            {
                  if (Shapes[i].Type == Microsoft.Office.Core.MsoShapeType.msoPicture) // <------------

                        {
                             Shapes[i].Delete();   // <------------
                        }             }

mein Problem sind jetzt die letzten Zeilen....

ich habe keine Ahnung wie ich das ComObjekt Shapes durchgehen kann um zB wenn es sich um ein Bild handelt diesen Shape zu löschen


ich hoffe du kannst mir helfen

LG Roschi
22.01.2011 13:54 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 9 Monate.
mstoll
myCSharp.de-Mitglied

Dabei seit: 10.06.2010
Beiträge: 4


mstoll ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Rainbird,

vielen Dank für deinen Wrapper, er hat mir ebenfalls schon viel gebracht.
Da ich Items aus einer Liste holen muss, habe ich den Wrapper um eine Methode erweitert.

C#-Code:
/// <summary>
/// Ruft das jeweilige Item ab
/// </summary>
/// <param name="index">
/// der Index
/// </param>
/// <returns>
/// das Item
/// </returns>
public ComWrapper GetItem(int index)
{
    var result = this.realComObject.GetType().InvokeMember(string.Empty, BindingFlags.Default | BindingFlags.InvokeMethod, null, this.realComObject, new object[] { index });

    if (result != null)
    {
        return new ComWrapper(result);
    }

    return null;
}

Falls das mal jemand braucht (auch wenn der Beitrag schon alt ist), viel Spaß damit :)

Beste Grüße
18.11.2011 11:13 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Sebastian.Lange Sebastian.Lange ist männlich
myCSharp.de-Poweruser/ Experte

Dabei seit: 22.06.2007
Beiträge: 1.038
Entwicklungsumgebung: Visual Studio 2008, 2010
Herkunft: Berlin


Sebastian.Lange ist offline MSN-Passport-Profil von Sebastian.Lange anzeigen

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Das Problem ist das diese Default Items in COM und ganz besonders in Office Objekten mal als Methode und mal als Property implementiert sind. Deine Implementierung zielt nur auf Methoden ab und wird damit in der allgemeinen Verwendung schnell Probleme verursachen.
18.11.2011 11:46 Beiträge des Benutzers | zu Buddylist hinzufügen
Rainbird Rainbird ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-2834.jpg


Dabei seit: 28.05.2005
Beiträge: 3.716
Entwicklungsumgebung: Visual Studio 2010
Herkunft: Mauer

Themenstarter Thema begonnen von Rainbird

Rainbird ist offline MSN-Passport-Profil von Rainbird anzeigen

NetOffice

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo mstoll,

schön Dir mein Office-Wrapper gefällt.

Dann könnte aber auch das folgende Open Source-Projekt für Dich interessant sein:

 http://netoffice.codeplex.com/

Das ist ein versionsunabhängiger (bezogen auf die Office-Version) Wrapper für Office und bringt noch eine Menge Zusatztools mit. Damit wird es noch viel einfacher verschiedene Office-Versionen mit einer einzigen Codebasis zu unterstützen.

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Rainbird am 18.11.2011 21:13.

18.11.2011 21:11 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegt mehr als ein Monat.
Comos User
myCSharp.de-Mitglied

Dabei seit: 20.12.2011
Beiträge: 2


Comos User ist offline

Nachfrage

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo,

Ich habe einen COM-Wrapper für späte Bindung nach diesem Muster erstellt, functioniert wunderbar. Daumen hoch



Nun zu Problem: der COM Server enthällt Funktionen (eigentlich Sub's) welche ihre Parameter By Ref erwarten und darüber ihre Ergebnisse zurückgeben.

Hier haperts unglücklich , von diesen Funktionen erhalte ich keine Werte, ich vermute, daß bei der Parameterübergabe was schief läuft.

Das paramarray vorher als Variable anzulegen löst das Problem leider nicht, die Objecte sind dann nach der auführung von InvokeMember immer noch NULL, bzw Nothing da ich VB nutze.

Gruß
Peter
20.12.2011 15:35 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Sebastian.Lange Sebastian.Lange ist männlich
myCSharp.de-Poweruser/ Experte

Dabei seit: 22.06.2007
Beiträge: 1.038
Entwicklungsumgebung: Visual Studio 2008, 2010
Herkunft: Berlin


Sebastian.Lange ist offline MSN-Passport-Profil von Sebastian.Lange anzeigen

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Für ref Unterstützung musst du mit der  ParameterModifierStruktur arbeiten.
20.12.2011 17:33 Beiträge des Benutzers | zu Buddylist hinzufügen
Comos User
myCSharp.de-Mitglied

Dabei seit: 20.12.2011
Beiträge: 2


Comos User ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke für den wertvollen Tip
21.12.2011 09:22 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 2 Monate.
Minski
myCSharp.de-Mitglied

Dabei seit: 08.03.2012
Beiträge: 3


Minski ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo,

Der Wrapper ist Klasse und hat mir auch schon gut geholfen...nur hab ich Probleme mit der SetProperty Funktion.
Und das an zwei Unterschiedlichen stellen : Zugriff auf Header und Font-Size

Mein Code für den Headerzugriff:

C#-Code:
//early binding: oWord.ActiveWindow.ActivePane.View.SeekView = Word.WdSeekView.wdSeekCurrentPageHeader;

Object[] objWdSeedCurrentPageHeader = new Object[1];
objWdSeedCurrentPageHeader[0] = 9;

ComObject header= word.GetObjectReturningProperty("ActiveWindow");
header= header.GetObjectReturningProperty("ActivePane");
header= header.GetObjectReturningProperty("View");
// Bis hier läuft es
header.SetProperty("SeekView", objWdSeedCurrentPageHeader);

und der Code für die Schrifgröße:

C#-Code:
ComObject Font_Size = selection.GetObjectReturningProperty("Font");
Font_Size.SetProperty("Size", new object[1] { 12 });

Fehler:

Fehlermeldung:
Ein Aufrufziel hat einen Ausnahmefehler verursacht.

Weiß jemand wo der Fehler steckt?
Irgendwie steh ich aufm Schlauch...ist bestimmt nur irgendwas kleines dummes ^^

grüße, Minski


mycsharp.de  Moderationshinweis von herbivore (14.03.2012 09:20):

Bei einer TargetInvokationException immer in die InnerException schauen, da dort die eigentliche Fehlerursache eingetragen ist.
 
13.03.2012 16:04 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Sebastian.Lange Sebastian.Lange ist männlich
myCSharp.de-Poweruser/ Experte

Dabei seit: 22.06.2007
Beiträge: 1.038
Entwicklungsumgebung: Visual Studio 2008, 2010
Herkunft: Berlin


Sebastian.Lange ist offline MSN-Passport-Profil von Sebastian.Lange anzeigen

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Du verschweigst leider was _selection ist.
Erstens Arrays mit Initialisierungsliste werden ohne fester Grösse angegeben.

Entferne:

C#-Code:
new object[1] { 12 })

und setze:

C#-Code:
new object[] { 12 })

Ich vermute mal das _selection vom Typ Word.Selection ist.
_Font.Size ist vom Typ Single und daher ersetze bitte 12 durch 12.0.

Abschliessend möchte ich dir noch das NetOffice Projekt empfehlen das auf LateBinding basiert und dich von den Low-Levels Details von LateBinding befreit.
 NetOffice - Ein versionsunabhängiger Wrapper für MS-Office

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Sebastian.Lange am 13.03.2012 17:05.

13.03.2012 17:04 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 4 Jahre.
Der letzte Beitrag ist älter als 2 Jahre.
Antwort erstellen


© Copyright 2003-2014 myCSharp.de-Team. Alle Rechte vorbehalten. 29.08.2014 20:03