2

The C# Program need to call the dll, but it looks like the parameter out is same to the value in. So I make this short sample to test. Here is the C++ Dll code: .h

#define EtrpDll extern "C" __declspec(dllexport)
EtrpDll void __fastcall WhyWrong(int &m);

.cpp

#include"Header.h"
#include<iostream>
void __fastcall WhyWrong(int &m) {
    m++;
}

int main() {
    int m = 0;
    WhyWrong(m);
    std::cout << m;
    int y;
    std::cin >> y;
}

And the C# code: .cs

using System;

using System.Runtime.InteropServices;
using System.Reflection;
using System.Reflection.Emit;

namespace DllTest
{
    class Class1 {
        [DllImport("Kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);
        [DllImport("Kernel32.dll")]
        private static extern bool FreeLibrary(IntPtr hModule);
        [DllImport("Kernel32.dll")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
        [DllImport("Kernel32.dll")]
        private static extern int GetLastError();
        private static IntPtr hModule = IntPtr.Zero;
        private static AssemblyName GbsAssemblyName;
        private static AssemblyBuilder GbsAssemblyBuilder;
        private static ModuleBuilder GbsModuleBuilder;
        public enum ModePass {
            ByValue = 0x0001,
            ByRef = 0x0002
        }

        private IntPtr GFuncPtr = IntPtr.Zero;
        private Type[] GParaType;
        private ILGenerator GIL;
        private ModePass[] GMdPass;
        public object[] GObject;
        private string Mname;

        private static Type GReturn = typeof(void);




        static void Main() {
            Class1.DllIni();
            Class1 G_WhyW = new Class1(new Type[1] { typeof(int) }, new Class1.ModePass[1] { Class1.ModePass.ByRef }, new object[1] { (int)0 });
            G_WhyW.LoadFunc("WhyWrong");
            Class1.GCreateGlbFunc();
            G_WhyW.GObject[0] = (int)1;//Input
            G_WhyW.InvokeDllFunc();
            Console.WriteLine((int)G_WhyW.GObject[0]);
            Console.ReadLine();
            Class1.UnLoadDll();
        }


        public static void DllIni(){
            hModule = LoadLibrary("DllTest.dll");
            if (hModule == IntPtr.Zero) {
                Console.WriteLine("DLL Not Loaded");
                int e = GetLastError();
                Console.WriteLine("Error Code: " + e);
            }
            GbsAssemblyName = new AssemblyName();
            GbsAssemblyName.Name = "GbsCore";
            GbsAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(GbsAssemblyName, AssemblyBuilderAccess.Run);
            GbsModuleBuilder = GbsAssemblyBuilder.DefineDynamicModule("FuncGrp");
        }
        public static void GCreateGlbFunc() {
            GbsModuleBuilder.CreateGlobalFunctions();
        }
        public void LoadFunc(string lpProcName) {
            GFuncPtr = GetProcAddress(hModule, lpProcName);
            if (GFuncPtr == IntPtr.Zero) {
                Console.WriteLine("Function " + lpProcName + " Not Loaded");
            }
            Mname = lpProcName + "_T";
            MethodBuilder GbsMethodBuilder = GbsModuleBuilder.DefineGlobalMethod(Mname, MethodAttributes.Public | MethodAttributes.Static, GReturn, GParaType);
            GIL = GbsMethodBuilder.GetILGenerator();
            if (GObject != null) {
                for (int i = 0; i < GObject.Length; i++) {
                    switch (GMdPass[i]) {
                        case ModePass.ByValue:
                            GIL.Emit(OpCodes.Ldarg, i);
                            break;
                        case ModePass.ByRef:
                            GIL.Emit(OpCodes.Ldarga, i);
                            break;
                        default:
                            Console.WriteLine("Pass Mode Error");
                            break;
                    }
                }
            }
            if (IntPtr.Size == 4) { GIL.Emit(OpCodes.Ldc_I4, GFuncPtr.ToInt32()); } //Platform
            else if (IntPtr.Size == 8) { GIL.Emit(OpCodes.Ldc_I8, GFuncPtr.ToInt64()); }
            else { throw new PlatformNotSupportedException(); }
            GIL.EmitCalli(OpCodes.Calli, CallingConvention.FastCall, GReturn, GParaType);
            GIL.Emit(OpCodes.Ret);
        }
        public void InvokeDllFunc() {
            MethodInfo GbsMethodInfo;
            if (GParaType == null) {
                GbsMethodInfo = GbsModuleBuilder.GetMethod(Mname);
            }
            else {
                GbsMethodInfo = GbsModuleBuilder.GetMethod(Mname, GParaType);
            }
            GbsMethodInfo.Invoke(null, GObject);//return void
        }
        public static void UnLoadDll() {
            FreeLibrary(hModule);
            hModule = IntPtr.Zero;
        }

        public Class1(Type[] T, ModePass[] MP, object[] OB) {
            GParaType = T;
            GMdPass = MP;
            GObject = OB;
        }
    }
}

Before invoking function WhyWrong, the value of parameter is 1, However the result I get on my try is 1 too. It should be 2, isn't it?

Dangee
  • 57
  • 4
  • 1
    question - is there a reason you're using `LoadLibrary` rather than just `[DllImport]` for the external method? – Marc Gravell Oct 20 '18 at 23:23
  • There are many static variables in my original project. `[DllImport]` cannot correctly deal with static variables. By using `[DllImport]`, if more than 1 dll are called. all these dlls will share same global variables. And if the program was not close normally, the dll will not release. When the program run again, old variables are still there and make program crash. – Dangee Oct 20 '18 at 23:35
  • 1
    You are passing `GParaType` in `DefineGlobalMethod`. And your `GParaType` is `new[] { typeof(int) }`, but not `new[] { typeof(int).MakeByRefType() }`. thus you are creating `WhyWrong_T(int)`, but not `WhyWrong_T(ref int)`. – user4003407 Oct 21 '18 at 09:40
  • `__fastcall` is not in fact supported. Backgrounder [is here](https://stackoverflow.com/a/15664100/17034). – Hans Passant Oct 23 '18 at 23:38

1 Answers1

0

This signature of the dynamic method is void(int), not void(ref int) as you intend, since it is initialized with new Type[]{typeof(int)}.

Using reflection to call methods with reference parameters is supported (and modifies the argument array), so get rid of the ModePass enum altogether and use typeof(int).MakeByRefType() when defining the method.

The whole switch (GMdPass[i]) is unnecessary. Since the argument is by reference, it will be simply provided to the called function as the reference itself (ldarg is sufficient).

By the way, why don't you simply use Marshal.GetDelegateForFunctionPointer? It pretty much does the same like the dynamic method, but should be faster. You only need a delegate type (non-generic) to be defined.

Also, isn't FastCall unsupported?

IS4
  • 11,945
  • 2
  • 47
  • 86