上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新

可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文件。便利性不是很高。

那么有么有办法能做到动态实时更新呢????

官方提供了这两个对象,动态编译源文件。

提供对 C# 代码生成器和代码编译器的实例的访问。 CSharpCodeProvider
提供一下方法加载源文件,

// 基于包含在 System.CodeDom.CodeCompileUnit 对象的指定数组中的 System.CodeDom 树,使用指定的编译器设置编译程序集。
public virtual CompilerResults CompileAssemblyFromDom(CompilerParameters options, params CodeCompileUnit[] compilationUnits);

// 从包含在指定文件中的源代码,使用指定的编译器设置编译程序集。
public virtual CompilerResults CompileAssemblyFromFile(CompilerParameters options, params string[] fileNames);

// 从包含源代码的字符串的指定数组,使用指定的编译器设置编译程序集。
public virtual CompilerResults CompileAssemblyFromSource(CompilerParameters options, params string[] sources);

上面的方法我测试了CompileAssemblyFromFile  CompileAssemblyFromSource 两个方法,

CompileAssemblyFromFile  的意思给定文件路径去编译源文件,可以直接加入调试信息,调试,

CompileAssemblyFromSource 的意思给定源码类容去编译源文件,无法直接加入调试信息,需要加入  System.Diagnostics.Debugger.Break(); 在源文件插入断点调试。但是在除非断点的时候会弹出对话框,跳转指定源文件附近才能调试。略微麻烦。

以上两种方法需要调试都需要下面的调试参数配合 IncludeDebugInformation = true; 才能有用

表示用于调用编译器的参数。 CompilerParameters
提供一下参数

//不输出编译文件
parameter.GenerateExecutable = false;
//生成调试信息
parameter.IncludeDebugInformation = true;
//不输出到内存
parameter.GenerateInMemory = false;
//添加需要的程序集
parameter.ReferencedAssemblies.AddRange(tempDllNames);

废话不多说,我们来测试一下代码

首先创建一个 LoadManager.cs

public class LoadManager
    {
        private static LoadManager instance = new LoadManager();
        public static LoadManager GetInstance { get { return instance; } }

        public void LoadFile(string path)
        {
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameter = new CompilerParameters();
            //不输出编译文件
            parameter.GenerateExecutable = false;
            //生成调试信息
            parameter.IncludeDebugInformation = true;
            //不输出到内存
            parameter.GenerateInMemory = false;
            //添加需要的程序集
            parameter.ReferencedAssemblies.Add("System.dll");
            //编译文件
            Console.WriteLine("动态加载文件:" + path);
            CompilerResults result = provider.CompileAssemblyFromFile(parameter, path);//根据制定的文件加载脚本
            //判断是否有错误
            if (!result.Errors.HasErrors)
            {
                //获取加载的所有对象模型
                Type[] instances = result.CompiledAssembly.GetExportedTypes();
                foreach (var itemType in instances)
                {
                    Console.WriteLine("生成实例:" + itemType.Name);
                    //生成实例
                    object obj = Activator.CreateInstance(itemType);
                }
            }
            else
            {
                var item = result.Errors.GetEnumerator();
                while (item.MoveNext())
                {
                    Console.WriteLine("动态加载文件出错了!" + item.Current.ToString());
                }
            }
        }
    }

创建测试源文件 TestCode.cs

public class TestCode
    {
        public TestCode()
        {
            Console.WriteLine("我是TestCode");
        }
    }

调用测试

string cspath = @"F:\javatest\ConsoleApplication6\CodeDomeCode\TestCode.cs";
            LoadManager.GetInstance.LoadFile(cspath);

输出结果 表面我们加载成功了,

动态加载文件:F:\javatest\ConsoleApplication6\CodeDomeCode\TestCode.cs
生成实例:TestCode
我是TestCode

接下来我们

修改一下 TestCode.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeDomeCode
{
    public class TestCode
    {
        public TestCode()
        {
            Console.WriteLine("我是TestCode");
        }
    }
}

结果程序输出错误

动态加载文件:F:\javatest\ConsoleApplication6\CodeDomeCode\TestCode.cs
动态加载文件出错了!f:\javatest\ConsoleApplication6\CodeDomeCode\TestCode.cs(,) : error CS0234: 命名空间“System”中不存在类型或命名空间名称“Linq”(是否缺少程序集引用?)

