Wie erzeuge ich im VS 2005.net eine DLL/OCX für Navision

3. November 2006 16:02

Hallo Zusammen ich würde gerne ein Custem Control für Navision erzeugen. Das ganze muss im Visual Studio 2005 .NET geschehen, am besten wär C# - muss aber nicht.

Leider schaffe ich es nicht meine DLL (eine 'Hello World' Messagebox) in Navision einzubinden. Hat jemand vielleicht ein einfaches Codebeispie, bzw. eine Anleitung oder ähnliches für mich?

3. November 2006 19:18

Das ist eigentlich relativ simpel:

Erstell ein neues Projekt unter VS2005 vom Typ "Class Library", in den Eigenschaften des Projekts setzt du unter Application => Assembly Information... "Make assembly COM-visible" einen Haken, weiter in den Eigenschaften unter Signing ein Haken in "Sign the assembly", dann in dem Dropdown auf <New>, dort gibst du dann z.B. HelloWorldCom.snk.

Als Code dann etwas in dem Stil:

Code:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace HelloWorldCom
{
   [Guid("B80E18FA-A733-4eaf-90ED-C9B052F46F64")]
   public interface IHelloWorldCom
   {
      [DispId(0x8001)]
      int SayHelloWorld(string name);
   }

   [Guid("8715F0A3-48B4-4aed-AE98-3D9B35367FEB"),
   ClassInterface(ClassInterfaceType.None)]
   public class HelloWorldCom : IHelloWorldCom
   {
      public int SayHelloWorld(string name)
      {
         MessageBox.Show(string.Format("Hello world {0}", name));

         return 42;
      }
   }
}


Anschließend noch das assembly für COM registrieren und am besten in den GAC kopieren. Das machst du so:

Code:
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe" HelloWorldCom.dll /tlb:HelloWorldCom.tlb
"C:\Programme\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /i HelloWorldCom.dll


Zum entfernen aus dem GAC und deregistrieren folgendes:

Code:
"C:\Programme\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /u HelloWorldCom.dll
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe" HelloWorldCom.dll /unregister /tlb:HelloWorldCom.tlb
del HelloWorldCom.tlb


Nun noch kurz zum mitschreiben:

COM-visible, damit erstmal das ganze Assembly für COM zugänglich wird.
Das Signing (snk) mit einem "Strong Name", damit das Assembly im GAC gespeichert werden darf. Es funktioniert auch im Navision Client Verzeichnis ohne es in den GAC zu schreiben und ohne Strong Name. Ich "verschmutze" aber ungern Applikationsverzeichnisse.

Im Code gibt es ein Interface für COM und eine Klasse die dieses Interface implementiert. Beiden wird jeweils eine frische GUID zugewiesen. Die DispId dient der "Durchnumerierung" innerhalb von COM.

Mit einfachen Datentypen ist die Übergabe recht einfach. Im Beispiel ein String und ein Integer. Komplexe Datentypen erfordern hier Marshalling (google).

Ich hoffe die Kurzform ist für den Anfang ausreichend.

P.S.: Hier noch der Navision Code:

Code:

CREATE(HelloWorldCom);

i := HelloWorldCom.SayHelloWorld('MyCom');
MESSAGE('COM returned ' + FORMAT(i));

CLEAR(HelloWorldCom);

5. November 2006 11:07

Supergeil - das geht ja. Klasse, dass hab ich mir seit 2 Jahren schon vorgenommen. Ganz versteh ich die sache noch nicht, aber den rest kann ich googlen und mir mit try and error erschließen. Danke

5. November 2006 12:11

Hallo "Namensvetter" ;-)

Solltest du "den Rest" bei Google & Co. gefunden haben, so würden wir uns alle hier sicherlich darüber freuen, wenn du uns daran teilhaben lässt.
So hätten dann alle was davon und diese Community wird zu dem deutschsprachigen Nachschlagewerk rund um die Microsoft Dynamics Produkte.

Wir danken dir dafür schonmal im Voraus.

5. November 2006 13:48

