将委托传递给泛型函数,然后传递给Marshal.GetDelegateForFunctionPointer()

时间:2017-09-09 12:20:40

标签: .net vb.net generics delegates unmanaged

我想编写一个通用的可重用函数,以方便在本机dll中调用导出函数的常见任务,并返回函数的返回值。

考虑我的以下方法:

Shared Function CallNativeFunction(Of TResult)(
                                   ByVal filepath As String,
                                   ByVal functionName As String,
                                   ByVal functionSignature As [Delegate]) As TResult

    Dim hLib As IntPtr = NativeMethods.LoadLibrary(filepath)
    If (hLib = IntPtr.Zero) Then
        Throw New Win32Exception(Marshal.GetLastWin32Error())
    End If

    Dim dllEntryPoint As IntPtr = NativeMethods.GetProcAddress(hLib, functionName)
    If (dllEntryPoint = IntPtr.Zero) Then
        Throw New Win32Exception(Marshal.GetLastWin32Error())
    End If

    functionSignature = 
        Marshal.GetDelegateForFunctionPointer(dllEntryPoint, GetType([Delegate]))

    Dim result As TResult = 
        Conversion.CTypeDynamic(Of TResult)(functionSignature)

    Return result

End Function

或者使用这个替代签名:

Shared Function CallNativeFunction(Of TResult, TDelegate)(
                                   ByVal filepath As String,
                                   ByVal functionName As String,
                                   ByVal functionSignature As TDelegate) As TResult

...

