c# - 调用 C++ DLL 时 C# IL 如何通过 Ref 取回参数
问题描述
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吧?
解决方案
动态方法的这个签名void(int)
不是void(ref int)
你想要的,因为它是用 初始化的new Type[]{typeof(int)}
。
支持使用反射来调用具有引用参数的方法(并修改参数数组),因此完全摆脱ModePass枚举并typeof(int).MakeByRefType()
在定义方法时使用。
整体switch (GMdPass[i])
是不必要的。由于参数是通过引用,它会简单地提供给被调用的函数作为引用本身(ldarg
就足够了)。
顺便说一句,你为什么不简单地使用Marshal.GetDelegateForFunctionPointer呢?它与动态方法几乎相同,但应该更快。您只需要定义一个委托类型(非泛型)。
另外,不是不支持FastCall吗?
推荐阅读
- android - 如何在单元测试中使用协程测试 livedata
- forms - 仅当聚焦于颤动时,如何在 TextFormField() 上显示计数器文本?
- html - 如何使剑道网格中的 headerTemplate 条件起作用
- r - 在R中使用多个模拟条件子集数据帧
- c# - Nunit System.InvalidOperationException
- java - 媒体控制器不适用于 Android 中的 VideoView
- bash - 仅在使用 bash 匹配某个表达式后才在终端上打印文本文件的内容
- java - 如何在下拉列表中使用 foreach 循环?
- node.js - 从 lambda 向 sns 主题发布消息
- javascript - 尝试执行我在 pyrthon 中执行的功能时的无限循环