werde versuchen noch dass ein oder andere zu ergänzen - im Moment arbeite ich dann aber an meinem nicht Navisionteil des Projekts und dass ist hier net so interessant

Bei Mibuso hab ich auch auf dass selbe Posting ein paar Tipps bekommen, vielleicht helfen die ja dem ein oder anderen weiter (der keine Angst vor Englisch hat)

http://www.mibuso.com/forum/viewtopic.php?t=14571

5. November 2006 15:08

Es führen immer viele Wege nach Rom, so auch hier.

Die aufgezeigte Lösung, zugegeben nicht gerade gespickt mit Informationen, erzeugt eine von NAV oder anderen Programmen die Automations einbinden können benutzbare DLL die nur noch um die eigenen Funktionen erweitert werden muss. Über das gesamte Thema, alleine COM unter .NET, könnte man Bücher schreiben.

Oft sind es aber nur die Kleinigkeiten wie oben, die einen davon abhalten seine großen Ideen umzusetzen, deshalb wollte ich es vorerst dabei belassen :-)

Deine Fragen kannst du auch hier stellen, dann kann man aus dem Thread ggf. ein komplettes How-To bauen.

10. November 2006 21:11

soweit sogut, ich hab jetzt etwas damit rumgespielt und komm ganz gut zurecht, leider hab ich jetzt folgendes Problem.

Wie bekommt denn dass Navision mit dass in der DLL etwas passiert ist? Im Moment hab ich es so gemacht, dass ich aus der DLL ein Fenster erzeugt hab und dieses wird beim Laden per Knopfdruck in navision geöffnet. Beim Schließen des C#-Fensters bekommt Navision einen Rückgabewert und kann an hand von dem Rückgabewert irgend etwas machen. Dass hat den nachteil, dass navision aber die Verarbeitung eintellt, bis es den Rückgabewert erhält, sprich beim Aufruf der Automationvariablen Wartet bis was zurück kommt.

Wie hab ich denn die möglichkeit in Navision einen Event zu "programmieren"? Im Zusammenhang mit dem Application Server hab ich sowas schonmal gesehen.

hat da jemand ne idee?

11. November 2006 16:58

Das ist ebenfalls recht einfach. Hier der erweiterte C# Code:

Code:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace HelloWorldCom
{
   [ComVisible(false)]
   public delegate void HelloWorldComEventHandler(string eventText);

   [Guid("B80E18FA-A733-4eaf-90ED-C9B052F46F64")]
   public interface IHelloWorldCom
   {
      [DispId(0x8001)]
      int SayHelloWorld(string name);

      [DispId(0x8002)]
      void StartSomething();
   }

   [Guid("48939794-243B-44c5-A992-42D5E0DAF76A"),
   InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
   public interface IHelloWorldComEventHandler
   {
      [DispIdAttribute(0x9001)]
      void SomeEvent(string eventText);
   }

   [Guid("8715F0A3-48B4-4aed-AE98-3D9B35367FEB"),
   ClassInterface(ClassInterfaceType.None),
   ComSourceInterfaces(typeof(IHelloWorldComEventHandler))]
   public class HelloWorldCom : IHelloWorldCom
   {
      event HelloWorldComEventHandler SomeEvent;

      public HelloWorldCom()
      {
      }

      public int SayHelloWorld(string name)
      {
         MessageBox.Show(string.Format("Hello world {0}", name));

         return 42;
      }

      public void StartSomething()
      {
         this.SomeEvent(
            string.Format("Time 1: {0}.", System.DateTime.Now));

         for (int i = 0; i < 100000; i++)
            ;

         this.SomeEvent(
            string.Format("Time 2: {0}.", System.DateTime.Now));

         for (int i = 0; i < 100000; i++)
            ;
      }
   }
}


Die wichtigsten Änderungen sind ein Delegate für das Event, ein weiteres Interface als Dspatch deklariert für den Event, die Definition von ComSourceInterfaces() für das neue Interface uind halt der Aufruf des Event.

Code für Navision:

Code:
-- OnRun()
CREATE(HelloWorldCom);

i := HelloWorldCom.SayHelloWorld('MyCom');
MESSAGE('COM returned ' + FORMAT(i));

HelloWorldCom.StartSomething();

MESSAGE ('Last message!');

CLEAR(HelloWorldCom);

-- HelloWorldCom::SomeEvent(eventText : Text[1024])
MESSAGE('Event Text: ' + eventText);


Wichtig ist hier, in den Properties der Automation WithEvents=Yes anzugeben.

Zunächst wird wie gehabt die COM Message ausgegeben, dann die Navision Message mit dem Rückgabewert, anschließend 2 Messages aus dem Event und zum Schluß die "Last message!" um zu demonstrieren, dass der Event auch wirklich ausgeführt wird.

Gute Informationen zu den einzelnen Attributen finden sich im MSDN. Es ist schon wichtig zu wissen, was genau passiert. Aber das würde halt diesen Thread sprengen. Außerdem bin ich Samstags immer etwas faul ;-)

