Laden...

Access Violation bei TCL-Kommandoaufruf über DLL

Erstellt von dls vor 8 Jahren Letzter Beitrag vor 8 Jahren 2.146 Views
D
dls Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren
Access Violation bei TCL-Kommandoaufruf über DLL

Hallo allerseits,

um die Einbindung eines externen TCL-Interpreters ("TCL84.DLL") auszuprobieren habe ich eine simple Konsolenanwendung erstellt. Das Ganze funktioniert soweit. Auch die Anmeldung eines eigenen Kommandos und die Zuweisung des Kommandonamens "test" mittels "Tcl_CreateCommand" funktioniert im Prinzip. Wenn ich "test" in der laufenden Konsole aufrufe gibt es jedoch eine Access Violation ("Es wurde versucht, im geschützten Speicher zu lesen...").


using System;
//using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        unsafe static int Main(string[] args)
        {
            string inp = "12";
            Console.WriteLine("input your TCL commands:");
            TclInterpreter interp = new TclInterpreter();
            interp.registerProc("test", new TclAPI.Tcl_ObjCmdProc(tcmd));
            string result="Interpreter result is != 0 !!!";

            // TCL test output:
            interp.evalScript("set a " + inp + ";");                            // -> a = 12
            interp.evalScript("expr {$a * 8}");                                 // "interp.Result" = a * 8 = 96
            if (interp.evalScript("expr {" + interp.Result + " + 2}") == 0)     // "interp.Result" = "interp.Result" + 2 = 98
            {                                                                   // if no error:
                result = interp.Result;                                         // then "result" is "interp.Result" = 98
            }
            Console.WriteLine(result);                                          // write line "result" = 98

            // enter & execute TCL user commands via console:
            while (1 == 1)
            {
                string s = Console.ReadLine();
                interp.evalScript(s);
                Console.WriteLine(interp.Result);
            }

            //  TCL Test per Konsole zum probieren:
            //  TCL Ausgabe Variable a: 		puts $a
            //  TCL Ausgabe Variable a: 		expr {$a}
            //  TCL Definition Variable b: 		set b 5
            //  TCL Berechnung + Ausgabe:		expr {$a * $b}

        }

        static unsafe int tcmd(IntPtr clientData, IntPtr interpr, int argc, byte** argv)
        {
            TclAPI.Tcl_AppendResult(interpr, "Hello");
            return 0;
        }

    }

    public class TclAPI
    {
        public unsafe delegate int Tcl_ObjCmdProc(IntPtr clientData,
            IntPtr interp,
            Int32 objc,
            byte** argv);
        [DllImport("tcl84.DLL", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Tcl_CreateInterp();
        [DllImport("tcl84.DLL", CallingConvention = CallingConvention.Cdecl)]
        public static extern int Tcl_Eval(IntPtr interp, string skript);
        [DllImport("tcl84.DLL", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Tcl_GetObjResult(IntPtr interp);
        [DllImport("tcl84.DLL", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Tcl_CreateCommand(IntPtr interp, String cmdName, Tcl_ObjCmdProc proc, IntPtr clientData, IntPtr deleteProc);
        [DllImport("tcl84.DLL", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr Tcl_AppendResult(IntPtr interp, String rslt);
        [DllImport("tcl84.DLL", CallingConvention = CallingConvention.Cdecl)]
        unsafe public static extern char* Tcl_GetStringFromObj(IntPtr tclObj, IntPtr length);
    }
    public class TclInterpreter
    {
        private IntPtr interp;
        private ArrayList delegates = new ArrayList();
        public TclInterpreter()
        {
            interp = TclAPI.Tcl_CreateInterp();
            if (interp == IntPtr.Zero)
            {
                throw new SystemException("can not initialize Tcl interpreter");
            }
        }
        public int evalScript(string script)
        {
            return TclAPI.Tcl_Eval(interp, script);
        }
        public void registerProc(string name, TclAPI.Tcl_ObjCmdProc proc)
        {
            // prevent cleaning delegates 
            delegates.Add(proc);
            TclAPI.Tcl_CreateCommand(interp, name, proc, IntPtr.Zero, IntPtr.Zero);
        }

        unsafe public string Result
        {
            get
            {
                IntPtr obj = TclAPI.Tcl_GetObjResult(interp);
                if (obj == IntPtr.Zero)
                {
                    return "";
                }
                else
                {
                    return Marshal.PtrToStringAnsi((IntPtr)TclAPI.Tcl_GetStringFromObj(obj, IntPtr.Zero));
                }
            }
        }
    }

}

Möglicherweise wird der Pointer auf meine Funktion nicht korrekt an die DLL übergeben. Die Fehlermeldung kommt jedenfalls mit der Programmzeile " return TclAPI.Tcl_Eval(interp, script);" (hier wird die aktuelle Zeile aus der Konsole an den Interpreter übergeben).
Hat jemand eine Idee, was ich hier machen kann?

W
872 Beiträge seit 2005
vor 8 Jahren

Dein Delegate/Funktionpointer hat nur Verweise im unmanaged Bereich.
Du musst einen zusätzlichen Verweis herstellen oder ein statisches Feld nehmen, damit der Garabge Collector das delegate nicht löscht.

D
dls Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

Hallo weismat,
Danke für die schnelle Antwort. Ich nehme an, Du meinst das Delegate "Tcl_ObjCmdProc".
Das Programm habe ich folgendermaßen geändert, um das Feld statisch zu machen:


    class Program
    {
        unsafe static TclAPI.Tcl_ObjCmdProc cbp = new TclAPI.Tcl_ObjCmdProc(tcmd);
        unsafe static int Main(string[] args)
        {
            string inp = "12";
            Console.WriteLine("input your TCL commands:");
            TclInterpreter interp = new TclInterpreter();
            //interp.registerProc("test", new TclAPI.Tcl_ObjCmdProc(tcmd));
            interp.registerProc("test", cbp);


Das Resultat ist jedoch leider das gleiche.

S
248 Beiträge seit 2008
vor 8 Jahren

Hallo dls,

versuche mal die korrekte Calling-Convention für den Tcl_ObjCmdProc Delegaten zu setzen:
UnmanagedFunctionPointerAttribute

Grüße
spooky

D
dls Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

Hallo spooky,
die Defintion sieht jetzt so aus:


        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate int Tcl_ObjCmdProc(IntPtr clientData,
            IntPtr interp,
            Int32 objc,
            byte** argv);


Damit funktioniert es! Allerdings nur, wenn ich die Applikation aus der Entwicklungsumgebung starte. Nach Start durch Doppelklick auf die .exe gibt es wieder einen Laufzeitfehler, sobald ich das angemeldete Kommando mittels "test" aufrufe. "Release" oder "Debug" ist dabei egal.

Grüße
dls

S
248 Beiträge seit 2008
vor 8 Jahren

Hallo,

kannst du bitte die originale Definition der Methodensignatur (C/C++) posten?

spooky

D
dls Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

Tut mir leid, aber wo finde ich die Methodensignatur?
Ich dachte bisher, es handelt sich um die Namen und die Parameterdefinitionen der Methoden. Die sind doch alle im geposteten Programm enthalten.

4.939 Beiträge seit 2008
vor 8 Jahren

Wir brauchen die C/C++ Header-Datei ("tcl.h"?) der "tcl84.dll"...
Wo hast du denn sonst die Funktionssignaturen her?

D
dls Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

Sorry, die Funktionssignaturen der DLL sind hier dokumentiert:
http://www.tcl.tk/man/tcl8.4/TclLib/contents.htm

Weiterhin habe ich mich an Programmbeispiele gehalten wie dieses hier:
http://wiki.tcl.tk/9563

Grüße
dls