我在将脚本功能集成到我的小组构建的应用程序方面取得了很大进展,但我现在有点陷入困境。
我们已经编写了数据采集软件(应用程序),我们也希望拥有脚本功能。我在应用程序中嵌入了一个能够读取和编译外部编写脚本的类。例如,我可以编写一个不以任何方式链接到应用程序的.cs文件,并在运行时调用该文件,成功执行它。
我现在需要做的是扩展脚本的有用性,即从脚本中调用应用程序中预先存在的方法。我将尝试使用以下示例来描述所有这些:
这里我有我的脚本文件,我可以在应用程序运行时随时更改
ScriptFile.cs
namespace SimpleScripts
{
public class MyScriptMul5 : ScriptingInterface.IScriptType1
{
public string RunScript(int value)
{
System.Console.WriteLine("Hello World! This works!");
//NEED THIS: Code to call pre-existing method in application
}
}
}
对于这个大型代码块感到抱歉,但我想如果出现问题我会包含所有内容。我想要做的就是用上面的脚本调用方法 TestExternalCall ,同时通过这个脚本处理程序运行它。
ScriptHandler.cs
namespace ScriptingInterface
{
public interface IScriptType1
{
string RunScript(int value);
}
}
namespace ScriptingExample
{
public static class ScriptingEx
{
public static void StartScript()
{
string path = @"TestScript1.cs";
// Open the file to read from.
string readText = File.ReadAllText(path);
Assembly compiledScript = CompileCode(readText);
if (compiledScript != null)
{
RunScript(compiledScript);
}
}
static Assembly CompileCode(string code)
{
Microsoft.CSharp.CSharpCodeProvider csProvider = new Microsoft.CSharp.CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.GenerateInMemory = true;
options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
// Compile our code
CompilerResults result;
result = csProvider.CompileAssemblyFromSource(options, code);
if (result.Errors.HasErrors)
{
// Report back to the user that the script has errored
Console.WriteLine("Script has errored");
for (int i = 0; i < result.Errors.Count; i++)
{
Console.WriteLine("Error {0}: {1}", i+1, result.Errors[i]);
}
return null;
}
if (result.Errors.HasWarnings)
{
Console.WriteLine("Script has warnings");
}
return result.CompiledAssembly;
}
static void RunScript(Assembly script)
{
foreach (Type type in script.GetExportedTypes())
{
foreach (Type iface in type.GetInterfaces())
{
if (iface == typeof(ScriptingInterface.IScriptType1))
{
ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
if (constructor != null && constructor.IsPublic)
{
ScriptingInterface.IScriptType1 scriptObject = constructor.Invoke(null) as ScriptingInterface.IScriptType1;
if (scriptObject != null)
{
//Lets run our script and display its results
MessageBox.Show(scriptObject.RunScript(50));
}
}
}
}
}
}
public static void TestExternalCall1()
{
Console.WriteLine("Called successfully!");
}
}
}
如果您有任何问题,请告诉我,希望我已经说清楚了。
答案 0 :(得分:0)
在我的一个项目中,我实现了类似的功能。我们通过提供接触RunScript
方法的接口来实现这一点,该方法暴露了跑步者的不同服务。
它允许脚本编写者在提交给执行者之前模拟服务并测试他们的代码。
该方法的另一个好处是您将在编译期间捕获不兼容问题。
作为替代解决方案,您可以使用反射,但它不是强类型的
如果执行者的服务发生了变化,您可能会在运行时遇到问题。
例如:
您的宏
namespace SimpleScripts
{
public class MyScriptMul5 : ScriptingInterface.IScriptType1
{
public string RunScript(ScriptingInterface.IServiceProvider serviceProvider, int value)
{
System.Console.WriteLine("Hello World! This works!");
serviceProvider.Messenger.SendMessage("Test");
}
}
}
包含宏引擎API的程序集(ScriptingInterface.dll)
namespace ScriptingInterface
{
public interface IScriptType1
{
string RunScript(int value);
}
public interface IMessenger{
void SendMessage(String message);
}
public interface IServiceProvider
{
IMessenger Messenger {get;}
String TempDirectory {get;}
}
}
宏编译器\执行者
namespace ScriptingExample
{
public static class ScriptingEx
{
public static void StartScript(ScriptingInterface.IServiceProvider serviceProvider)
{
string path = @"TestScript1.cs";
// Open the file to read from.
string readText = File.ReadAllText(path);
Assembly compiledScript = CompileCode(readText);
if (compiledScript != null)
{
RunScript(serviceProvider, compiledScript);
}
}
static Assembly CompileCode(string code)
{
Microsoft.CSharp.CSharpCodeProvider csProvider = new Microsoft.CSharp.CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.GenerateInMemory = true;
options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
//Add references to ScriptingInterface.dll
String pathToScriptingInterfaceDll = Path.Combine(Environment.CurrentDirectory, "ScriptingInterface.dll");
options.ReferencedAssemblies.Add(pathToScriptingInterfaceDll);
// Compile our code
CompilerResults result;
result = csProvider.CompileAssemblyFromSource(options, code);
if (result.Errors.HasErrors)
{
// Report back to the user that the script has errored
Console.WriteLine("Script has errored");
for (int i = 0; i < result.Errors.Count; i++)
{
Console.WriteLine("Error {0}: {1}", i+1, result.Errors[i]);
}
return null;
}
if (result.Errors.HasWarnings)
{
Console.WriteLine("Script has warnings");
}
return result.CompiledAssembly;
}
static void RunScript(ScriptingInterface.IServiceProvider serviceProvider, Assembly script)
{
foreach (Type type in script.GetExportedTypes())
{
foreach (Type iface in type.GetInterfaces())
{
if (iface == typeof(ScriptingInterface.IScriptType1))
{
ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
if (constructor != null && constructor.IsPublic)
{
ScriptingInterface.IScriptType1 scriptObject = constructor.Invoke(null) as ScriptingInterface.IScriptType1;
if (scriptObject != null)
{
//Lets run our script and display its results
MessageBox.Show(scriptObject.RunScript(50));
}
}
}
}
}
}
public static void TestExternalCall1()
{
Console.WriteLine("Called successfully!");
}
}
}