好吧,我试图弄清楚哪个是目前定义函数的Type参数的最佳方法,我怎么能把正确的参数传递给函数让它作为预期。 现在它没有像预期的那样工作;当我传递[Delegate]函数时,对GetDelegateForFunctionPointer的调用抛出一个ArgumentException,说该参数必须来自[Delegate]类型,如果我传递{{1}它表示类型不能是通用类型。

所以,想象一下,例如我想打电话给" DllRegisterServer "以本机dll导出,为了完成该任务,我应该在下面编写以下代码:

Func(of T)

...但是该代码是硬编码的,委托是在类级别和<UnmanagedFunctionPointer(CallingConvention.StdCall)> Friend Delegate Function PointerToMethodInvoker() As Integer Sub CallNativeFunction(...) ' non-relevant code here ... ' ... Dim invoker As PointerToMethodInvoker = Marshal.GetDelegateForFunctionPointer(Of PointerToMethodInvoker)(dllEntryPoint) Dim result As Integer = invoker() End Sub 的块内定义的,我必须知道定义的委托的确切名称才能继续。我想避免/简化这些事情。我的目的是将该代码示例转换为可重用的函数,以便能够传递非硬编码的CallNativeFunction

我的问题:如何调整我当前的代码以满足我解释的预期行为?。

1 个答案:

答案 0 :(得分:1)

我写了一个时间解决方案。这并不像我希望的那样普遍,但有些东西总比没有好,我认为在使用这个功能时会保存相当大一部分代码而不会失去太多的移动性/效率和错误处理。有一点我不确定当有两个导出定义为相同名称但签名不同的情况时,我怎么能处理消歧。

如果有人采取比矿井更好的方法,那么请发出答复。我不会接受我自己的答案。感谢您在我的问题的评论栏中提供给我的帮助,提示和/或建议。

我编写了两个函数,一个用于获取传递委托的本机函数(因此最终用户可以做任何他想对返回的委托做的事情),另一个用于执行函数,它有点用概念编写记住:“轻松完成这项任务,对我来说没有任何困难”,我知道,这不是太安全,但是......它只是一个简单的辅助方法。

我希望此代码对某些特定场景中的某些人有用。用法示例在XML文档中编写。代码缺少一些必需的Windows API定义,以避免产生太大的代码,我认为它足够了,每个人都可以在pinvoke.net上查找所需的定义。

GetNativeFunction

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Gets a function exported in a native dll.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Public Module Module1
''' 
'''     ''' ----------------------------------------------------------------------------------------------------
'''     ''' &lt;summary&gt;
'''     ''' A delegate to call DllRegisterServer or DllUnregisterServer functions in a native dll.
'''     ''' &lt;/summary&gt;
'''     ''' ----------------------------------------------------------------------------------------------------
'''     ''' &lt;returns&gt;
'''     ''' The result of calling DllRegisterServer or DllUnregisterServer functions in a native dll.
'''     ''' &lt;/returns&gt;
'''     ''' ----------------------------------------------------------------------------------------------------
'''     &lt;UnmanagedFunctionPointer(CallingConvention.StdCall)&gt;
'''     Friend Delegate Function PointerToRegistrationMethodInvoker() As Integer
''' 
'''     Public Sub Main()
''' 
'''         Dim methodInvoker As PointerToRegistrationMethodInvoker =
'''                 GetNativeFunction(Of PointerToRegistrationMethodInvoker)("C:\native.dll", "DllRegisterServer")
''' 
'''         Dim result As Integer = methodInvoker.Invoke()
'''         If (result &lt;&gt; 0) Then
'''             ' ToDo: Handle specific errors...
'''         End If
''' 
'''     End Sub
''' 
''' End Module
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <typeparam name="TDelegate">
''' The <see cref="Type"/> of the source <see cref="[Delegate]"/> 
''' that will represent the signature of the native function.
''' </typeparam>
'''  
''' <param name="filepath">
''' The file path of the native dll.
''' </param>
''' 
''' <param name="functionName">
''' The name of the function to be retrieved.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' The resulting <see cref="[Delegate]"/> which represents a pointer to the method invoker.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Shared Function GetNativeFunction(Of TDelegate)(ByVal filepath As String,
                                                       ByVal functionName As String) As TDelegate

    Dim hLib As IntPtr
    Dim win32err As Integer

    hLib = NativeMethods.LoadLibrary(filepath)
    win32err = Marshal.GetLastWin32Error()
    If (hLib = IntPtr.Zero) Then
        If (win32err = Win32ErrorCode.ERROR_BAD_EXE_FORMAT) Then ' 193
            Throw New BadImageFormatException("Failed to load library.", filepath)
        ElseIf (win32err = Win32ErrorCode.ERROR_MOD_NOT_FOUND) Then ' 126
            Throw New FileNotFoundException("File not found.", filepath)
        Else
            Throw New Win32Exception(win32err)
        End If
    End If

    Dim dllEntryPoint As IntPtr = NativeMethods.GetProcAddress(hLib, functionName)
    win32err = Marshal.GetLastWin32Error()
    If (dllEntryPoint = IntPtr.Zero) Then
        If (win32err = Win32ErrorCode.ERROR_PROC_NOT_FOUND) Then ' 127
            Throw New EntryPointNotFoundException(String.Format("Failed to get entry point: '{0}'", functionName))
        Else
            Throw New Win32Exception(win32err)
        End If
    End If

    Return Marshal.GetDelegateForFunctionPointer(Of TDelegate)(dllEntryPoint)

End Function

CallNativeFunction

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Executes a function exported in a native dll and returns the result value.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Public Module Module1
''' 
'''     ''' ----------------------------------------------------------------------------------------------------
'''     ''' &lt;summary&gt;
'''     ''' A delegate to call DllRegisterServer or DllUnregisterServer functions in a native dll.
'''     ''' &lt;/summary&gt;
'''     ''' ----------------------------------------------------------------------------------------------------
'''     ''' &lt;returns&gt;
'''     ''' The result of calling DllRegisterServer or DllUnregisterServer functions in a native dll.
'''     ''' &lt;/returns&gt;
'''     ''' ----------------------------------------------------------------------------------------------------
'''     &lt;UnmanagedFunctionPointer(CallingConvention.StdCall)&gt;
'''     Friend Delegate Function PointerToRegistrationMethodInvoker() As Integer
''' 
'''     Public Sub Main()
''' 
'''         Dim result As Integer = 
'''             CallNativeFunction(Of Integer, PointerToRegistrationMethodInvoker)(filepath, "DllRegisterServer", Nothing)
''' 
'''         Dim result As Integer = methodInvoker.Invoke()
'''         If (result &lt;&gt; 0) Then
'''             ' ToDo?: Handle specific errors...
'''         End If
''' 
'''     End Sub
''' 
''' End Module
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <typeparam name="TResult">
''' The <see cref="Type"/> of the value returned by the native function.
''' </typeparam>
'''  
''' <typeparam name="TDelegate">
''' The <see cref="Type"/> of the source <see cref="[Delegate]"/> 
''' that will represent the signature of the native function.
''' </typeparam>
'''
''' <param name="filepath">
''' The file path of the native dll.
''' </param>
''' 
''' <param name="functionName">
''' The name of the function to be executed.
''' </param>
''' 
''' <param name="functionSignature">
''' A <see cref="[Delegate]"/> that will represent the signature of the native function.
''' </param>
''' 
''' <param name="parameters">
''' An argument list for the invoked function. 
''' This is an array of objects with the same number, order, and type as the parameters of the function to be invoked.
''' <para></para>
''' If there are no parameters, parameters should be null.If the method
''' Any object in this array that is not explicitly initialized with a value will contain the default value for
''' that object type. 
''' <para></para>
''' For reference-type elements, this value is <see langword="Nothing"/>. 
''' For value-type elements, this value is 0, 0.0, or false, depending on the specific element type.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' The result of calling the native function.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Shared Function CallNativeFunction(Of TResult, TDelegate)(ByVal filepath As String,
                                                                 ByVal functionName As String,
                                                                 ByVal functionSignature As TDelegate,
                                                                 ByVal ParamArray parameters As Object()) As TResult

    functionSignature = GetNativeFunction(Of TDelegate)(filepath, functionName)

    Dim methodInvoker As MulticastDelegate = Conversion.CTypeDynamic(Of MulticastDelegate)(functionSignature)
    Dim result As TResult = CType(methodInvoker.DynamicInvoke(parameters), TResult)
    Return result

End Function