Algemein vielleicht noch: Die verwendeten GUIDs sind zwar nötig, aber es müssen nicht diese sein. Die dienen nur zur Identifikation.

11. November 2006 17:37

Danke - ich werd dass im Lauf der Woche gleich mal ausprobieren, weiß aber noch net wann - wohl kauf vor Mittwoch, ich meld mich wieder wenn dass geklappt hat

13. November 2006 12:22

Hi,
Ich habe ein Problem mit meiner Schnittstelle immer wenn ich mehr als eine Variable vom Typ
Code:
string
als Parameter verwende
Code:
SayHelloWorld(string name, string name)
und in Navision beiden eine Variabel vom Typ Text aufrufe
Code:
TEXTOCX.SayHelloWorld(VarText01,VarText02);
stürtz mein Navision Client beim kompilieren ab!
Kennt jemand von Euch dieses Verhalten?

13. November 2006 12:41

ich würde als erst mal vermuten, dass in deiner Funktion die Parameter

SayHelloWorld(string name, string name)

nicht gleich heißen dürfen. Mach doch mal

SayHelloWorld(string name, string name2)

vielleicht war es dass schon

24. November 2006 16:05

Hallo Leute,

ihr habt ja alle viel Ahnung doch leider ich nicht!

Ich versuche gerade das Codebeispiel von SilverX in C# umzusetzten,

habe auch alles registriert und so weiter, doch navision kann mir keine Instanz erstellen, was mache ich verkehrt, oder habe ich was vergessen??

Ich habe den Code eins-zu-eins kopiert und immer die blöde Fehlermeldung!

Vielen Dank für Hilfreiche Tips

Guido, (C# Anfänger)

24. November 2006 18:12

Halllloooo,

hat sich erledigt!! Habs doch noch hinbekommen!!

Servus und ein Schönes Wochenende!

24. November 2006 19:22

Hallo Roger, und hallo an alle anderen!

Tja die Lösung war ganz einfach! Ich hätte den Text von SilverX besser lesen sollen! Er hatte ja (ganz oben) alles Haarklein erklärt! Ich habe schlicht und einfach die Eigenschaften des Projekts nicht ganz richtig eingestellt!

Nun habe ich es genauso gemacht wie SilverX es beschrieben hat!

Grüße
Guido

4. Oktober 2007 14:14

Das Thema ist fast perfekt für meine Frage :)

Ich habe ein ähnliches Problem wie timo es hatte. Nur ist die Sache die, wenn ich der Variable den Wert aus meiner DLL übergeben möchte sagt er mir das die Anweisung zu komplex ist und das ich sie reduzieren soll. Die DLL Methode hat viele Parameter so dass der Aufruf über 3 Zeilen geht. Geht es generell nur mit einem Parameter? Oder darf der Aufruf nur eine Zeile betragen?

Zur Verdeutlichung mal der Code

Code:
IF ISCLEAR(Catalog) THEN
CREATE(Catalog);

Pos := STRPOS(Customer.Name,' ');
Vorname := COPYSTR(Customer.Name,1,Pos-1);
Name := COPYSTR(Customer.Name,Pos+1);

