Imports System.Reflection

<AttributeUsage(AttributeTargets.Method, AllowMultiple:=False, Inherited:=True)> _
Public Class DllResourceImportAttribute
    Inherits Attribute

    '//constants
    Public Const DefaultBindingFlags As BindingFlags = BindingFlags.Instance Or _
                                                       BindingFlags.Static Or _
                                                       BindingFlags.Public

    '//fields
    Private Shared finalizingObject As FinalizerObject

    '//properties
    Private Shared _assemblies As Dictionary(Of String, Assembly)
    Protected Shared ReadOnly Property Assemblies As Dictionary(Of String, Assembly)
        Get
            If DllResourceImportAttribute._assemblies Is Nothing Then
                DllResourceImportAttribute._assemblies = New Dictionary(Of String, Assembly)()
            End If
            Return DllResourceImportAttribute._assemblies
        End Get
    End Property

    Private _library As String
    Public ReadOnly Property Library As String
        Get
            Return Me._library
        End Get
    End Property

    Private _bindingFlags As BindingFlags = DllResourceImportAttribute.DefaultBindingFlags
    Public ReadOnly Property BindingFlags As BindingFlags
        Get
            Return Me._bindingFlags
        End Get
    End Property

    Private _methodName As String
    Public ReadOnly Property MethodName As String
        Get
            Return Me._methodName
        End Get
    End Property

    Private _declaringTypeQualifiedName As String
    Public ReadOnly Property DeclaringTypeQualifiedName As String
        Get
            Return Me._declaringTypeQualifiedName
        End Get
    End Property

    '//constructors
    Shared Sub New()

        '//used specifically for loaded assembly clean up
        DllResourceImportAttribute.finalizingObject = New FinalizerObject()
    End Sub

    Public Sub New(ByVal resourceLibrary As String)
        If String.IsNullOrEmpty(resourceLibrary) Then
            Throw New DllResourceImportException(Nothing, New ArgumentNullException("resourceLibrary"))
        End If

        resourceLibrary = DllResourceImportAttribute.CleanLibraryName(resourceLibrary)

        If Not DllResourceImportAttribute.Assemblies.ContainsKey(resourceLibrary) Then

            '//modify this line for C#
            Dim dll = My.Resources.ResourceManager.GetObject(resourceLibrary)

            If dll IsNot Nothing Then
                Dim rawAssembly = TryCast(dll, Byte())

                If rawAssembly IsNot Nothing Then
                    Try
                        DllResourceImportAttribute.Assemblies.Add(resourceLibrary, Assembly.Load(rawAssembly))
                    Catch ex As Exception
                        Throw New DllResourceImportException(Nothing, ex)
                    End Try
                End If
            End If
        End If

        Me._library = resourceLibrary
    End Sub

    Public Sub New(ByVal resourceLibrary As String, ByVal bindingFlags As BindingFlags)
        Me.New(resourceLibrary)

        Me._bindingFlags = bindingFlags
    End Sub

    Public Sub New(ByVal resourceLibrary As String, ByVal bindingFlags As BindingFlags, _
                   ByVal methodName As String)
        Me.New(resourceLibrary, bindingFlags)

        Me._methodName = methodName
    End Sub

    Public Sub New(ByVal resourceLibrary As String, ByVal bindingFlags As BindingFlags, _
                   ByVal methodName As String, ByVal declaringTypeQualifiedName As String)
        Me.New(resourceLibrary, bindingFlags, methodName)

        Me._declaringTypeQualifiedName = declaringTypeQualifiedName
    End Sub

    '//methods
    Private Shared Function CleanLibraryName(ByVal library As String) As String
        Return IO.Path.GetFileNameWithoutExtension(library)
    End Function

    Private Shared Function ConstructParameterTypes(ByVal method As MethodBase) As Type()
        Dim parameters = method.GetParameters()
        Dim types(parameters.Length - 1) As Type

        For i = 0 To parameters.Length - 1
            types(i) = parameters(i).ParameterType
        Next

        Return types
    End Function

    Public Shared Function Execute(ByVal ParamArray arguments() As Object) As Object
        Dim caller = New StackFrame(1)
        Dim method = caller.GetMethod()
        Dim attr = Attribute.GetCustomAttribute(method, GetType(DllResourceImportAttribute))

        If attr Is Nothing Then
            Throw New InvalidOperationException(String.Format("{0} is not defined on this method.", _
                                                              GetType(DllResourceImportAttribute).Name))
        End If

        Dim import = DirectCast(attr, DllResourceImportAttribute)
        Dim assembly = DllResourceImportAttribute.Assemblies(import.Library)
        Dim declaringType = method.DeclaringType
        Dim fullName = import.DeclaringTypeQualifiedName

        If String.IsNullOrEmpty(fullName) Then
            fullName = String.Format("{0}.{1}", import.Library, declaringType.Name)
        End If

        Dim assemblyDeclaringType = assembly.GetType(fullName, False, True)
        Dim methodName = If(import.MethodName, method.Name)

        If assemblyDeclaringType IsNot Nothing Then
            Dim assemblyMethod = assemblyDeclaringType.GetMethod(methodName, import.BindingFlags, Nothing, _
                                                                 DllResourceImportAttribute.ConstructParameterTypes(method), Nothing)
            If assemblyMethod IsNot Nothing Then

                '//do not need to create an instance of the declaring type in the assembly
                If method.IsStatic Then
                    Try
                        Return assemblyMethod.Invoke(Nothing, arguments)
                    Catch ex As Exception
                        Throw New DllResourceImportException(Nothing, ex)
                    End Try
                End If

                Try
                    Dim instance = Activator.CreateInstance(assemblyDeclaringType, False)
                    If instance IsNot Nothing Then
                        Try
                            Dim result = assemblyMethod.Invoke(instance, arguments)

                            '//clean up the type used if applicable
                            Dim disposable = TryCast(instance, IDisposable)
                            If disposable IsNot Nothing Then
                                disposable.Dispose()
                            End If
                            instance = Nothing

                            Return result
                        Catch ex As Exception
                            Throw New DllResourceImportException(Nothing, ex)
                        End Try
                    End If
                Catch ex As Exception
                    Throw New DllResourceImportException(Nothing, ex)
                End Try

            End If
        End If

        Return Nothing
    End Function

    '//nested types
    Private Class FinalizerObject

        '//fields
        Private owner As DllResourceImportAttribute

        '//methods
        Protected Overrides Sub Finalize()
            If DllResourceImportAttribute._assemblies IsNot Nothing Then
                DllResourceImportAttribute._assemblies.Clear()
                DllResourceImportAttribute._assemblies = Nothing
            End If

            MyBase.Finalize()
        End Sub

    End Class

End Class

Public Class DllResourceImportException
    Inherits Exception

    '//constructors
    Public Sub New(ByVal message As String, ByVal innerException As Exception)
        MyBase.New(message, innerException)
    End Sub

End Class