Lua结合C#调用C++或者C的函数

来源:互联网 发布:jsp无法import java类 编辑:程序博客网 时间:2024/06/01 17:07

本文所用到的知识点:
1.Lua与C#交互,参考这里
2.C#调用非托管DLL的方法(dllImport或者kernel32.dll中的LoadLibrary)
3.C# 反射动态生成方法并调用。
最终想达到的效果是:在Lua中能直接填写Dll中的函数,并传递参数调用。

一、Lua与C#交互:
在上面的链接中,将NLua下载下来。NLua是C#和Lua交互的桥梁。有两种方式:
a.C#+NLua+KopiLua(KopiLua是Lua的纯C#实现库)
b.C#+NLua+KeraLua+Lua52.dll
采用以上两种方法中的一种,需要将NLua的编译条件改一下,例如我用的是.C#+NLua+KopiLua的方式,NLua条件编译截图如下:
这里写图片描述
交互如下:

class Program{    static void Main(string[] args)    {        NLua.Lua lua = new NLua.Lua();//初始化Lua虚拟机        lua.LoadCLRPackage();//加载clr,可以在Lua中随意调用C#的DLL        Program p = new Program();        lua["this"] = p;//将对象传入Lua,对象的所有的公开方法都可以在lua中使用        lua.DoFile("test.lua");//调用文件test.lua,可以在文件中使用this    }    public void DebugOut(object msg)    {         Console.WriteLine(msg);    }}

二、C#调用C++的DLL以及动态生成方法
无论采用DLLImport还是kernel32.dll中的三个函数的方法,都必须事先声明方法或者委托,由于我们想实现动态的生成委托或者方法来调用,所以必须动态的生成与C++DLL中的函数相同签名的方法。故在网上查询了一下,将别人的博文偷过来:原文请参考这里