GUI := Customer."Kundennr. Shop";
CLEAR(GUI);

REPEAT

GUI := Catalog.CreateUserProfile('C:\Dokumente und Einstellungen\alle\Desktop\Pending\',GUI,'',
'',Customer."E-Mail",'','',Customer."Fax No.",Customer."Phone No.",Vorname,Name,
'',Customer.Address,Customer."Country Code",Customer."Post Code",Customer.City,Customer.Geburtsdatum,'');

UNTIL Customer.NEXT = 0;

MESSAGE('%1',GUI);


Kann mir da jemand weiter helfen?

Nachtrag:

Wir haben nochmal damit rummprobiert. Er bringt den Fehlern bei allen 18 Parametern. Nehm ich jedoch einen Parameter weg (also 17) funktioniert es einwandfrei. Weiß einer warum?

5. Oktober 2007 08:52

Ich muss vorwegnehmen, das ich keine Ahnung habe von c#.

Aber kann es sein das die "Parameterlänge" 256 zeichen nicht überschreiten darf? ich kenne das aus vielen anderen Programmiersprachen, das es da eine Grenze bei der Übergabe der Parameter gibt.

Zähl doch einfach mal nach...entweder den aufruf, oder nachher die Länge deiner Variablen alle zusammen....

5. Oktober 2007 09:00

Wir haben das jetzt ander gelst indem wir mehrere Methoden gemacht haben mit weniger Variabeln. Das ging auf die schnelle auch.

Ich hatte bei mibuso Gestern noch recherchiert und es scheint wohl auf eine gewisse Anzahl von Zeichen festgelegt zu sein. Zumindest wird das vermutet weil in einigen Navisioneigenen Code Teilen mal 15 Parameter, mal 18 Parameter und mal 20 Parameter übergeben werden und sobald man eins hinzufügt kommt die Fehlermeldung.

Also gut mglich das es 256 Zeichen sind. Genaues kann ich leider auch nix sagen. Und bei mibuso wusste auch keiner eine genaue Antwort.

Frage

12. Oktober 2007 20:07

Hallo zusammen,

ich möchte diesen Thread nutzen, da ich ein ähnliches Problem habe.
Wir haben von einem Dienstleister eine DLL Datei erhalten, die ich aus Navision heraus ansprechen muss. Leider lässt sich diese DLL nicht registrieren, da es keine COM Dll ist.

Meine Frage, kann ich mittels Visual Studio 2005 eine Möglichkeit finden, über "Umwege" wie hier z.B. beschrieben diese DLL einzubinden? Ich habe an eine Möglichkeit gedacht, dass ich mir über VS 2005 eine COM Dll erstelle und in dieser dann a la LoadLibrary die externe DLL lade.
Ist das zu realisieren?

Wäre super, wenn mir hierbei jemand einen Tipp geben könnte.

Gruß
Pierre

12. Oktober 2007 21:54

Nun, du könntest die DLL in einem neuen Projekt (wie oben beschrieben) referenzieren und die Funktionen in dem Projekt kapseln. Also von dort aufrufen.

Dann hättest du einen Wrapper und könntest die DLL bzw. die neue von NAV aus ansprechen.

13. Oktober 2007 23:56

Hallo SilverX,

danke für den Hinweis. Ich habe heute den gazen Tag versucht das umzusetzen, doch leider bin ich noch nicht auf die richtige Lösung gestoßen. Ich kopiere mal den C# Code.

Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace GCard
{   
 
    [Guid("5BD50CFD-2CB9-434E-B058-65765BFBC1D8")]
    public interface IsetCardId
    {
        [DispId(1)]       
        void setCardId(string ID);
    }

    [Guid("23E08A8F-47F3-4295-90B6-387BB102A53F"),
     ClassInterface(ClassInterfaceType.None)]
    public class GC : IsetCardId
    {
       
        public void setCardId(string ID)
        {
            unsafe
            {
                CallFunction.setCardId(ID);
            }
        }
        public static class CallFunction
        {
            [DllImport("GCARD.dll")]
            public unsafe static extern void setCardId(string ID);


        }       

    }
}