这就出现了一个问题,

//添加需要的程序集
            parameter.ReferencedAssemblies.Add("System.dll");

我们的编译参数。附件编译依赖程序集的只添加了 System.dll 文件,所有导致编译出错。

那么我们知道思考一个问题,这个依赖程序集,必须要手动添加嘛?是不是太费事 ?

如果是做公共模块的话。我这么知道需要哪些依赖程序集呢?

系统提供了AppDomain.CurrentDomain.GetAssemblies();获取当前程序集所有程序集

Assembly.GetModules();程序集依赖项;

既然这样,我们是不是可以依赖当前应用程序域加载呢?

修改一下依赖程序集添加方式

HashSet<String> ddlNames = new HashSet<string>();
            var asss = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var item in asss)
            {
                foreach (var item222 in item.GetModules(false))
                {
                    ddlNames.Add(item222.FullyQualifiedName);
                }
            }

            //添加需要的程序集
            parameter.ReferencedAssemblies.AddRange(ddlNames.ToArray());

编译完成,依赖于依赖当前应用程序域加载依赖程序集;(需要注意的时候你的代码里面依赖的程序集,当前应用程序域也需要加载)

动态加载文件:F:\javatest\ConsoleApplication6\CodeDomeCode\TestCode.cs
生成实例:TestCode
我是TestCode

接下来我们看看如何才能加入调试情况呢?

有两个问题,如果要加入调试,需要修改两个参数才能加入断点调试

//生成调试信息
parameter.IncludeDebugInformation = true;
//输出编译对象到内存
parameter.GenerateInMemory = true;

在代码中直接加入断点测试

运行起来

进入断点调试了。

如果是源文件是文本文件但是需要加入调试的话;

System.Diagnostics.Debugger.Break();

我们看到加入了调试了,两种方式都能加入调试信息;

问题继续出现,我们在加载源文件的时候,需求里面肯定存在更新所加载的源文件吧。

而且加载的文件对象肯定需要保存提供调用;

修改程序。

使用线程安全的集合保存所加载的实例对象

 ConcurrentDictionary<string, ConcurrentDictionary<string, object>> Instances = new ConcurrentDictionary<string, ConcurrentDictionary<string, object>>();
//获取加载的所有对象模型
            Type[] instances = assembly.GetExportedTypes();
            foreach (var itemType in instances)
            {
                //获取单个模型的所有继承关系和接口关系
                Type[] interfaces = itemType.GetInterfaces();
                //生成实例
                object obj = Activator.CreateInstance(itemType);
                foreach (var iteminterface in interfaces)
                {
                    //判断是否存在键
                    if (!Instances.ContainsKey(iteminterface.Name))
                    {
                        Instances[iteminterface.Name] = new ConcurrentDictionary<string, object>();
                    }
                    //无加入对象,有更新对象
                    Instances[iteminterface.Name][itemType.Name] = obj;
                }
            }

把对象加入到集合中

/// <summary>
        /// 返回查找的脚本实例
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <returns></returns>
        public IEnumerable<T> GetInstances<T>()
        {
            //使用枚举迭代器,避免了再一次创建对象
            string typeName = typeof(T).Name;
            if (Instances.ContainsKey(typeName))
            {
                foreach (var item in Instances[typeName])
                {
                    if (item.Value is T)
                    {
                        yield return (T)item.Value;
                    }
                }
            }
        }

最后附加全套源码

提供 源文件 .cs  和程序集加载 .dll

提供支持路径递归加载和指定文件加载方式,并且提供了后缀筛选和附加dll加载。

using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

/**
 *
 * @author 失足程序员
 * @Blog http://www.cnblogs.com/ty408/
 * @mail 492794628@qq.com
 * @phone 13882122019
 *
 */
namespace Sz.Network.LoadScriptPool
{

    /// <summary>
    /// 加载脚本文件
    /// </summary>
    public class LoadScriptManager
    {

        private static LoadScriptManager instance = new LoadScriptManager();
        public static LoadScriptManager GetInstance { get { return instance; } }

        HashSet<String> ddlNames = new HashSet<string>();

        LoadScriptManager()
        {
            var asss = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var item in asss)
            {
                foreach (var item222 in item.GetModules(false))
                {
                    ddlNames.Add(item222.FullyQualifiedName);
                }
            }
        }