 /// <summary>    /// 参数传递方式枚举,ByValue 表示值传递,ByRef 表示址传递    /// </summary>    public enum ModePass    {        ByValue = 0x0001,        ByRef = 0x0002    }    /// <summary>    /// 常见类型    /// </summary>    public enum CLRType    {        String=0,        Int32=1,        Double=2,        Bool=3    }    public class DllHelper    {        [DllImport("kernel32.dll")]        public static extern IntPtr LoadLibrary(string lpFileName);        [DllImport("kernel32.dll")]        public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProceName);        [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]        public static extern bool FreeLibrary(IntPtr hModule);        /// <summary>        /// Loadlibrary 返回的函数库模块的句柄        /// </summary>        private IntPtr hModule = IntPtr.Zero;        /// <summary>        /// GetProcAddress 返回的函数指针        /// </summary>        private IntPtr farProc = IntPtr.Zero;        /// <summary>        /// 装载 Dll        /// </summary>        /// <param name="lpFileName">DLL 文件名 </param>        public void LoadDll(string lpFileName)        {            hModule = LoadLibrary(lpFileName);            if (hModule == IntPtr.Zero)                throw (new Exception(" 没有找到 :" + lpFileName + "."));        }        /// <summary>        /// 获得函数指针        /// </summary>        /// <param name="lpProcName"> 调用函数的名称 </param>        public void LoadFun(string lpProcName)        { // 若函数库模块的句柄为空,则抛出异常            if (hModule == IntPtr.Zero)                throw (new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));            // 取得函数指针            farProc = GetProcAddress(hModule, lpProcName);            // 若函数指针,则抛出异常            if (farProc == IntPtr.Zero)                throw (new Exception(" 没有找到 :" + lpProcName + " 这个函数的入口点 "));        }        /// <summary>        /// 卸载 Dll        /// </summary>        public void UnLoadDll()        {            FreeLibrary(hModule);            hModule = IntPtr.Zero;            farProc = IntPtr.Zero;        }        /// <summary>        /// 调用所设定的函数        /// </summary>        /// <param name="ObjArray_Parameter"> 实参 </param>        /// <param name="TypeArray_ParameterType"> 实参类型 </param>        /// <param name="ModePassArray_Parameter"> 实参传送方式 </param>        /// <param name="Type_Return"> 返回类型 </param>        /// <returns> 返回所调用函数的 object</returns>        public object Invoke(object[] ObjArray_Parameter, Type[] TypeArray_ParameterType, ModePass[] ModePassArray_Parameter, Type Type_Return)        {            // 下面 3 个 if 是进行安全检查 , 若不能通过 , 则抛出异常            if (hModule == IntPtr.Zero)                throw (new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));            if (farProc == IntPtr.Zero)                throw (new Exception(" 函数指针为空 , 请确保已进行 LoadFun 操作 !"));            if (ObjArray_Parameter.Length != ModePassArray_Parameter.Length)                throw (new Exception(" 参数个数及其传递方式的个数不匹配 ."));            // 下面是创建 MyAssemblyName 对象并设置其 Name 属性            AssemblyName MyAssemblyName = new AssemblyName();            MyAssemblyName.Name = "InvokeFun";            // 生成单模块配件            AssemblyBuilder MyAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName, AssemblyBuilderAccess.Run);            ModuleBuilder MyModuleBuilder = MyAssemblyBuilder.DefineDynamicModule("InvokeDll");            // 定义要调用的方法 , 方法名为“ MyFun ”,返回类型是“ Type_Return ”参数类型是“ TypeArray_ParameterType ”            MethodBuilder MyMethodBuilder = MyModuleBuilder.DefineGlobalMethod("MyFun", MethodAttributes.Public | MethodAttributes.Static, Type_Return, TypeArray_ParameterType);            // 获取一个 ILGenerator ,用于发送所需的 IL            ILGenerator IL = MyMethodBuilder.GetILGenerator();            int i;            for (i = 0; i < ObjArray_Parameter.Length; i++)            {// 用循环将参数依次压入堆栈                switch (ModePassArray_Parameter[i])                {                    case ModePass.ByValue:                        IL.Emit(OpCodes.Ldarg, i);                        break;                    case ModePass.ByRef:                        IL.Emit(OpCodes.Ldarga, i);                        break;                    default:                        throw (new Exception(" 第 " + (i + 1).ToString() + " 个参数没有给定正确的传递方式 ."));                }            }            if (IntPtr.Size == 4)            {// 判断处理器类型                IL.Emit(OpCodes.Ldc_I4, farProc.ToInt32());            }            else if (IntPtr.Size == 8)            {                IL.Emit(OpCodes.Ldc_I8, farProc.ToInt64());            }            else            {                throw new PlatformNotSupportedException();            }            IL.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, Type_Return, TypeArray_ParameterType);            IL.Emit(OpCodes.Ret); // 返回值            MyModuleBuilder.CreateGlobalFunctions();            // 取得方法信息            MethodInfo MyMethodInfo = MyModuleBuilder.GetMethod("MyFun");            return MyMethodInfo.Invoke(null, ObjArray_Parameter);// 调用方法,并返回其值        }        /// <summary>        /// 调用方法,参数类型默认按照值传递,如果不是按照值传递,则需要全部写上按什么传递        /// </summary>        /// <param name="paras">调用方法所需要的参数</param>        /// <param name="returnType">方法的返回值类型</param>        /// <param name="modePassArray_Parameter">参数按照什么传递</param>        /// <returns></returns>        public object Invoke(object[] paras,Type returnType, ModePass[] modePassArray_Parameter=null)        {            if (paras == null)            {                paras = new object[] { };            }            Type[] paras_type;  //传递的参数类型                      if (paras.Length == 0)            {                paras_type = new Type[] { };                modePassArray_Parameter=new ModePass[]{};            }            else            {                paras_type = new Type[paras.Length];                for (int i = 0; i < paras.Length; i++)                {                    paras_type[i] = paras[i].GetType();                }                if (modePassArray_Parameter == null || modePassArray_Parameter.Length == 0)                {                    modePassArray_Parameter = new ModePass[paras.Length];                    for (int i = 0; i < paras.Length; i++)                    {                        modePassArray_Parameter[i] = ModePass.ByValue;//默认按照值传递                    }                }            }            return Invoke(paras, paras_type, modePassArray_Parameter, returnType);        }        public object Invoke(NLua.LuaTable paraTable,NLua.LuaTable paraTypeTable, int returnType, NLua.LuaTable paraPassTable = null)        {            Type type=null;            type = GetClrType((CLRType)returnType);            if (paraTable == null || paraTable.Keys.Count == 0)            {                return Invoke(new object[] { }, new Type[] { }, new ModePass[] { }, type);            }            else            {                object[] paras = new object[paraTable.Keys.Count];//参数                Type[] parasType = new Type[paraTypeTable.Keys.Count];//传递参数类型                ModePass[] parasPassType = new ModePass[paraTable.Keys.Count];//以什么样的方式传递                Type returnValueType=GetClrType((CLRType)(returnType));                int count2 = 0;                foreach (var key in paraTypeTable.Keys)                {                    parasType[count2] = GetClrType((CLRType)int.Parse(paraTypeTable[key].ToString()));                    count2++;                }                int count1 = 0;                foreach (var key in paraTable.Keys)                {                    paras[count1] = ConvertToType(parasType[count1], paraTable[key]);//从Lua中传过来的数字都是double,所以要再次转一次                    count1++;                }                if (paraPassTable == null )                {                                       for (int i = 0; i < parasPassType.Length; i++)                    {                        parasPassType[i] = ModePass.ByValue;//默认按照值传递                    }                }                else if (paraPassTable.Keys.Count == 0)                {                    for (int i = 0; i < parasPassType.Length; i++)                    {                        parasPassType[i] = ModePass.ByValue;//默认按照值传递                    }                }                else                {                    int count3 = 0;                    foreach (var key in paraPassTable.Keys)                    {                        parasPassType[count3] = (ModePass)int.Parse(paraPassTable[key].ToString());                        count3++;                    }                }                return Invoke(paras, parasType, parasPassType,returnValueType);            }                 }        private Type GetClrType(CLRType type)        {            switch (type)            {                case CLRType.String:                    return typeof(string);                case CLRType.Int32:                    return typeof(int);                case CLRType.Double:                    return typeof(double);                case CLRType.Bool:                    return typeof(bool);                default:                    return typeof(object);            }        }        private object ConvertToType(Type type,object value)        {            if(type==typeof(string))            {                return value.ToString();            }            else if(type==typeof(int))            {                return int.Parse(value.ToString());            }            else if(type==typeof(double))            {                return double.Parse(value.ToString());            }            else if (type == typeof(bool))            {                return bool.Parse(value.ToString());            }            else             {                return value;            }                 }    }

三、演示:
现有test.lua在程序更目录下,test.lua的内容如下:

ClrType={String=0,Int32=1,Double=2,Bool=3};ModePass={ByValue=1,ByRef=2};function Main()    dllHelper:LoadDll("MFCSuibian.dll");    dllHelper:LoadFun("Add");    local ret=dllHelper:Invoke({1,9},{ClrType.Int32,ClrType.Int32},ClrType.Int32);    this:DebugOut(ret);    ret=dllHelper:Invoke({1,100},{ClrType.Int32,ClrType.Int32},ClrType.Int32);    this:DebugOut(ret);end

其中MFCSuibian.dll为C++的DLL,位于程序运行目录下,其中有两个函数,“Add”和“Devide”,传入两个int,分别返回其值得和与差。
然后使用C#来调用Lua中的Main函数:

 class Program    {        static void Main(string[] args)        {            NLua.Lua lua = new NLua.Lua();//初始化Lua虚拟机            lua.LoadCLRPackage();//加载clr,可以在Lua中随意调用C#的DLL            Program p = new Program();            lua["this"] = p;//将对象传入Lua,对象的所有的公开方法都可以在lua中使用            DllHelper dllHelper = new DllHelper();            lua["dllHelper"] = dllHelper;            lua.DoFile("test.lua");            lua.DoString("return Main()");            Console.Read();        }        public void DebugOut(object msg)        {            Console.WriteLine(msg);        }    }

最后运行结果:
这里写图片描述
再调用user32.dll中的CloseClipboard方法。该方法不需要传入参数,返回一个Bool值。修改test.lua的Main函数代码如下:

ClrType={String=0,Int32=1,Double=2,Bool=3};ModePass={ByValue=1,ByRef=2};function Main()    dllHelper:LoadDll("user32.dll");    dllHelper:LoadFun("CloseClipboard");    local ret=dllHelper:Invoke(nil,nil,ClrType.Bool);    this:DebugOut(ret);end

运行结果如下:
这里写图片描述
本文实例下载(含NLua源码)

原创粉丝点击