Wenn ich anschließend in Navision versuche die Komponente anzusprechen, erhalta ich immer folgenden Fehler:

"Der Aufruf zum Member setCardId ist fehlgeschlagen. GCard gab folgende Meldung zurück: Es wurde versucht, im geschützten Speicher zu lesen oder schreiben. Dies ist häufig ein Hinweis darauf, dass anderer Speicher beschädigt ist."

Ich denke mal, dass der Weg, wie ich versuche, die Funktion aus der externen DLL aufzurufen das Problem ist oder?

Wäre toll, wenn du mir hierbei helfen könntest.

Gruß
Pierre

14. Oktober 2007 08:47

Hallo Pierre,

leider ist es mit einer unbekannten DLL schwierig hier über die Ferne eine Lösung anzubieten.

Zunächst solltest du die DLL nicht nochmals in einer Klasse kapseln, sondern direkt in der Klasse GC. Es gibt bei der Deklaration einiges zu beachten. Vielleicht hilft dir folgender Artikel weiter:
Reusing Legacy DLLs in C#. Dort sind einige Dinge bezüglich der Aufrufkonventionen beschrieben.

Die letzte Möglichkeit wäre, du sendest mir die DLL per PN zu und ich kann beispielhaft 2-3 Funktionen umsetzen.

15. Oktober 2007 10:55

Hi Silver,

danke nochmal für die Hinweise. Ich bin schon ein paar Schritte weiter gekommen. Mit Hilfe des marshallens konnte ich jetzt die Parameter ohne zuletzt erwähnte Fehlermeldung an die DLL Funktion übergeben.

Aktuell sieht das Projekt dann so aus.

Code:
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace GiftCard2005
{

   
    public static class CallMember
    {
        [DllImport("GC_CLIENT.dll", EntryPoint = "?setCardId@Request@giftcard@lsag@@QAEXPAD@Z",CallingConvention = CallingConvention.Cdecl)]
        public unsafe static extern void setCardId(ref IntPtr s);

        [DllImport("GC_CLIENT.dll", EntryPoint = "?setDomainIp@Response@giftcard@lsag@@QAEXPAD@Z",CallingConvention = CallingConvention.Cdecl)]
        public unsafe static extern void setDomainIp(ref IntPtr s);

        [DllImport("GC_CLIENT.dll", EntryPoint = "?setDomainPort@Response@giftcard@lsag@@QAEXPAD@Z",CallingConvention = CallingConvention.Cdecl)]
        public unsafe static extern void setDomainPort(ref IntPtr s);

        [DllImport("GC_CLIENT.dll", EntryPoint = "?setTransType@Request@giftcard@lsag@@QAEXH@Z", CallingConvention = CallingConvention.Cdecl)]
        public unsafe static extern void setTransType(ref IntPtr s);
       
        [DllImport("GC_CLIENT.dll", EntryPoint = "?getBalanceMax@Response@giftcard@lsag@@QAEPADXZ",CallingConvention = CallingConvention.Cdecl)]
        public unsafe static extern IntPtr getBalance();

    }

    [Guid("6AB89431-8D2D-4e1e-9207-741F0DC8290B")]
    public interface IsetCardId
    {
        [DispId(1)]
        void setCardId(string s);
        [DispId(2)]
        void setDomainIp(string s);
        [DispId(3)]
        void setDomainPort(string s);
        [DispId(4)]
        string getBalance();

    }

    [Guid("D40857A8-11C2-4865-95E9-03680E534B50"),
     ClassInterface(ClassInterfaceType.None)]
    public class GiftCard : IsetCardId
    {
       
        public unsafe void setCardId(string s)
        {           
                                   
            IntPtr ID = Marshal.StringToHGlobalAnsi(s);           
            CallMember.setCardId(ref ID);
       
        }

        public unsafe void setDomainIp(string s)
        {
            IntPtr IP = Marshal.StringToHGlobalAnsi(s);           
            CallMember.setDomainIp(ref IP);
        }

        public unsafe void setDomainPort(string s)
        {
            IntPtr Port = Marshal.StringToHGlobalAnsi(s);
            CallMember.setDomainPort(ref Port);
           
        }

        public unsafe void setTransType(string s)
        {
            IntPtr Type = Marshal.StringToHGlobalAnsi(s);
            CallMember.setDomainPort(ref Type);

        }

       
        public unsafe string getBalance()
        {
            IntPtr Balance = IntPtr.Zero;
            string Erg;
           
            Balance = CallMember.getBalance();           
            Erg = Marshal.PtrToStringAnsi(Balance);                                   

            return Erg;


        }

    }



}


