繁体   English   中英

C# 具有通用委托的 GetDelegateForFunctionPointer

[英]C# GetDelegateForFunctionPointer with generic delegate

我需要动态调用dll function。

我正在使用标准 windows API 加载 dll 并获取进程地址。

检索 proc 的 IntPtr 后,我尝试将其转换为委托:

Func<int> inv = (Func<int>)Marshal.GetDelegateForFunctionPointer(proc, typeof(Func<int>));

但它失败了,因为 typeof(Func) 返回 generics 类型。

是否有任何干净的方法来完成我正在尝试做的事情而不仅仅是声明成员委托并将其用作类型?

我的意思是我试图避免以下冗余:

//I need this member only for typeof operator
private delegate int RunDll();
RunDll inv = (RunDll)Marshal.GetDelegateForFunctionPointer(proc, typeof(RunDll));

如果委托类型是动态的并且您不知道参数,那将更糟。 然后,您可以使用.NET方法生成它。

public static class DelegateCreator
{
    private static readonly Func<Type[],Type> MakeNewCustomDelegate = (Func<Type[],Type>)Delegate.CreateDelegate(typeof(Func<Type[],Type>), typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers").GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static));

    public static Type NewDelegateType(Type ret, params Type[] parameters)
    {
        Type[] args = new Type[parameters.Length];
        parameters.CopyTo(args, 0);
        args[args.Length-1] = ret;
        return MakeNewCustomDelegate(args);
    }
}

DelegateCreator.NewDelegateType(typeof(int)) //returns non-generic variant of Func<int>

根据文档 ,此API根本不支持泛型。 请注意,这并不是很大的损失-毕竟,您只需指定一次签名即可; 唯一的缺点是您不能内联指定它-使用Func<int>不需要该间接调用,但是(由于强制转换)在某种意义上实际上是多余的。

顺便说一句,您可能希望查看纯旧的DllImport如果DLL和函数是DllImport已知的,则无需执行此手动委托包装。

这里的原始问题(可能)最好由DllImport回答,但讨论激发了NativeGenericDelegates的灵感。

该项目旨在提供具有通用接口的System.Delegate对象,可以与 P/Invoke 一起使用,并且可以使用不依赖于DynamicInvokeInvoke方法从托管和非托管代码调用。

非托管 function 指针的调用约定由所有方法显式指定,并且可选择支持返回值和参数的自定义封送处理行为。

问题的(损坏的)代码:

Func<int> inv = (Func<int>)Marshal.GetDelegateForFunctionPointer(proc, typeof(Func<int>));

可以使用以下方式实现:

var inv = INativeFunc<int>.FromFunctionPointer(proc, CallingConvention.Cdecl); // update calling convention as needed
int result = inv.Invoke(); // invoke the native function
Func<int> invFunc = inv.ToFunc(); // convert to Func<int> for use with other APIs

有关更复杂的方法签名(包括封送处理和修改后的签名)的详细信息包含在项目存储库和源文件中。

首先: 方法DelegateCreator.NewDelegateType的实现不正确!

public static Type NewDelegateType(Type ret, params Type[] parameters) {
  /*
  Type[] args = new Type[parameters.Length];   // Create "args" array of same length as "parameters" (must be length + 1)
  parameters.CopyTo(args, 0);                  // Copy all values of parameters to "args" array
  args[args.Length - 1] = ret;                 // Put "ret" value to last item of "args" array
  return MakeNewCustomDelegate(args);
  */
  var offset = parameters.Length;
  Array.Resize(ref parameters, offset + 1);
  parameters[offset] = ret;
  return MakeNewCustomDelegate(parameters);
}

如果我们无法得到类型化的委托来调用,那么该助手的好处是什么? 请参阅下面的测试示例。

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices;

namespace GenericDelegates {
  static class DelegateCreator {
    public static readonly Func<Type[], Type> MakeNewCustomDelegate = (Func<Type[], Type>) Delegate.CreateDelegate(
      typeof(Func<Type[], Type>),
      typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers").GetMethod(
        "MakeNewCustomDelegate",
        BindingFlags.NonPublic | BindingFlags.Static
      )
    );
    public static Type NewDelegateType(Type ret, params Type[] parameters) {
      var offset = parameters.Length;
      Array.Resize(ref parameters, offset + 1);
      parameters[offset] = ret;
      return MakeNewCustomDelegate(parameters);
    }
  }
  static class Kernel {
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetModuleHandle(string name);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr module, string name);
  }
  static class InvokeHelper {
    public static Delegate MakeDelegate(this IntPtr address, Type ret, params Type[] parameters) {
      return Marshal.GetDelegateForFunctionPointer(address, DelegateCreator.NewDelegateType(ret, parameters));
    }
  }
  class Program {
    static void Main(string[] args) {
      var kernel = Kernel.GetModuleHandle("kernel32.dll");
      var address = Kernel.GetProcAddress(kernel, "GetModuleHandleW");
      Console.WriteLine(@"
Module base: 0x{0:X8}
Entry point: 0x{1:X8}
", (int) kernel, (int) address);
      var invoke = address.MakeDelegate(typeof(IntPtr), typeof(string));
      Console.WriteLine(@"
Untyped delegate: {0}
Cast to Invoke: {1}
", invoke, invoke as Func<string, IntPtr> == null ? "Error" : "Valid"); // invoke as Func<string, IntPtr> = NULL
      Console.ReadKey();
    }
  }
}

我正在做这样的事情:

var calculate = FunctionDelegateAccessor.GetMethod<Calculate>(handle, "Calculate");
Console.WriteLine("10 + 10 = " + calculate(10, 10));
var multiply = FunctionDelegateAccessor.GetMethod<Multiply>(handle, "Multiply");
Console.WriteLine("10 * 10 = " + multiply(10, 10));

public delegate int Calculate(int a, int b);
public delegate int Multiply(int a, int b);

控制台中的 Output:

10 + 10 = 20 
10 * 10 = 100

数学.cpp

DLL_API int Calculate(int a, int b)
{
    return a + b;
}
DLL_API int Multiply(int a, int b)
{
    return a * b;
}

数学.h

#pragma once

#define DLL_API __declspec(dllexport)

extern "C" 
{
    DLL_API int Calculate(int a, int b);
    DLL_API int Multiply(int a, int b);
}
internal static class FunctionDelegateAccessor
{
    public static TDelegate GetMethod<TDelegate>(IntPtr moduleHandle, string name) where TDelegate 
    {
        var methodProcAddress = ExternalKernelCalls.GetProcAddress(moduleHandle, name);
        if (methodProcAddress == IntPtr.Zero)
        {
            throw new Win32Exception();
        }
        
        var @delegate = Marshal.GetDelegateForFunctionPointer<TDelegate>(methodProcAddress);
        if (@delegate == null)
        {
            throw new Exception("Function delegate not found!");
        }
        return @delegate;
    }
}
internal static class ExternalKernelCalls
{
    [DllImport(Kernel32LibraryName, SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    private const string Kernel32LibraryName = "kernel32.dll";
}

有关 GitHub 存储库的更多信息,请点击此处

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM