Laden...

DLL in Excel-AddIn einbinden

Erstellt von Telefisch vor 15 Jahren Letzter Beitrag vor 14 Jahren 13.816 Views
T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren
DLL in Excel-AddIn einbinden

Hallo Forum...
noch ein Problem:

Ich bin dabei ein AddIn für Excel zu basteln.
Das klappt auch weitestgehend. Ich stehe allerdings im Moment vor einem für mich noch nicht nachvollziehbaren Problem:

Ich muss in mein AddIn eine DLL eines Drittanbieters einbinden.
Diese DLL kann theoretisch, abhängig von der Installation, überall auf dem Client-Rechner liegen.
Jetzt habe ich bereits Möglichkeiten geschaffen womit der User die Position eingeben kann.

Wie verweise ich jetzt auf diese DLL?
Bis jetzt passiert beim Aufruf einer Funktion, die diese DLL verwendet nichts.
Im Debug-Modus geht der Cursor nichtmal in die Funktion rein.

Mein "Drittanbieter" schreibt dazu folgendes:

probieren Sie erst mal folgendes. Suchen Sie auf der Festplatte alle .dll, die nicht im bin-Verzeichnis unserer Software liegen und löschen diese. Ich vermute, das das VS lokale Kopien angelegt hat. Diese Kopien findet die Excel-Applikation und löst dann keinen AssemblyResolve Event aus. Diese Assemblies können dann aber nicht geladen werden, weil die statisch gelinkten Abhängigkeiten nicht im Pfad liegen . Stellen Sie das Studio so ein, das keine lokalen Kopien angelegt werden.

Das hab ich erfolglos gemacht.
Dann kam als nächste Antwort:

"bei mir funktionieren dies VSTO Programme auch nicht immer. Manchmal meint .NET, es habe die Assemblies gefundenen zu haben, hat aber in Wirklichkeit die falschen und kann diese dann nicht laden."

Anbei ein Beispiel für ein kleines Offline-Programm, mit dem man eine Excel-Tabelle von außen ausliest. Ich würde vorschlagen, Sie verfrachten die Logik in ein kleines Executable, das Sie in unser bin Verzeichnis installieren. Diese exe rufen Sie dann von Ihrer Excel-Tabelle aus auf (System.Diagnostics.Process.Start(...)) und übergeben den Namen der Tabelle als Parameter ....

Das kann aber doch nicht die Lösung sein ?!

Mein Code zu den Assemblies sieht derzeit so aus:


Environment.CurrentDirectory = strBIN; //BIN-Verzeichnis
            AppDomain MyAppDomain = AppDomain.CurrentDomain;
            MyAppDomain.AssemblyResolve += new ResolveEventHandler(appDomain_AssemblyResolve);

Und der Eventhandler:


        System.Reflection.Assembly appDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly MyAssembly;
            string strTempAssmbPath = "";


            strTempAssmbPath = strBIN + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";

            //Load the assembly from the specified path. 					
            MyAssembly = Assembly.Load(strTempAssmbPath);

            //Return the loaded assembly.
            return MyAssembly;
        }

Meine Funktion müsste ja eigentlich den Eventhandler aufrufen, wenn es einen Fehler gibt.
Tut sie aber nicht.
Kann vielleicht einer von den Profis hier ein paar Kommentare zum Code Posten?