Die DLL Datei wurde mit Visual CPP erstellt, die Entry Points sehen sehr merkwürdig aus :), die habe ich mittels einen Disassemler ausfindig machen können.

Die Meinungen, wie die Parameter an eine CPP Dll übergeben werden scheinen im Netz etwas auseinander zu gehen. Zuletzt habe ich gelesen, dass bei einem CPP char* Type ein IntPtr als Referenz übergeben werden muss. (wie oben zu sehen).

Jetzt bin ich nur auf ein neues Problem gestoßen. Eine Funktion in der CPP DLL erwartet einen eigenen Paramtertypen. Ich kopiere mal die Definition der Funktion.

Code:
int Gateway::doTrans(const char* domainIp, const char* domainPort,
  const ParameterMap& requestMap, ParameterMap& responseMap) {


Und in einer Header Datei habe ich die Definition für den Typen ParameterMap gefunden.

Code:
#include <string>
#include <map>

using namespace std;

namespace lsag {
  namespace giftcard {

    typedef map<string, string> ParameterMap;
    typedef ParameterMap::value_type ParameterPair;

  }
}
#endif



Mein Problem ist jetzt, wie ich in meinem C# Programm solch einen Typen abbilden muss(also die Parameter für den Typen ParameterMap), damit ich die Funktion aus C# korrekt aufrufen kann.


Hast du nochmal einen Tipp für mich, wie ich das jetzt realisieren kann?
Tausend Dank.

Gruß
Pierre

Custem Control VB.Net 2005

10. März 2008 10:44

Hallo zusammen, ich habe ein Custem Control in VB erstellt. Soweit so gut :-) Ich habe nur ein Problem.

Mein Custem Control erzeugt für mein Event keine Variable!
Der VB Code

Public Event EventText (ByVal TextSTR As String)

In Navision wir nur add_EventText und remove_EventText als Option angezeigt.

In VB6 erzeugt er mir eine String Variable in Naviosn (alles ok)
Beispiel:
CREATE(Va);
Va::EventText(VAR TextSTR : Text[1024])

Wer kann mir helfen!
Gruß
Andreas

10. März 2008 16:14

Hallo Eipel,

willkommen in unserer Community.

Zunächst: Bitte stell eine Frage nur einmal :)

Zur Frage: Ich bin nicht ganz sicher, ob ich die verstanden hab. Erzeugt dein Control für das Event eine Variable oder nicht? Unten siehts danach aus!? Kannst du vielleicht die Frage etwas umformulieren?

Vb.Net DLL

10. März 2008 17:19

Hallo SilverX,

ok das nächste mal nur einmal fragen! :-)
Sorry, ich dachte er hatte es beim dem ersten mal nicht genommen.

Zu Deiner Frage, mein Programmierung sollte eine Variable in Navision erzeugen! Macht es aber leider nicht!

Der Hintergrund:
Ich bekomme von einem Gerät an Com1 Daten gesendet.
Diese muss ich in eine Variable über einem Event in Navision bekommen.
Also ein Beispiel: alte ActiveX die funktioniert!
Ceate(Auto) "Automation"
Auto::EventPort1(VAR EventText : Text[1024])

Bei meiner VB.Net Programmierung erzeugt er mir kein
Auto::EventPort1(VAR EventText : Text[1024])

Sonst ist alles ok


Ich hoffe ich habe es verständlich erklärt.
P.S. Bin .NET Neuling