Partager via


Dynamically load a DLL from a runtime specified path

When running VB.Net or C# code, often it’s useful to call native code. One way is using PInvoke. (for other ways, see How fast is interop code?)

For example, you can call GetWindowText to get the title of a window. To get it’s managed signature, you can use this code from https://www.pinvoke.net/default.aspx/user32/GetWindowText.html

    <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _

    Shared Function GetWindowText(ByVal hwnd As IntPtr, _

                       ByVal lpString As StringBuilder, _

                       ByVal cch As Integer) As Integer

    End Function

 

This code works because "User32.dll" is a well known system Dll and can be found along the PATH. However, many times the Dll you want may not be on the path. Sometimes it's in a known location relative to where your code is executing (like a subdirectory).

 

The string name of the Dll specified in the DllImport can include a path name, like "c:\windows\system32\user32.dll", but it must be constant at compile time. This limitation contradicts the name for Dll: "Dynamic Link Library"

 

However, sometimes a DLL may not live in a known location on a disk, but you still want to call it. Perhaps the location can be specified at runtime.

 

A simple way to work around this problem is to explicitly call LoadLibrary (which accepts a full path) first. Then the call will work.

 

Run the sample code below.

 

Start Visual Studio. File->New->Project->VB ->Windows Application.

Double click the form and replace the code with the sample below.

The sample creates two buttons on a form and tries to invoke GetWindowText from a specified DLL.

The 1st button invokes the one in the Windows System32 directory.

The 2nd button copies the system one to a different folder and a different name ("User32Copy.dll"), then calls it.

 

 

See also:

How fast is interop code?

Create a .Net UserControl that calls a web service that acts as an ActiveX control to use in Excel, VB6, Foxpro

 

<Sample Code>

Imports System.Runtime.InteropServices

Imports System.IO

Imports System.Text

Public Class Form1

    Dim WithEvents b32 As New Button

    Dim WithEvents b32Copy As New Button

    Dim TargDir = IO.Path.GetTempPath

    Dim SysDir = Path.Combine(My.Application.GetEnvironmentVariable("windir"), "System32") ' C:\Windows\System32

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Me.Text = "Form1 " + DateTime.Now.ToLongTimeString

        b32.Text = "User32"

        Me.Controls.Add(b32)

        b32Copy.Text = "User32Copy"

        b32Copy.Top = 30

  Me.Controls.Add(b32Copy)

    End Sub

    Sub btn32_click() Handles b32.Click

        CallGetWindowText(New Wrap32, Path.Combine(SysDir, Wrap32.cDllName))

    End Sub

    Sub btn32Copy_click() Handles b32Copy.Click

        Dim cFullName = Path.Combine(TargDir, Wrap32Copy.cDllName) ' D:\user32Copy.dll

        If Not File.Exists(cFullName) Then

            File.Copy(Path.Combine(SysDir, Wrap32.cDllName), cFullName)

        End If

        Using ldr = New DynamicDllLoader(cFullName)

            CallGetWindowText(New Wrap32Copy, cFullName)

        End Using

    End Sub

    Sub CallGetWindowText(ByVal wrap As Object, ByVal cFullName As String)

        Dim sbStr As New StringBuilder(255)

        wrap.GetWindowText(Me.Handle.ToInt32, sbStr, sbStr.Capacity)

        Dim ParentWind = Me.Handle.ToInt32 'sets the owner of the Msgbox. also try Me.Handle.ToInt32 and 0 to observe Alt-Tab behavior

        MessageBox.Show("From " + cFullName + vbCrLf + "Form caption = " + sbStr.ToString, "From " + cFullName, 0)

    End Sub

End Class

Friend Class Wrap32

    Friend Const cDllName = "user32.dll"

    <DllImport(cDllName, SetLastError:=True, CharSet:=CharSet.Auto)> _

    Shared Function GetWindowText(ByVal hwnd As IntPtr, _

                       ByVal lpString As StringBuilder, _

                       ByVal cch As Integer) As Integer

    End Function

End Class

Friend Class Wrap32Copy

    Friend Const cDllName = "user32Copy.dll"

    <DllImport(cDllName, SetLastError:=True, CharSet:=CharSet.Auto)> _

    Shared Function GetWindowText(ByVal hwnd As IntPtr, _

                       ByVal lpString As StringBuilder, _

                       ByVal cch As Integer) As Integer

    End Function

End Class

Friend Class DynamicDllLoader

    Implements IDisposable

    <DllImport("kernel32.dll", _

        CallingConvention:=CallingConvention.Winapi, _

        CharSet:=CharSet.Auto, _

        EntryPoint:="LoadLibrary", _

        PreserveSig:=True)> _

    Friend Shared Function LoadLibrary(<[In]()> ByVal DllPath As String) As Integer

    End Function

    <DllImport("kernel32.dll", _

        CallingConvention:=CallingConvention.Winapi, _

        EntryPoint:="FreeLibrary", _

        PreserveSig:=True)> _

    Friend Shared Function FreeLibrary(<[In]()> ByVal Handle As Integer) As Integer

    End Function

    Private _HandleDll As Integer

    Sub New(ByVal FullPath As String, Optional ByVal fChangeDir As Boolean = False)

        'must change folder to loc of MSVBIDE so VSAssert and MSVBIDEui can be found

        Dim OrigCurdir = System.IO.Directory.GetCurrentDirectory

        If fChangeDir Then

            System.IO.Directory.SetCurrentDirectory(Path.GetDirectoryName(FullPath))

        End If

        Try

            If IO.File.Exists(FullPath) Then

       _HandleDll = LoadLibrary(FullPath)

                If _HandleDll = 0 Then

                    Throw New FileNotFoundException("couldn't load " + FullPath)

                End If

            Else

                Throw New FileNotFoundException(FullPath)

            End If

        Catch ex As Exception

        Finally

            If fChangeDir Then

                Directory.SetCurrentDirectory(OrigCurdir)

            End If

        End Try

    End Sub

    Sub UnLoadDll()

        If _HandleDll Then

            FreeLibrary(_HandleDll)

            _HandleDll = 0

        End If

    End Sub

    Private disposedValue As Boolean = False ' To detect redundant calls

    ' IDisposable

    Protected Overridable Sub Dispose(ByVal disposing As Boolean)

    If Not Me.disposedValue Then

            If disposing Then

                ' TODO: free other state (managed objects).

            End If

            UnLoadDll()

            ' TODO: free your own state (unmanaged objects).

            ' TODO: set large fields to null.

        End If

        Me.disposedValue = True

    End Sub

#Region " IDisposable Support "

    ' This code added by Visual Basic to correctly implement the disposable pattern.

    Public Sub Dispose() Implements IDisposable.Dispose

        ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.

        Dispose(True)

        GC.SuppressFinalize(Me)

    End Sub

#End Region

End Class

</Sample Code>