Documentation > Administrator Documentation > Features
Previous: Setup Tutorial
Next: User Documentation
The C# Interpreter can grant access to a lot of things to a user during runtime, but consideration should be given and there are features that can control it to some extent. This section describes features that set up the environment for users to code in. This continues the example from the previous section.
In some cases, the user may require parameters from the caller in order to make decisions or calculations. Consider the following for loop.
// the code
Console.WriteLine("Enter code below...");
string code = Console.ReadLine();
for(int i = 0; i < 5; i++) {
// give the user the iteration number
Dictionary<string, object> vars =
new Dictionary<string, object>();
vars.Add("x", i);
// run the Interpreter, collect the return value
object obj = CInterpreter.run(code, null, true, false, vars);
// if the return value is null, make it the word null
if(obj == null) obj = "null";
// write the value to the console
Console.WriteLine("Returned value was: " + obj);
}
// wait for user
Console.ReadKey();
In the run method, a dictionary object is passed into the Interpreter containing the key-value pair “x” and the variable i. See Full Definition below for a proper explanation of the run method. Run the program and test the variable; something like Figure 7 should come up.
The key-value pair always consists of a string and an object. The string always becomes the variable name, and the object can be anything. Should the name already exist as a variable for some reason – possibly recursion – the variable’s contents will be set aside for evaluation and reassigned at the end. This is to not lose the old value while still providing the users with the new values they need (and are expecting).
Including classes for the C# Interpreter to use is like the "using" directives at the top of the program, which in this case allows for direct access in the Interpreter instead of declaring the namespace. The Interpreter does not allow users to find namespaces; thus, the classes must be made available by the administrator directly, via the addMe method.
Consider the point class below, which would be inside the last curly bracket at the end to be in the same namespace.
class point {
private int _x, _y;
public int x { get { return _x; } set { _x = value; } }
public int y { get { return _y; } set { _y = value; } }
public point(int x, int y) { this._x = x; this._y = y; }
public override string ToString() { return "{"+_x+","+_y+"}"; }
}
Then give the details of the point class at the beginning of the Main method.
// give the user access to point
CInterpreter.addMe(typeof(point));
// the code
Console.WriteLine("Enter code below...");
...
Now the user can create point objects at will, as in figure 8.
Note: If there is an instance of the object readily available, the GetType() method could be used. Otherwise, the typeof() function works fine.
Note: Protection modifier on members must be public for users to be able to access them.
Warning: Be aware that users may be able to gain access to objects without explicit access, for instance the Type object via the GetType() method. If there are ways to access objects that should be restricted, use the security features (see banObject definition), add limited versions of classes (see below), or both.
When giving a user access to a class, one should look at every method, assume the user will try to access it, and decide if that should be allowed. If there are methods that, if used improperly, could cause the entire program to crash, for example, then the class should not be allowed (and maybe even banned). If one required certain functions of a class, a good option could be to make an access object. For example, to make the Console class “safe,” one may allow access to only WriteLine and ReadLine, as below.
static class SafeConsole {
public static void WriteLine(object obj)
{ if(obj != null) Console.WriteLine(obj); }
public static string ReadLine()
{ return Console.ReadLine(); }
}
(inside Main)
// give the user access to safe console
CInterpreter.addMe(typeof(SafeConsole), "Console");
// the code
Console.WriteLine("Enter code below...");
...
Now the user can read and write to the console, but in a controlled manner, as in Figure 9. Note that the
Note: The "Console" after the type is an alias the user can use instead of the whole name. It also gives the illusion they are using the real console directly.
Note: Since there are two lines, the return keyword must be used.
A difficulty with the C# Interpreter is using the returned values, because they could be almost anything. Consider the following example (modified from end of section Tutorial > Running).
// run the Interpreter
object obj = CInterpreter.run(code, null);
// perform calculations
int a = 5;
int b = (int)obj;
int c = a + b;
// output results
Console.WriteLine("5 + (" + code + ") = " + c);
This works fine when the user enters a simple number like 6, as in Figure 10. The problem occurs when the user attempts to enter a number that is not an int, like 4.5, in which the exception in Figure 11 occurs. All this means is that the system tried to cast something, but it didn’t know how. If one had calculations that could handle doubles, they may attempt the following code.
int a = 5;
double b = (double)obj;
double c = a + b;
This now works when double values are returned, but the same exception happens when an int is returned. This is a difficult issue, but there are some options. A feature to force the Interpreter to return a specific type of object else throw an error may be coming soon. In the meantime, one option is to simply check if the user returned a specific type of value (or set of types) and perhaps ask for different code otherwise, and the other is to use a special object called a dynamic, featured below; note that it may require a using System.Dynamic; statement. In this code, the calculation will return a double value type whenever the user returns a numeric type initially supported by the Interpreter. It is still recommended that the following code should be set inside a try-catch block.
int a = 5;
dynamic b = obj;
double c = a + b;
Figure 10
Figure 11, Result of casting from object type when real type is not the target cast.
Table 1 contains a full definition of the C# Interpreter, for the administrator.
Some of the members were not described earlier but only need brief mention. ranSuccessfully should be used to determine if a default value should be used, or if some other function needs to be implemented. Note that if evaluation fails, null is always returned. An alternative would be to enter false for the safe parameter when calling the run method, and then putting the section in a try – catch block. This would also allow for capturing specific errors from the C# Interpreter.
For the run method, there are a few things to keep in mind. The value returned by the user may be null, a string, an int, or anything. See Returning Values above for more information. The fourth parameter (second Boolean) should be true only if the user may need the previous variables, or the variables made should stay till next time (both evaluations need to have this parameter true).
ext has a proper definition in User Documentation, but a member of particular note here is the persistent object list. It is a Dictionary<string, object> type where users can store their persistent data. This could provide set up variables for the user or additional return value, for example, a list of settings for a game.
For security, the Interpreter really shouldn’t be used with the intent of high security. In fact, it may be the single greatest security threat to ever approach a compiled language. However, an attempt was made by first limiting the initially available objects, and second by optionally banning certain classes. Coupled with C# not letting the Interpreter see private members, it is somewhat okay. If this is intended for debugging purposes, the only thing to worry about is making sure to disable this feature when releasing the application. But use at your own risk; no warranties.
In summary, using the interpreter can be broken down into the following steps:
Get the code as a string from the user.
Give the string to the C# Interpreter.
Optionally use the value returned.
Watch out for errors.
The Interpreter, in its simplest form, could be something like bool yesOrNo = (bool)CInterpreter.run(Console.ReadLine(), null);. All the extra steps taken should hopefully provide everything needed to know to give users greater functionality and access. There are a few final things to always keep in mind:
All fields, methods, properties, and classes must be marked public in order to be accessed by the user. This is a built-in feature of C#.
From a security standpoint, the user can access anything marked public, so make sure users can’t access objects or methods they shouldn’t be able to.
Be aware that there is absolutely no control of what the user may do. Potential solutions to this may be available in the future, but as of now, a user could run a hundred lines doing who knows what before returning a proper value. Once again: Use at your own risk!
Next: