简体   繁体   English

C# 具有通用委托的 GetDelegateForFunctionPointer

[英]C# GetDelegateForFunctionPointer with generic delegate

I need to dynamically invoke dll function.我需要动态调用dll function。

I'm using standard windows API to load dll and get proc address.我正在使用标准 windows API 加载 dll 并获取进程地址。

After I retrieve proc's IntPtr I try to convert it to delegate:检索 proc 的 IntPtr 后,我尝试将其转换为委托:

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

But it fails because typeof(Func) return generics type.但它失败了,因为 typeof(Func) 返回 generics 类型。

Is there any clean way to accomplish what I'm trying to do without just declaring member delegate and use it as type?是否有任何干净的方法来完成我正在尝试做的事情而不仅仅是声明成员委托并将其用作类型?

I mean I'm trying to avoid the following redundancy:我的意思是我试图避免以下冗余:

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

It would be worse if the delegate type was dynamic and you didn't know the parameters. 如果委托类型是动态的并且您不知道参数,那将更糟。 Then you could use .NET methods to generate it. 然后,您可以使用.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>

According to the documentation generics simply aren't supported by this API. 根据文档 ,此API根本不支持泛型。 Note that this isn't a great loss - after all, you need to specify the signature just once; 请注意,这并不是很大的损失-毕竟,您只需指定一次签名即可; the only disadvantage is that you cannot specify it inline - using Func<int> doesn't require that indirection, but (due to the cast) it's in a sense actually more redundant. 唯一的缺点是您不能内联指定它-使用Func<int>不需要该间接调用,但是(由于强制转换)在某种意义上实际上是多余的。

Incidentally, you may want to look at plain old DllImport - if the DLL and function are known ahead of time, you don't need to do this manual delegate wrapping. 顺便说一句,您可能希望查看纯旧的DllImport如果DLL和函数是DllImport已知的,则无需执行此手动委托包装。

The original question here was (probably) best answered by DllImport , but the discussion inspired NativeGenericDelegates .这里的原始问题(可能)最好由DllImport回答,但讨论激发了NativeGenericDelegates的灵感。

This project aims to provide System.Delegate objects that have a generic interface, can be used with P/Invoke, and may be invoked from both managed and unmanaged code with an Invoke method that doesn't rely on DynamicInvoke .该项目旨在提供具有通用接口的System.Delegate对象,可以与 P/Invoke 一起使用,并且可以使用不依赖于DynamicInvokeInvoke方法从托管和非托管代码调用。

The calling convention of the unmanaged function pointer is explicitly specified by all methods, and custom marshaling behavior for the return value and parameters is optionally supported.非托管 function 指针的调用约定由所有方法显式指定,并且可选择支持返回值和参数的自定义封送处理行为。

The (broken) code from the question:问题的(损坏的)代码:

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

Could be implemented using:可以使用以下方式实现:

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

Details on more complex method signatures (including marshaling and modified signatures) are included in the project repo and source files.有关更复杂的方法签名(包括封送处理和修改后的签名)的详细信息包含在项目存储库和源文件中。

At first: the implementation of method DelegateCreator.NewDelegateType isn't correct! 首先: 方法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);
}

What is profit of that helper if we cannot get a typed delegate to invoke? 如果我们无法得到类型化的委托来调用,那么该助手的好处是什么? See test example below. 请参阅下面的测试示例。

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();
    }
  }
}

I'm doing something like that:我正在做这样的事情:

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 in Console:控制台中的 Output:

10 + 10 = 20 
10 * 10 = 100

Math.cpp数学.cpp

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

Math.h数学.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";
}

More info on the GitHub repo with sources here .有关 GitHub 存储库的更多信息,请点击此处

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

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