Bitte helft mir X(

Danke, Gruss Carsten

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren

Ich bin ein kleines Stück weiter...

Der Event-Handler wird angesprochen aber...
Sobald

MyAssembly = Assembly.Load(strTempAssmbPath);

durchlaufen wird, wird der Eventhandler ein zweites mal durchlaufen und dort wird beim

strTempAssmbPath = strBIN + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";

abgebrochen...???!!!

Beim ersten Durchlauf ist args.Name nur der Dateiname der DLL.
Beim zweiten Durchlauf besteht args.Name aus dem kompletten Pfad, wobei da alle Verzeichnisse durch vier!! \ getrennt sind.
Als DLL wird aber wieder die gleiche DLL gesucht.

Hilft das vielleicht weiter?

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren

...das sind die Fehlermeldungen:

Eine Ausnahme (erste Chance) des Typs "System.ArgumentOutOfRangeException" ist in mscorlib.dll aufgetreten.
Eine Ausnahme (erste Chance) des Typs "System.IO.FileLoadException" ist in DDT_ASG_EPL2003.DLL aufgetreten.
Eine Ausnahme (erste Chance) des Typs "System.Reflection.TargetInvocationException" ist in mscorlib.dll aufgetreten.

...die vermutlich mit dem völlig desolaten Datei-/Pfadnamen zusammen hängen.

Gruss Carsten

3.728 Beiträge seit 2005
vor 15 Jahren
Nachladen von Assemblies

Hallo Telefisch,

ich verstehe Deine Vorgehensweise nicht ganz.

Warum lädst Du die Assembly des Drittanbieters über Reflection?
Warum bindest Du die Assembly nicht einfach als Verweis ein und verteilst sie mit Deinem Add-In?
Warum lädst Du die Assembly in eine separate Anwendungsdomäne?

Wenn Du Assembly aus irgendwelchen Gründen nicht mitverteilen kannst, solltest Du sicherstellen, dass die Assembly samt ihrer Abhängigkeiten im GAC abgelegt wird. Dann können alle .NET Anwendungen auf diese Assembly zugreifen, ohne einen Dateipfad wissen zu müssen. Die Assembly muss dazu allerdings signiert sein und über einen starken Namen verfügen. Wenn das nicht der Fall ist, solltest Du den unprofessionellen Drittanbieter zusamenstauchen und ihn dazu verdonnern, dir eine signierte Assembly zu schicken.

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren

Hallo Rainbird,
ich lade die DLL erst beim Ausführen weil diese DLL zu einem Softwarepaket gehört und eine API-Schnittstelle für diese Software zur Verfügung stellt.
Da der Hersteller verschiedene Releases seiner Software zur Verfügung stellt, möchte ich natürlich auch nach einem Update immer auf die vom Benutzer verwendete Version zugreifen.
Es kommt vor, dass der ein oder andere User mit älteren Releases der Software arbeitet und es da Probleme zwischen API und Software geben kann.

Warum das so Programmiert ist hängt eigentlich mit den Mustern des Herstellers zusammen. Alleine hätte ich das zum jetzigen Zeitpunkt sicher nicht hinbekommen. X(

Gibt es denn einen besseren Weg die dll's einzubinden?
Ich hab versucht das Beispiel mithilfe der MSDN zu verstehen und glaube eigentlich, dass es so korrekt sein müsste. Hmm....

Meinen Drittanbieter wird es erfahrungsgemäß nicht interessieren was ich möchte daher wird zusammenstauchen nichts bringen. Mit Kundenfreundlichkeit hat der es nicht so, ist in seiner Branche aber trotzdem leider Marktführer.
Und wo wir grade dabei sind... erklär nem Anfänger wie mir doch bitte mal, was ein GAC ist und wie ich feststellen kann, ob die DLL's signiert sind.
Ich könnte sie dann ja vielleicht programmtechnisch dort hin kopieren und registrieren oder ?

Danke,Gruss Carsten

3.728 Beiträge seit 2005
vor 15 Jahren
Fremd-API aufrufen

Hallo Telefisch,

jetzt verstehe ich, was Du machen willst. Die API des Fremdherstellers kann auf verschiedenen Clients in unterschiedlichen Versionen vorliegen. Dein Add-In soll aber mit verschiedenen Versionen der Fremdsoftware zusammenarbeiten.

Ich würde den Pfad zur Einstiegs-DLL der Fremd-API in der Konfigurationsdatei des Add-Ins hinterlegen. Verwende besser die Assembly.LoadFrom-Methode, statt Assembly.Load. LoadForm lädt auch automatisch alle Abhängigkeiten. Das wird Deine Probleme höchstwahrscheinlich lösen.

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren

Hallo Rainbird...
nachdem ich wiedermal gezwungen wurde mich mit access zu beschäftigen gehts jetzt endlich wieder an mein AddIn...

Im Augenblick löse ich es so, dass der Pfad zu den DLLs in der Registry LocalMachine abgelegt wird.
dem füge ich dann ja die gewünschte DLL zur Laufzeit hinzu.

Hier ist strBIN der Pfad:

strTempAssmbPath = strBIN + @"" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";

Momentan sieht der Pfad danach so aus:

"C:\Programme\EPLAN\Electric P8\1.8.5\BIN\Eplan.EplApi.Systemu.dll"

nächster Schritt wäre dann:

MyAssembly = Assembly.LoadFrom(strTempAssmbPath);

Wenn ich im Debug-Modus auf diesen Schritt treffe, wird der Programmverlauf völlig emotionslos beendet.

Folgende Meldungen bekomme ich im Ausgabefenster:

Eine Ausnahme (erste Chance) des Typs "System.Security.Policy.PolicyException" ist in mscorlib.dll aufgetreten.
Eine Ausnahme (erste Chance) des Typs "System.IO.FileLoadException" ist in mscorlib.dll aufgetreten.
Eine Ausnahme (erste Chance) des Typs "System.IO.FileLoadException" ist in DDT_ASG_EPL2003.DLL aufgetreten.
Eine Ausnahme (erste Chance) des Typs "System.Reflection.TargetInvocationException" ist in mscorlib.dll aufgetreten.

Die Datei existiert, soviel kann ich garantieren.

Hmm... bin völlig ratlos. Kann das vielleicht wirklich ein .NET-Problem sein?

3.728 Beiträge seit 2005
vor 15 Jahren
CAS Policy

Hallo Telefisch,

ich würde mal die CAS-Richtlinien überprüfen. Um zu Testen, ob es daran liegt, gibtst Du temporär der Codegruppe "All Code" den Berechtigungssatz Fulltrust.

Ich hatte kürzlich ein ähnliches Problem mit einer Drittanbieter Assembly, die mit dem .NET Framework 1.1 kompiliert wurde. Unter .NET 2.0 wollte sie einfach nicht laufen. Umschifft wurde das Problem am Ende durch eine .NET 1.1 Konsolenanwendung, die per Process.Start von der .NET 2.0 Anwendung mit Parametern aufgerufen wurde. Ist zwar hässlich, aber jetzt läuft das Ding.

Hast Du Dir den Code der Drittanbieter-Assembly mal mit dem .NET Reflector angesehen? Wenn Du den Code siehst, kommst Du auch am ehesten darauf, warum etwas nicht wie gewünscht funktioniert.

Du kannst den Reflector hier kostenlos runterladen: http://www.aisto.com/roeder/dotnet/

Hat mir einmal z.B. geholfen einen Memory Leak Bug in Crystal Reports zu umschiffen.

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren
ratlos

hmm...

ich fürchte hier verlasse ich grade die Verständniskurve.

Ich finde nichts aussagekräftiges über die Richtlinien. Ich denke es handelt sich hier um die Richtlinien des Systems.
Dort finde ich aber den Punkt All Code nicht (auch nichts deutsches, Ähnliches).

Ich sehe schon, ich komme wohl um die Version mit der zweiten exe nicht herum.

Naja, da hat Microsoft noch Verbesserungspotenzial.

Ich dank Dir auf jeden Fall für Deine Bemühungen, Rainbird.

LG Carsten

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren
Konsolenprogramm

Hallo Rainbird,
vielleicht könntest Du mir noch einen Tipp geben, wie ich eine weitere exe aus meinem Plugin anspreche und vor allem, wie man das Programmiert (debugging und deklaration etc.).

Ich stelle mir das jetzt so vor, dass ich eine weitere exe bastel in der alle Routinen enthalten sind, die die API verwenden. Diese müsste dann ja vermutlich im API-Verzeichnis des Drittanbieters installiert werden.
Am liebsten würde ich diese exe dann wie eine Klasse verwenden. Geht das?
Sowas hab ich noch nie gemacht.

Danke im Voraus...

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren
Dll

Also....
ich habe zuerst mal ein neues Projekt in meiner Projektmappe angelegt (DLL).
Zum Testen hab ich einfach mal ne Variable in der DLL gesetzt und wieder ausgelesen.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDT.API
{
    public class Class1
    {
        private string mWert = "";
        public string Wert
        {
            get
            {
                return mWert;
            }
            set
            {
                mWert = value;
            }
        }
    }
}

Ich habe aber das gleiche Problem wie mit der API.
Sobald die Funktion, die die Klasse ansprechen soll aufgerufen wird, komme ich ins AssemblyResolve-Ereignis.

Wie ist denn normalerweise der Workflow, wenn man eine DLL erstellen will?
Gibt es eine Möglichkeit die DLL beim Starten des Debuggers in das API-Verzeichnis meines Anbieters zu kopieren?
Wie muss das Environment.Directory eingestellt werden?
Auf das API-Verzeichnis oder auf das AddIn-Verzeichnis oder auf Excel?

Ich habe in meiner Solution die DLL und das "Testprogramm". Im Testprogramm hab ich das Projekt (DLL) als Verwies eingefügt.
Irgend etwas läuft da aber schief, dass die DLL nicht geladen werden kann.

Boah bin ich zu blöd oder läuft hier was nicht richtig?

3.728 Beiträge seit 2005
vor 15 Jahren
Reflection

Hallo Telefisch,

vergiss dieses AssemblyResolve-Ereignis. Du brauchst das nur, wenn Du das Laden der Assemblies selber unter kontrolle haben willst/musst. Das ist aber in Deinem Fall nicht nötig, da Du ja nichts weiter tun willst, als eine externe Assembly per Reflection zu laden und deren Typen zu verwenden.

Bevor ich noch viel erkläre, habe ich ein keines Snippet geschrieben, welches genau das macht, was Du brauchst, um Deine Drittanbieter-DLL zu laden:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Security;
using System.Security.Policy;

namespace Rainbird.Examples.Reflection
{
    /// <summary>
    /// Lädt Typen aus Assemblies dynamisch zur Laufzeit.
    /// </summary>
    public static class ExternalTypeHelper
    {
        /// <summary>
        /// Erzeugt eine Instanz eines Typs in einer externen Assembly.
        /// </summary>
        /// <param name="assemblyPath">Vollständiger Pfad zur Assembly-Datei</param>
        /// <param name="typeName">Vollständiger Typenname (z.B. Rainbird.Examples.Test.SomeClass)</param>
        /// <returns>Objektinstanz</returns>
        public static object CreateInstanceFromExternalType(string assemblyPath, string typeName)
        {
            // Externe Assembly laden
            Assembly externalAssembly=Assembly.LoadFrom(assemblyPath, new Evidence(Assembly.GetExecutingAssembly().Evidence));

            // Typinformationen abrufen
            Type externalType = externalAssembly.GetType(typeName);

            // Instanz erzeugen und zurückgeben
            return Activator.CreateInstance(externalType);
        }
    }
}

Diese Helferklasse wird folgendermaßen angewendet:


// Instanz erzeugen
object apiObject=ExternalTypeHelper.CreateInstanceFromExternalType("c:\test\xyz.dll","Hello.World");

// Funktion "SomeFunction" mit zwei Paramatern aufrufen
apiObject.GetType().InvokeMember("SomeFunction", BindingFlags.InvokeMethod, null, apiObject, new object[2] { "Parameter1", "Parameter2" });

Wie Environment.Directory eingestellt ist, spielt absolut keine Rolle.

Noch Fragen?

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 15 Jahren

Das kapier ich nicht....

So sollte es sein:


System.Object m_oEplApp = null;

        private void ThisWorkbook_Open()
        {
            EplApplication oEplApp = null;
            if (m_oEplApp != null)
            {
                oEplApp = m_oEplApp as EplApplication;
            }
            else
            {
                oEplApp = new EplApplication();
                try
                {
                    oEplApp.Init("", true);
                    m_oEplApp = oEplApp;
                }
                catch (System.Exception excp)
                {

                    string sTest = excp.ToString();
                   
                }
            }
        }

Wie soll so ein Beispiel denn dann aussehen?

3.728 Beiträge seit 2005
vor 15 Jahren
Frühe oder Späte Bindung

Dein Beispiel verwendet frühe Bindung:

oEplApp = new EplApplication();

Dafür ist ein Verweis auf die Drittanbieter-Assembly im Projekt zwingend erforderlich. Du hattest aber oben gesagt, dass Du die Assembly nicht mitverteilen darfst/kannst und sie aus einem Pfad irgendwo zur Laufzeit laden musst. Das geht nur mit Später Bindung (über Reflection). In diesem Fall hat Dein Visual Stduio Projekt keinen Verweis auf die Assembly des Drittanbieters. Die Klasse EplApplication kann dann aber nicht direkt im Code verwendet werden. Du musst dann so programmieren, wie in meinem Beispiel Snippet. Das ist wesentlich komplizierter und es gibt dabei auch keine Intellisense (da ja kein Verweis und damit keine Typeninformationen bekannt sind).

Ich kann mir aber nicht vorstellen, dass Du die API zwingtend über Späte Bindung ansprechen musst. Du hast bestimmt was falsch verstanden. Im Normalfall nimmst Du die Assembly einfach als Verweis in Dein Add-In-Projekt auf und kannst sie direkt benutzen. Eine Kopie der Assembly wird dann automatisch in das Ausgabeverzeichnis (bin\Debug bzw. bin\Release) Deines Projekts kopiert. Oder die Assembly liegt im GAC. Dann spielt ihr Speicherort gar keine Rolle.

Dein Beispiel würde mit Reflection und meiner Hilfsklasse etwa so aussehen:


// Pfad zur Eplan-Assembly
string apiPath=@"C:\Programme\EPLAN\Electric P8\1.8.5\BIN\Eplan.EplApi.Systemu.dll";

// Instanz von EplApplication erzeugen
// Folgende Zeile entspricht: object oEplApp=new EplApplication();
object oEplApp=ExternalTypeHelper.CreateInstanceFromExternalType(apiPath,"Eplan.EplApi.EplApplication");

// Funktion "Init" mit zwei Paramatern aufrufen
// Folgende Zeile entspricht: oEplApp.Init("",true);
oEplApp.GetType().InvokeMember("Init", BindingFlags.InvokeMethod, null, oEplApp, new object[2] { "", true });

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

Hallo Rainbird...
nach einigen anderen kleineren Projekten muss ich mich doch wieder diesem üblen Thema widmen...

Ich habe mein AddIn mittlerweile zum Laufen bekommen, allerdings mit eingebundenen Assemblies.
Nun muss das ganze auf späte Bindung umgebaut werden.

Dazu könntest Du mir vielleicht noch einmal einen kleinen Schubser geben...

Folgender Code soll zum Beispiel auf späte Bindung umgebaut werden:

private Project oProject;

private bool OpenProject( string DestinationProject )
        {
            try
            {
                if ( oProject != null && oProject.Properties.PROJ_FULL_PROJECTPATH != DestinationProject )
                {
                    oProject.Close();
                    oProject = null;
                }

                if ( oProject == null && EplApp != null )
                {
                    using ( LockingStep oLS = new LockingStep() )
                    {

                        if ( new ProjectManager().ExistsProject( DestinationProject ) )
                        {
                            oProject = new ProjectManager().OpenProject( DestinationProject );
                            
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
                else
                    return true;
            }
            catch ( Exception ex )
            {
                throw ex;
            }
        }

die private Variable oProject würde ich zunächst erst einmal als Object deklarieren. Richtig?
Wenn oProject dann einmal richtig erzeugt ist, kann ich dann einfach auf Beispielsweise oProject.Properties zugreifen oder wie muss das dann aussehen?

Boah irgendwie kapier ich das garnicht (hatte grade versucht zu coden, was ich verstanden hab, komme aber schon mit oProject = new ProjectManager().OpenProject(DestinationProject) überhaupt nicht klar) 🙁

Könntest Du mir vielleicht nochmal den o.a. Code umbauen, bitte?
vielleicht vertsehe ich das dann für meinen restlichen Code selbst umzubauen ...

Sorry, dass ich mich so dämlich anstelle 🤔

tnx Carsten

3.728 Beiträge seit 2005
vor 14 Jahren
Hausaufgaben

Hallo Telefisch,

ich helfe Dir gerne weiter, aber ich mach' Dir nicht die Hausaufgaben. Die Technologie für Späte Bindung heißt Reflection. Hier steht alles darüber, was Du wissen musst: http://msdn.microsoft.com/en-us/library/cxz4wk15.aspx

Es ist gefährlich, sich Code von jemand anderen schreiben zu lassen und diesen dann in einem Projekt zu verwenden, ohne ihn selber verstanden zu haben. Spätestens wenn die Anwendung gewartet oder erweitert werden muss, fällst Du damit auf die Nase. Arbeite Dich lieber in die entsprechenden Technologieen selber ordentlich ein. Nur wer sich weiterentwickelt, hat auf Dauer Erfolg. In wirtschaftlich schlechten Zeiten ganz besonders.

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

Jaja, ist schon klar, hast ja auch Recht.
So sollte das nicht aussehen.
Ich dachte nur, dass es so etwas leichter verständlich für mich wird.

Wenn ich zum Beispiel folgende Zeile sehe:
using ( LockingStep oLS = new LockingStep() )
sehe ich es richtig, dass ich hier dann

using ( Object oLS = ExternalTypeHelper.CreateInstanceFromExternalType(apiPath + "\\Eplan.EplApi.DataModelu.dll","Eplan.EplApi.DataModel.Lockingstep"))

verwende?

Und für diese Zeile muss ich dann wohl erst auf dem gleichen Weg ein ProjectManager-Object erzeugen, bevor ich dann auf ExistsProject und Openproject zugreifen kann?

if ( new ProjectManager().ExistsProject( DestinationProject ) )
                        {
                            oProject = new ProjectManager().OpenProject( DestinationProject );

Wie gesagt, Du sollst mir nicht die Hausaufgaben machen nur ist es etwas problematisch für mich das, naja, doch ziemlich abstrakte denken gleich auf die ganze Klasse zu übertragen weil zwischendurch debuggen geht nunmal nicht.
Und dann ist es etwas blöd, wenn in der gesamen Klasse das oProject nicht passt.
(und die ganzen anderen Methodenaufrufe, Objekte und Eigenschaften.)
Keine Sorge, Mein Freund (Visual C# Kompendium) und ich arbeiten schon dran. Ist halt schön, wenn man weiß, dass man auf dem richtigen Weg ist 😉

Deswegen wollte ich nur einen Hinweis für den richtigen Weg.

tnx Carsten

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

Sodele...
hab mittlerweile rausgefunden dass using offensichtlich gar nicht funktioniert.

Wenn ich die using-Anweisung richtig verstanden habe dient sie ja lediglich dazu, das Objekt nach Verwendung direkt zu zerstören.
Wenn die Prozedur aber verlassen wird, wird die lokale Variable doch eh vom Garbage-Collector zwerstört, richtig?
Dann kann ich mir das using und ein eventuelles manuelles disposen doch sparen oder?

tnx Carsten

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

...nächstes Problem...

foreach-Schleifen

Wie kann man sowas übersetzen?


foreach ( Placement placement in windowMacro.Objects )

sowohl Placement als auch windowMacro gehören zu den spät gebundenen Assemblys.
🤔

EDIT:
Hier mein funktionierender Versuch:


//Original:
                    Hashtable tableVars = new Hashtable();
                    using ( LockingStep oLS = new LockingStep() )
                    {
                        Insert oEplInsert = new Insert();   //Macro
                        #region Fenstermakro lesen
                        if ( Path.GetExtension( MacroName ) == ".ema" )
                        {
                            //Window Macro
                            WindowMacro windowMacro = new WindowMacro();
                            windowMacro.Open( MacroName, oProject );
                            foreach ( Placement placement in windowMacro.Objects )
                            {
                                if ( placement is PlaceHolder )
                                {
                                    PlaceHolder placeHolder = placement as PlaceHolder;
                                    int i = 0;
                                    foreach ( string sVarName in placeHolder.VariableNames )
                                    {
                                        if ( !tableVars.ContainsKey( sVarName ) )
                                        {
                                            tableVars.Add( i, sVarName );
                                            i++;
                                        }
                                    }
                                }
                            }
                            return tableVars;
                        }
                        #endregion


//späte Bindung
                    Hashtable tableVars = new Hashtable();
                    Object oLS = ExternalTypeHelper.CreateInstanceFromExternalType( strApiFolder + "\\Eplan.EplApi.DataModelu.dll", "Eplan.EplApi.DataModel.LockingStep" );
                    {
                        #region Fenstermakro lesen
                        if ( Path.GetExtension( MacroName ) == ".ema" )
                        {
                            //Window Macro
                            Object windowMacro = ExternalTypeHelper.CreateInstanceFromExternalType( strApiFolder + "\\Eplan.EplApi.DataModelu.dll", "Eplan.EplApi.DataModel.MasterData.WindowMacro" );
                            windowMacro.GetType().InvokeMember( "Open", BindingFlags.InvokeMethod, null, windowMacro, new object[2] { MacroName, oProject } );
                            
                            object placeHolders = windowMacro.GetType().InvokeMember( "PlaceHolders", BindingFlags.GetProperty, null, windowMacro, null );
                            int placeHoldersCount;
                            if ( placeHolders is Array )
                            {
                                placeHoldersCount = ( ( Array )placeHolders ).Length;
                                for ( int icount = 0; icount < placeHoldersCount; icount++ )
                                {
                                    Object placeHolder =((Array) placeHolders).GetValue(icount);
                                    int i = 0;
                                    object varNames = placeHolder.GetType().InvokeMember( "VariableNames", BindingFlags.GetProperty, null, placeHolder, null );
                                    int varsCount;
                                    if ( varNames is StringCollection )
                                    {
                                        varsCount = ( ( StringCollection )varNames ).Count;
                                        for ( int iVar = 0; iVar < varsCount; iVar++ )
                                        {

                                            if ( !tableVars.ContainsKey( ( ( StringCollection )varNames )[iVar] ) )
                                            {
                                                tableVars.Add( i, ( ( StringCollection )varNames )[iVar] );
                                                i++;
                                            }
                                        }
                                    }
                                }
                            }
                            return tableVars;
                        }
                    

Aber das ganze kommt mir ein wenig unelegant vor...
Könnte da bitte mal jemand drüber schauen und mir sagen, wo es noch was zu verbessern gibt?
Danke Carsten

3.728 Beiträge seit 2005
vor 14 Jahren
Egelanz

Hallo Telefisch,

das sieht gut aus. Ich würde allerdings fast jede Zeile kommentieren. Das ist bei Reflection-Code besonders wichtig, da es für Kollegen (oder auch einem selbst, wenn man ein halbes jahr später nochmal damit zu tun hat) viel leichter ist, den Code zu verstehen.

Eleganter wird es nicht. Reflection sieht meistens nicht elegant aus. Das ist eben so. Das stört den Endanwender und meistens auch den Chef nicht, wenn es am Ende funktioniert.

Schön dass Du es doch selber hinbekommen hast.

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

Ok, wenn's ansonsten in Ordnung ist, bin ich zufrieden.

Ein Problem habe ich aber noch...
wie komme ich an Enumerationen dran?

Beispiel : Eplan.EplApi.DataModel.MasterData.PageMacro.Enums.NumerationMode.None
Einfach die Enumeration nachbilden wird ja wohl nicht gehen oder?

tnx Carsten

3.728 Beiträge seit 2005
vor 14 Jahren
enums

probier es mal so:


Array enumValues = Enum.GetValues(enumType);

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

Hallo Rainbird,
ich muss diesen Threat mal wieder ausgraben...
Ich habe meine komplette Anwendung mittlerweile zum Laufen gebracht und nach einigen Problemen mit der Setup-Routine auch das hinbekommen.
Alles lief bei mir einwandfrei, bis ein Kollege jetzt auf seinem System das Add-In probieren wollte.
Teile der spät gebundenen Assemblies funktionieren aber bei einigen dll's schmeisst mir :

public static object CreateInstanceFromExternalType( string assemblyPath, string typeName )
        {
            try
            {
                // Externe Assembly laden
                Assembly externalAssembly = Assembly.LoadFrom( assemblyPath, new Evidence( Assembly.GetExecutingAssembly().Evidence ) );

                // Typinformationen abrufen
                Type externalType = externalAssembly.GetType( typeName );
                
                // Instanz erzeugen und zurückgeben
                return Activator.CreateInstance( externalType );
            }
            catch ( Exception ex ) { throw ex; }
        }

eine FileNotFound Exception in der Zeile wo die Assembly geladen werden soll.
Das selbe Phänomen konnte ich jetzt auch auf meinem Rechner verfolgen, allerdings tritt das nicht immer auf.
Woran könnte das liegen? (Die Dateien existieren!)

Danke und Gruss
Carsten

3.728 Beiträge seit 2005
vor 14 Jahren
Abhängigkeiten

Hallo Telefisch,

vermutlich haben die, über Reflection geladenen, Assemblies Abhängigkeiten, die nicht geladen werden können. Das kann z.B. vorkommen, wenn eine Assembly von Assemblies abhängig ist, die im GAC liegen. Wenn Du Deine Anwendung nun aber z.B. mit XCopy (also durch schlichtes kopieren auf einen anderen Rechner) ausrollst, werden die erforderlichen in Assemblies im GAC nicht mitgenommen.

Du solltest die Ausnahme nochmal genau ansehen (auch die InnerExceptions) und nachsehen, welche Assembly er genau nicht findet.

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

Hi Rainbird...
ich habe es nun mehrfach wieder versucht, kann den Fehler aber nicht erneut auf meinem Rechner erzeugen.
Also die gesuchte assembly ist in jedem Falle durch den Hersteller installiert (zumindest kann ich das in diesem Fall garantieren).

Als ich den Fehler hatte, konnte eine defakto vorhandene Datei nicht gefunden werden.
Hier ist noch ein Kommentar zu diesen Problemen vom Hersteller:

Die Problematik ist darin begründet, dass das P8 API managed C++ ist. Es bietet nach außen hin eine .Net-Schnittstelle und ist nach innen unmanaged C++. Sie laden also im Endeffekt die nativen dlls von P8 in Ihren Prozess und dazu müssen diese Im Pfad liegen.

Offensichtlich hat die Assembly Probleme mit Abhängigkeiten, wenn die dll's irgendwo doppelt vorkommen aber auch das kann ich in diesem Fall ausschließen 🙁
Wie gesagt, im Augenblick läuft es wieder mal bei mir 🙁

T
Telefisch Themenstarter:in
375 Beiträge seit 2008
vor 14 Jahren

Hallo...
ich habe heute noch einmal mit der Hotline des Herstellers gesprochen und wieder kam die Diskussion auf, dass Excel schuld sei.
Offensichtlich macht Excel beim Laden der DLLs eine lokale kopie hiervon und da fehlen dann die statischen Abhängigkeiten 😦

Ich habe also mal FUSLOGVW.exe bemüht und bin tatsächlich auf diesen Fehler hier gestoßen:

LOG: Explizite IJW-Bindung. Dateipfad:C:\Programme\EPLAN\Electric P8\1.9.10\BIN\Eplan.EplApi.AFu.dll.
LOG: Die IJW-Assemblybindung hat einen anderen Pfad zurückgegeben: C:\Dokumente und Einstellungen\Fischer\Lokale Einstellungen\Anwendungsdaten\assembly\dl3\9HVVD91E.RWZ\OCAMDONH.VM7\2b776793\0064222f_3199c901\Eplan.EplApi.AFu.dll. Verwenden Sie die gelieferte Datei.

Kennt vielleicht jemand eine Möglichkeit Excel dieses Verhalten ab zu gewöhnen?

Danke Tele