        #region 返回查找的脚本实例 public IEnumerable<T> GetInstances<T>()
        /// <summary>
        /// 返回查找的脚本实例
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <returns></returns>
        public IEnumerable<T> GetInstances<T>()
        {
            //使用枚举迭代器,避免了再一次创建对象
            string typeName = typeof(T).Name;
            if (Instances.ContainsKey(typeName))
            {
                foreach (var item in Instances[typeName])
                {
                    if (item.Value is T)
                    {
                        yield return (T)item.Value;
                    }
                }
            }
        }

        ConcurrentDictionary<string, ConcurrentDictionary<string, object>> Instances = new ConcurrentDictionary<string, ConcurrentDictionary<string, object>>();

        #endregion

        #region 根据指定的文件动态编译获取实例 public void LoadCSharpFile(string[] paths, List<String> extensionNames, params string[] dllName)
        /// <summary>
        /// 根据指定的文件动态编译获取实例
        /// <para>如果需要加入调试信息,加入代码 System.Diagnostics.Debugger.Break();</para>
        /// <para>如果传入的是目录。默认只会加载目录中后缀“.cs”文件</para>
        /// </summary>
        /// <param name="paths">
        /// 可以是目录也可以是文件路径
        /// </param>
        /// <param name="dllName">加载的附加DLL文件的路径,绝对路径</param>
        public void LoadCSharpFile(string[] paths, params string[] dllName)
        {
            LoadCSharpFile(paths, null, dllName);
        }

        List<String> csExtensionNames = new List<String>() { ".cs" };

        /// <summary>
        /// 根据指定的文件动态编译获取实例
        /// <para>如果需要加入调试信息,加入代码 System.Diagnostics.Debugger.Break();</para>
        /// <para>如果传入的是目录。默认只会加载目录中后缀“.cs”文件</para>
        /// </summary>
        /// <param name="paths">
        /// 可以是目录也可以是文件路径
        /// </param>
        /// <param name="extensionNames">需要加载目录中的文件后缀</param>
        /// <param name="dllName">加载的附加DLL文件的路径,绝对路径</param>
        public void LoadCSharpFile(string[] paths, List<String> extensionNames, params string[] dllName)
        {
            GC.Collect();
            if (extensionNames == null)
            {
                extensionNames = csExtensionNames;
            }
            foreach (var item in dllName)
            {
                ddlNames.Add(item);
            }
            foreach (var item in ddlNames)
            {
                Console.WriteLine("加载依赖程序集:" + item);
            }
            List<String> fileNames = new List<String>();
            ActionPath(paths, extensionNames, ref fileNames);
            string[] tempDllNames = ddlNames.ToArray();
            foreach (var path in fileNames)
            {
                CSharpCodeProvider provider = new CSharpCodeProvider();
                CompilerParameters parameter = new CompilerParameters();
                //不输出编译文件
                parameter.GenerateExecutable = false;
                //生成调试信息
                parameter.IncludeDebugInformation = true;
                //需要调试必须输出到内存
                parameter.GenerateInMemory = true;
                //添加需要的程序集
                parameter.ReferencedAssemblies.AddRange(tempDllNames);
                //编译文件
                Console.WriteLine("动态加载文件:" + path);
                CompilerResults result = provider.CompileAssemblyFromFile(parameter, path);//根据制定的文件加载脚本
                if (result.Errors.HasErrors)
                {
                    var item = result.Errors.GetEnumerator();
                    while (item.MoveNext())
                    {
                        Console.WriteLine("动态加载文件出错了!" + item.Current.ToString());
                    }
                }
                else
                {
                    ActionAssembly(result.CompiledAssembly);
                }
            }
        }
        #endregion

        #region 根据指定的文件动态编译获取实例 public void LoadDll(string[] paths)

        List<String> dllExtensionNames = new List<String>() { ".dll", ".DLL" };

