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());

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

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

        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) {
                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;

               public struct Tcl_Obj 
               int     refCount;
               IntPtr  bytes;
               int     length;
               IntPtr  typePtr;
               Int64   value;

         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

         public unsafe static extern IntPtr Tcl_GetString(Tcl_Obj* obj);

         public static extern IntPtr Tcl_CreateInterp();

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

         public static extern IntPtr Tcl_GetObjResult(IntPtr interp);

             public static extern int Tcl_SetObjResult(IntPtr interp, IntPtr objPtr);

                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]);
           return args;         

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

  } // Namespace