一个项目用到C#,调用的外部函数很多,在网上找了一个名为dld的调用dll的类,感觉很强大,但遇到一个问题就是用引用方式传入的参数,在dll中改变了该参数的值时在外部仍得到的是原始值;比如在testdll.dll中有这么一个函数:
 int test(int a1, int a2, int* a3)
{
*a3=a1+a2;  
         return a1+a2;
}我如下方式调用,得到的c始终是0,通过委托等其它方式调用都是对的,请指教一下如何处理?
dld myfun = new dld();
            myfun.LoadDll("testdll.dll"); // 加载 DLL 
            myfun.LoadFun("test"); // 调入函数 
            int a=4, b=5,c=0;
            object[] Parameters = new object[] { a, b, c }; // 实参             Type[] ParameterTypes = new Type[] { typeof(int), typeof(int),typeof(int) }; // 实参类型            ModePass[] themode = new ModePass[] { ModePass.ByValue, ModePass.ByValue, ModePass.ByRef }; // 传送方式
            Type Type_Return = typeof(int); // 返回类型为 int 
            myfun.Invoke(Parameters, ParameterTypes, themode, Type_Return);
            myfun.UnLoadDll();

解决方案 »

  1.   

    //dld是这样的
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices; // 用 DllImport 需用此 命名空间 
    using System.Reflection; // 使用 Assembly 类需用此 命名空间 
    using System.Reflection.Emit; // 使用 ILGenerator 需用此 命名空间 namespace test
    {    /// <summary>     /// 参数传递方式枚举 ,ByValue 表示值传递 ,ByRef 表示址传递     /// </summary>     public enum ModePass
        {        ByValue = 0x0001,        ByRef = 0x0002    } 
        class dld
        {
            /// <summary> 
            /// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName); 
            /// </summary> 
            /// <param name="lpFileName">DLL 文件名 </param> 
            /// <returns> 函数库模块的句柄 </returns> 
            [DllImport("kernel32.dll")]
            static extern IntPtr LoadLibrary(string lpFileName);
            /// <summary> 
            /// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName); 
            /// </summary> 
            /// <param name="hModule"> 包含需调用函数的函数库模块的句柄 </param> 
            /// <param name="lpProcName"> 调用函数的名称 </param> 
            /// <returns> 函数指针 </returns> 
            [DllImport("kernel32.dll")]
            static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
            /// <summary> 
            /// 原型是 : BOOL FreeLibrary(HMODULE hModule); 
            /// </summary> 
            /// <param name="hModule"> 需释放的函数库模块的句柄 </param> 
            /// <returns> 是否已释放指定的 Dll</returns> 
            [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
            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 + "."));
            }        public void LoadDll(IntPtr HMODULE) //若已有已装载Dll的句柄,可以使用LoadDll方法的第二个版本:
            {
                if (HMODULE == IntPtr.Zero)
                    throw (new Exception(" 所传入的函数库模块的句柄 HMODULE 为空 ."));
                hModule = HMODULE;
            }        /// <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> 
            /// 获得函数指针 
            /// </summary> 
            /// <param name="lpFileName"> 包含需调用函数的 DLL 文件名 </param> 
            /// <param name="lpProcName"> 调用函数的名称 </param> 
            public void LoadFun(string lpFileName, string lpProcName)
            { // 取得函数库模块的句柄 
                hModule = LoadLibrary(lpFileName);
                // 若函数库模块的句柄为空,则抛出异常 
                if (hModule == IntPtr.Zero)
                    throw (new Exception(" 没有找到 :" + lpFileName + "."));
                // 取得函数指针 
                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="IntPtr_Function"> 函数指针 </param>         /// <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(IntPtr IntPtr_Function, object[] ObjArray_Parameter, Type[] TypeArray_ParameterType, ModePass[]        ModePassArray_Parameter, Type Type_Return)
            {            // 下面 2 个 if 是进行安全检查 , 若不能通过 , 则抛出异常             if (hModule == IntPtr.Zero)                throw (new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));            if (IntPtr_Function == IntPtr.Zero)                throw (new Exception(" 函数指针 IntPtr_Function 为空 !"));            farProc = IntPtr_Function;            return Invoke(ObjArray_Parameter, TypeArray_ParameterType, ModePassArray_Parameter, Type_Return);        }
        }
    }
      

  2.   

    问题在这
      int a=4, b=5,c=0;
      object[] Parameters = new object[] { a, b, c }; // 实参  
    你实际传递的是个数组里的abc
    并不是你定义的int abc
    函数执行完你在跑一边
    c=(int)Parameters[2];
    应该就可以了
      

  3.   

    楼主,你把问题复杂化了!对于平台调用,一般是指静态调用,而你用的是动态调用,当然也可以实现要求,却是把问题大大复杂化,一般人很难理解!就你的这个函数:int test(int a1, int a2, int* a3),“但遇到一个问题就是用引用方式传入的参数,在dll中改变了该参数的值时在外部仍得到的是原始值”,
    其实在C#,很简单就对应了:[DllImport("xxx.dll")]
    public static extern int test(int a1, int a2,ref int a3);使用:
    int retRef=-1;
    test(1,2,ref retRef);你运行,试试看retRef是不是3!
      

  4.   

    LZ好乱啊
    你试试这样 点击你项目名字下面那个引用 右键添加引用
    然后把你的这些dll全部引用进来如果是别人的话 可以在工具栏  右键选项卡 把你的dll添加进去
      

  5.   

    没用过LZ这个库.感觉动态过头了.
    如果函数原型确定的话.
    LoadLibrary + GetProcAddress
    后转成预定义的delegate比你这个来得方便的多.
      

  6.   

    “这样做肯定是没问题的,要得到正确答案的调用方法也还多,问题是我要做的是动态调用,要调用的函数很多,我就觉得这个类确实很合适”,我在强调一次,你这样做并没有错!当然,动态调用使用起来要高级,比较自动化,就让net的反射机制!但是,也会带来代价:晦涩难懂,调试困难!在你明确知道C++的函数接口定义(当然,动态调用至少要知道函数原型:参数+返回值)的情况下,用静态调用是最合理、最直接的方法。如果你一定要最强所谓“高端”技术,我也没必要说服你,因为每个人有自己的做法。
      

  7.   

    我改用委托的方式来处理了,请问下这个调用约定在哪里处理呀?我只知道DllImport可以设置这个参数
      

  8.   

    UnmanagedFunctionPointerAttribute
    具体的查文档