Как C # IL возвращает параметры по ссылке при вызове C ++ DLL - PullRequest
0 голосов
/ 21 октября 2018

Программа на C # должна вызывать dll, но похоже, что параметр out совпадает со значением in. Поэтому я сделаю этот короткий пример для тестирования.Вот код C ++ Dll: .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;
}

И код C #: .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;
        }
    }
}

До вызова функции WhyWrong,значение параметра равно 1, однако результат, который я получаю при попытке, тоже равен 1.Должно быть 2, не так ли?

1 Ответ

0 голосов
/ 24 октября 2018

Эта сигнатура динамического метода равна void(int), а не void(ref int), как вы и предполагали, поскольку она инициализируется с new Type[]{typeof(int)}.

Поддерживается использование отражения для вызова методов со ссылочными параметрами (и изменяетмассив аргументов), поэтому полностью избавьтесь от перечисления ModePass и используйте typeof(int).MakeByRefType() при определении метода.

Весь switch (GMdPass[i]) не нужен.Поскольку аргумент является ссылкой, он будет просто предоставлен вызываемой функции как сама ссылка (достаточно ldarg).

Кстати, почему бы вам просто не использовать Marshal.GetDelegateForFunctionPointer ?Это почти так же, как динамический метод, но должно быть быстрее.Вам нужно только определить тип делегата (не универсальный).

Кроме того, не FastCall не поддерживается?

...