c#-在不同的进程中运行委托

在C#中,我们有漂亮的CodeDomProvider类,该类使我们能够从源代码动态创建一些exe文件,然后运行它等等.问题是当我们没有此类资源时.例如,我有一个委托(例如Action string),我想编译一个exe,该exe从命令行获取第一个...

在C#中,我们有漂亮的CodeDomProvider类,该类使我们能够从源代码动态创建一些exe文件,然后运行它等等.

问题是当我们没有此类资源时.例如,我有一个委托(例如Action< string>),我想编译一个exe,该exe从命令行获取第一个参数,然后执行传递的Action< string>.用它.在更复杂的情况下,我有Func< string [],string&gt ;,它接受传递的命令行参数,执行某些操作并在标准输出中写入内容. 我想要类似字符串ExecuteOutOfProcess(Func< string [],string> func,string [] args)之类的东西,它可以编译exe,使用提供的参数运行它,从标准输出中获取结果,然后将其作为结果返回.理想情况下,它应该更通用,例如,使用TResult ExecuteOutOfProcess< T,TResult>(Func< T,TResult> func,T input),并且它应该在内部对所有内容进行反序列化和序列化,从而透明地调用代码.

有什么要实现的吗?因为执行类似操作的唯一方法是编写反编译器,然后从委托中获取C#源代码,然后将这些源代码与CodeDomProvider一起使用,然后再次解析源代码…无法直接在编译器中传递表达式?

解决方法:

这实际上很棘手.但是,如果确保以某种方式使用它,则可以将其简化很多:

>仅允许调用具有可序列化参数和返回值且不涉及任何其他托管状态的静态函数.
>由于您需要64位-> 32位互操作,具有在设置为AnyCPU的程序集中声明的功能

在这些约束条件下,您可以使用一个简单的技巧:发送要尝试执行的任何操作的类型和方法名称,您的助手测试运行程序可以使用程序集限定名称加载该类型并调用该方法.要发送所需的数据,您可以使用诸如WCF或内存映射文件之类的东西.

一个非常简单(且脆弱)的示例:

public static async Task<T> Run<T>(Func<T> func)
{
    var mapName = Guid.NewGuid().ToString();

    using (var mapFile = MemoryMappedFile.CreateNew(mapName, 65536))
    {
        using (var stream = mapFile.CreateViewStream())
        using (var bw = new BinaryWriter(stream))
        {
            bw.Write(func.Method.DeclaringType.AssemblyQualifiedName);
            bw.Write(func.Method.Name);

            if (func.Target == null)
            {
                bw.Write(0);
            }
            else
            {
                using (var ms = new MemoryStream())
                {
                    new BinaryFormatter().Serialize(ms, func.Target);
                    var data = ms.ToArray();
                    bw.Write(data.Length);
                    bw.Write(data);
                }
            }
        }

        using (var process = Process.Start(new ProcessStartInfo("LambdaRunner", mapName) { UseShellExecute = false, CreateNoWindow = true }))
        {
            process.EnableRaisingEvents = true;
            await process.WaitForExitAsync();

            switch (process.ExitCode)
            {
                case -10: throw new Exception("Type not accessible.");
                case -11: throw new Exception("Method not accessible.");
                case -12: throw new Exception("Unexpected argument count.");
                case -13: throw new Exception("Target missing.");
                case 0: break;
            }
        }

        using (var stream = mapFile.CreateViewStream())
        {
            return (T)(object)new BinaryFormatter().Deserialize(stream);
        }
    }
}

帮助程序运行程序可执行文件如下所示:

static int Main(string[] args)
{
    var mapName = args[0];

    using (var mapFile = MemoryMappedFile.OpenExisting(mapName))
    {
        string typeAqn;
        string methodName;
        byte[] target;

        using (var stream = mapFile.CreateViewStream())
        using (var br = new BinaryReader(stream))
        {
            typeAqn = br.ReadString();
            methodName = br.ReadString();
            target = br.ReadBytes(br.ReadInt32());
        }

        var type = Type.GetType(typeAqn);
        if (type == null) return -10;

        var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.InvokeMethod);
        if (method == null) return -11;
        if (method.GetParameters().Length > 0) return -12;

        object returnValue;

        if (target.Length == 0)
        {
            if (!method.IsStatic) return -13;

            returnValue = method.Invoke(null, new object[0]);
        }
        else
        {
            object targetInstance;
            using (var ms = new MemoryStream(target)) targetInstance = new BinaryFormatter().Deserialize(ms);

            returnValue = method.Invoke(targetInstance, new object[0]);
        }

        using (var stream = mapFile.CreateViewStream())
            new BinaryFormatter().Serialize(stream, returnValue);

        return 0;
    }
}

用法示例:

static string HelloWorld1()
{
    return "Hello world!";
}

static async Task RunTest<T>(int num, Func<Task<T>> func)
{
    try
    {
        Console.WriteLine($"Test {num}: {await func()}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Test {num} failed: {ex.Message}");
    }
}

[Serializable]
public struct Fun
{
    public string Text;
    public int Number;

    public override string ToString() => $"{Text} ({Number})";
}

static async Task MainAsync(string[] args)
{
    await RunTest(1, () => Runner.Run(HelloWorld1));
    await RunTest(2, () => Runner.Run(() => "Hello world from a lambda!"));
    await RunTest(3, () => Runner.Run(() => 42));
    await RunTest(4, () => Runner.Run(() => new Fun{Text = "I also work!", Number = 42}));
}

如果您可以将自己限制在我概述的限制范围内,那么它将很好用-只需确保还添加适当的错误处理即可.可悲的是,没有简单的方法来确保您要调用的函数是“纯”的-如果某处对某个静态状态有依赖性,则它将无法正常工作(也就是说,它将不会在其中使用静态状态).您的过程,而是拥有自己的过程,无论什么意思).

您必须确定这种方法是否适合您的情况.它可能使事情变得更简单,也可能使事情变得更糟:)

本文标题为:c#-在不同的进程中运行委托

基础教程推荐