        /// <summary>
        /// 根据指定的文件动态编译获取实例
        /// <para>如果需要加入调试信息,加入代码 System.Diagnostics.Debugger.Break();</para>
        /// </summary>
        /// <param name="paths">
        /// 可以是目录也可以是文件路径
        /// <para>如果传入的是目录。只会加载目录中后缀“.dll”,“.DLL”文件</para>
        /// </param>
        public void LoadDll(string[] paths)
        {
            GC.Collect();
            List<String> fileNames = new List<String>();
            ActionPath(paths, dllExtensionNames, ref fileNames);
            byte[] bFile = null;
            foreach (var path in fileNames)
            {
                try
                {
                    Console.WriteLine("动态加载文件:" + path);
                    using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                    {
                        using (BinaryReader br = new BinaryReader(fs))
                        {
                            bFile = br.ReadBytes((int)fs.Length);
                            ActionAssembly(Assembly.Load(bFile));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("动态加载文件:" + ex);
                }
            }
        }
        #endregion

        #region 处理加载出来的实例 void ActionAssembly(Assembly assembly)
        /// <summary>
        /// 处理加载出来的实例
        /// </summary>
        /// <param name="assembly"></param>
        void ActionAssembly(Assembly assembly)
        {
            ConcurrentDictionary<string, ConcurrentDictionary<string, object>> tempInstances = new ConcurrentDictionary<string, ConcurrentDictionary<string, object>>();
            //获取加载的所有对象模型
            Type[] instances = assembly.GetExportedTypes();
            foreach (var itemType in instances)
            {
                //获取单个模型的所有继承关系和接口关系
                Type[] interfaces = itemType.GetInterfaces();
                //生成实例
                object obj = Activator.CreateInstance(itemType);
                foreach (var iteminterface in interfaces)
                {
                    //加入对象集合
                    if (!Instances.ContainsKey(iteminterface.Name))
                    {
                        tempInstances[iteminterface.Name] = new ConcurrentDictionary<string, object>();
                    }
                    tempInstances[iteminterface.Name][itemType.Name] = obj;
                }
            }
            Instances = tempInstances;
        }
        #endregion

        #region 处理传入的路径 void ActionPath(string[] paths, List<String> extensionNames, ref List<String> fileNames)
        /// <summary>
        /// 处理传入的路径,
        /// </summary>
        /// <param name="paths"></param>
        /// <param name="extensionNames"></param>
        /// <param name="fileNames"></param>
        void ActionPath(string[] paths, List<String> extensionNames, ref List<String> fileNames)
        {
            foreach (var path in paths)
            {
                if (System.IO.Path.HasExtension(path))
                {
                    if (System.IO.File.Exists(path))
                    {
                        fileNames.Add(path);
                    }
                    else
                    {
                        Console.WriteLine("动态加载 无法找到文件:" + path);
                    }
                }
                else
                {
                    GetFiles(path, extensionNames, ref fileNames);
                }
            }
        }
        #endregion

        #region 根据指定文件夹获取指定路径里面全部文件 void GetFiles(string sourceDirectory, List<String> extensionNames, ref  List<String> fileNames)
        /// <summary>
        /// 根据指定文件夹获取指定路径里面全部文件
        /// </summary>
        /// <param name="sourceDirectory">目录</param>
        /// <param name="extensionNames">需要获取的文件扩展名</param>
        /// <param name="fileNames">返回文件名</param>
        void GetFiles(string sourceDirectory, List<String> extensionNames, ref  List<String> fileNames)
        {
            if (!Directory.Exists(sourceDirectory))
            {
                return;
            }
            {
                //获取所有文件名称
                string[] fileName = Directory.GetFiles(sourceDirectory);
                foreach (string path in fileName)
                {
                    if (System.IO.File.Exists(path))
                    {
                        string extName = System.IO.Path.GetExtension(path);
                        if (extensionNames.Contains(extName))
                        {
                            fileNames.Add(path);
                        }
                        else
                        {
                            Console.WriteLine("无法识别文件:" + path);
                        }
                    }
                    else
                    {
                        Console.WriteLine("动态加载 无法找到文件:" + path);
                    }
                }
            }
            //拷贝子目录
            //获取所有子目录名称
            string[] directionName = Directory.GetDirectories(sourceDirectory);
            foreach (string directionPath in directionName)
            {
                //递归下去
                GetFiles(directionPath, extensionNames, ref fileNames);
            }
        }
        #endregion
    }
}

一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版的更多相关文章

  1. 【Quick 3.3】资源脚本加密及热更新(三)热更新模块

