Tcl Interpreter in C# Application

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

  // Works with TCL86, [Bill Moore (WPM) 3/16/2017]
  // Requires: tcl86.dll and zlib1.dll from ActiveState Tcl placed in EXE directory
  namespace TCL {
        public delegate int Callback(List<string> args, out string result);

        public interface IShell {
          void   AddFunction  (string cmd, Callback F);
          int    Eval         (string script, out string result);
        }

    public class Shell : IShell {        
        private IntPtr                        interp;
        private Dictionary<string, Callback>  DictCmds;
        private TclDll.Tcl_ObjCmdProc         DispatcherDelegate;

        public Shell() {
                DictCmds   = new Dictionary<string, Callback>();

                // Need Dispatcher Delegate for lifetime of object to avoid GC
                //  (Apparently C# automatically handles pinning of the pointers for delegates)
                unsafe {
                        DispatcherDelegate = new TclDll.Tcl_ObjCmdProc(Dispatcher);
                }

                try {
              interp = TclDll.Tcl_CreateInterp();
                }
                catch(Exception x) {
                        System.Windows.MessageBox.Show("TclDll.Tcl_CreateInterp failed 1\r\n" + x.ToString());
                        return;
                }

            if (interp == IntPtr.Zero) {
                        System.Windows.MessageBox.Show("TclDll.Tcl_CreateInterp failed 2");
                        return;
            }

            AddFunction("hello",    Cmd_MyHello); 
            AddFunction("listfunt", Cmd_ListFunt);
            return;            
        }


        public int Eval(string script, out string ResultString) {
            int ResultInt;    

            // Fix to allow multi-line scripts using either windows or linux formatted string..
            // Remove Windows '\r' from Windows NewLine of '\r\n' leaving only '\n' Linux Newline
            script = script.Replace("\r", "");

            ResultInt    = TclDll.Tcl_Eval(interp, script);
            ResultString = TclDll.Helper_GetStringResult(interp);
            return ResultInt;
        }

       public void AddFunction(string cmd, Callback F)       
       {

         if (interp == null)
         {
                 throw new SystemException ("ERROR: tcl_backend: interp is null\n");
         }

         DictCmds.Add(cmd, F);
         TclDll.Helper_RegisterProc(interp, cmd, DispatcherDelegate);                 
       }

       private unsafe int Dispatcher(
         IntPtr           clientData,  
         IntPtr           interp,
         int              objc,
         TclDll.Tcl_Obj** objv)
       {
                List<string> args = TclDll.Helper_ObjvToList(objc, objv);

                if (args.Count == 0) {
            System.Windows.MessageBox.Show("EMPTY cmd dispatch");
            return 0;            
                }

                string cmd = args[0];

         if (DictCmds.ContainsKey(cmd)) {
                 string result_string;
                 int    result_code;

                 var F = DictCmds[cmd];

                 result_code = F(args, out result_string);

            TclDll.Tcl_SetObjResult(interp, TclDll.Tcl_NewStringObj(result_string, -1));
            return result_code;
                }

         return -1; //TclDll.TCL_OK;
       }

       public int Cmd_MyHello(List<string> args, out string result) {
                //System.Windows.MessageBox.Show("MyHello!\n");
                result="DONE WITH RESULT";
            return 0; 
       }

       public int Cmd_ListFunt(List<string> args, out string result) {
             List<string> keys = new List<string>(DictCmds.Keys);

         result = "";             
             foreach (string key in keys) {
               result += key + "\r\n";
             }

         return 0; 
       }


    } // Class Shell


    public class TclDll {
         public const int TCL_OK = 0;

        [StructLayout(LayoutKind.Sequential)]
               public struct Tcl_Obj 
             {
               int     refCount;
               IntPtr  bytes;
               int     length;
               IntPtr  typePtr;
               Int64   value;
             };

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

         [DllImport(@"tcl86.dll", CallingConvention= CallingConvention.Cdecl)]             
             public static extern IntPtr  Tcl_CreateObjCommand(
               IntPtr          interp,
           [In, MarshalAs (UnmanagedType.LPStr)] string cmdName, 
           IntPtr          proc, 
                   IntPtr          clientData,
               IntPtr          deleteProc
             );

         [DllImport(@"tcl86.dll")]
         public unsafe static extern IntPtr Tcl_GetString(Tcl_Obj* obj);

         [DllImport(@"tcl86.dll")]
         public static extern IntPtr Tcl_CreateInterp();

         [DllImport(@"tcl86.dll")]
         public static extern int Tcl_Eval(
                 IntPtr interp,
                 [In, MarshalAs (UnmanagedType.LPStr)] string script);

         [DllImport(@"tcl86.dll")]
         public static extern IntPtr Tcl_GetObjResult(IntPtr interp);

         [DllImport(@"tcl86.dll")]         
             public static extern int Tcl_SetObjResult(IntPtr interp, IntPtr objPtr);

         [DllImport(@"tcl86.dll")]             
                public static extern IntPtr  Tcl_NewStringObj(
                 [In, MarshalAs (UnmanagedType.LPStr)] string  ObjName, 
                 int length
         );

         public unsafe static string Helper_GetString(Tcl_Obj* obj) {
                  IntPtr b = TclDll.Tcl_GetString(obj);
             string s = Marshal.PtrToStringAnsi(b);
                  return s;
         }

         public static string Helper_GetString(IntPtr obj) {
                 unsafe {
                   IntPtr b = TclDll.Tcl_GetString((Tcl_Obj*)obj);
             string s = Marshal.PtrToStringAnsi(b);
                  return s;
                 }
         }

         public static string Helper_GetStringResult(IntPtr interp) {
                IntPtr obj = Tcl_GetObjResult(interp);
                if (obj == IntPtr.Zero) {
                    return "";
                } else {
                        return Helper_GetString(obj); 
                }                 
         }

         public static unsafe List<string> Helper_ObjvToList(
           int       objc,
           Tcl_Obj** objv)
         {
                  var args = new List<string>();

           for (int i=0; i < objc; i++)
           {
                    string s = Helper_GetString(objv[i]);
                  args.Add(s);
           }      
           return args;         
         }

         public static unsafe void Helper_RegisterProc(IntPtr interp, string name, TclDll.Tcl_ObjCmdProc proc) {
                Tcl_CreateObjCommand(
                        interp     : interp,
                        cmdName    : name,
                        proc       : Marshal.GetFunctionPointerForDelegate(proc),
                        clientData : IntPtr.Zero,
                        deleteProc : IntPtr.Zero
                );

        }
    }
  } // Namespace