    [Quick 3.3]资源脚本加密及热更新(三)热更新模块 注:本文基于Quick-cocos2dx-3.3版本编写 一.介绍 lua相对于c++开发的优点之一是代码可以在运行的时候才加载,基于此我们 ...

  2. seajs实现JavaScript 的 模块开发及按模块加载

    seajs实现了JavaScript 的 模块开发及按模块加载.用来解决繁琐的js命名冲突,文件依赖等问题,其主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载. 官方文档:http:/ ...

  3. Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c  start_ke ...

  4. iOS开发UI篇—懒加载

    iOS开发UI篇—懒加载 1.懒加载基本 懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...

  5. Android开发中如何解决加载大图片时内存溢出的问题

    Android开发中如何解决加载大图片时内存溢出的问题    在Android开发过程中,我们经常会遇到加载的图片过大导致内存溢出的问题,其实类似这样的问题已经屡见不鲜了,下面将一些好的解决方案分享给 ...

  6. 【Java Web开发学习】Spring加载外部properties配置文件

    [Java Web开发学习]Spring加载外部properties配置文件 转载:https://www.cnblogs.com/yangchongxing/p/9136505.html 1.声明属 ...

  7. SwiftUI - 一步一步教你使用UIViewRepresentable封装网络加载视图(UIActivityIndicatorView)

    概述 网络加载视图,在一个联网的APP上可以讲得上是必须要的组件,在SwiftUI中它并没有提供如 UIKit 中的UIActivityIndicatorView直接提供给我们调用,但是我们可以通过 ...

  8. Excel催化剂开源第7波-VSTO开发中Ribbon动态加载菜单

    在VS开发环境中,特别是VSTO的开发,微软已经现成地给开发者准备了设计器模式的功能区开发,相对传统的VBA.ExcelDna和其他方式的COM加载项开发来说,不需要手写xml功能区,直接类似拖拉窗体 ...

  9. IClient for js开发之地图的加载

    进行web开发之前首先需要安装IServer以及iClient for JavaScript的开发包.在这两中都具备的前提下进行第一步,如何调用IServer中发布的服务 调用iServer 中发布的 ...

随机推荐

  1. PHP的FastCGI

    CGI全称是“通用网关接口”(Common Gateway Interface), 它可以让一个客户端,从网页浏览器向执行在Web服务器上的程序请求数据. CGI描述了客户端和这个程序之间传输数据的一 ...

  2. SQL Server 2016 CTP2.3 的关键特性

    SQL Server 2016 CTP2.3 的关键特性 数据库方面的增强 Row Level Security已经支持In-memory OLTP 表.用户现在可以对内存优化表实施row-level ...

  3. CMS模板引擎:XHtmlAction

    前言: 先说说大伙关心的工作上的事,在上家公司任了一个多月的技术经理后,和公司中止了合作关系. 主要原因在于一开始的待遇没谈的太清楚: 1:没有合同,没有公积金,连社保也没交. 2:工资的30%变成了 ...

  4. PostgreSQL配置优化

    硬件和系统配置 操作系统 Ubuntu13.04 系统位数 64 CPU Intel(R) Core(TM)2 Duo CPU 内存 4G 硬盘 Seagate ST2000DM001-1CH164 ...

  5. UI控件(UISlider)

    @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UISlider* slider = [[UISli ...

  6. CSharpGL(3)使用CSharpGL.vsix插件

    CSharpGL(3)使用CSharpGL.vsix插件 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo,更 ...

  7. 喜大普奔,微软Microsoft JDBC Driver For SQL Server已发布到maven中央仓库

    相信通过java和SQLServer开发应用的同学们都经历过如下类似的问题. 微软提供的JDBC官方驱动没有放置在Maven仓库中,这样如果你的Java应用需要访问SQL Server,你不得不下载s ...

  8. 解决微信公众号OAuth出现40029(invalid code,不合法的oauth_code)的错误

    关于OAuth 官方教程:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842&token=&la ...

  9. salesforce 零基础学习(五十)自定义View或者List以及查看系统原来的View或者List

    salesforce给我们提供了标准的页面,比如标准的页面包括标准的列表和标准的详细页视图.有的时候我们想要自定义视图,比如做一个项目的时候不希望使用者直接通过ID查看到标准的详细页,而是跳转到指定处 ...

  10. iOS完整学习路线图