Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

File not found when loading dll from vb6

Tags:

interop

dll

vb6

I am declaring and calling a dll function using the following syntax in VB6:

'Declare the function
Private Declare Sub MYFUNC Lib "mylib.dll" ()

'Call the function
MYFUNC

Calling the function results in the error File not found: mylib.dll. This happens when the application is run from the vb6 IDE or from a compiled executable.

The dll is in the working directory, and I have checked that it is found using ProcMon.exe from sysinternals. There are no failed loads, but the Intel Fortran dlls are not loaded (the ProcMon trace seems to stop before then).

I have also tried running the application in WinDbg.exe, and weirdly, it works! There are no failures on this line. The ProcMon trace shows that the Intel Fortran dlls are loaded when the program is run in this way.

The dll is compiled with Fortran Composer XE 2011.

Can anyone offer any help?

like image 272
mickeyt Avatar asked Jan 11 '13 15:01

mickeyt


4 Answers

When loading DLLs, "file not found" can often be misleading. It may mean that the DLL or a file it depends on is missing - but if that was the case you would have spotted the problem with Process Monitor.

Often, the "file not found" message actually means that the DLL was found, but an error occured when loading it or calling the method.

There are actually three steps to calling a procedure in a DLL:

  1. Locate and load the DLL, running the DllMain method if present.
  2. Locate the procedure in the DLL.
  3. Call the procedure.

Errors can happen at any of these stages. VB6 does all this behind the scenes so you can't tell where the error is happening. However, you can take control of the process using Windows API functions. This should tell you where the error is happening. You can alse set breakpoints and use Process Monitor to examine your program's behaviour at each point which may give you more insights.

The code below shows how you can call a DLL procedure using the Windows API. To run it, put the code into a new module, and set the startup object for your project to "Sub Main".

Option Explicit

' Windows API method declarations
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function CallWindowProc Lib "user32" Alias _
    "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, _
    ByVal Msg As Any, ByVal wParam As Any, ByVal lParam As Any) _
    As Long

Private Declare Function FormatMessage Lib "kernel32" Alias _
    "FormatMessageA" (ByVal dwFlags As Long, lpSource As Long, _
    ByVal dwMessageId As Long, ByVal dwLanguageId As Long, _
    ByVal lpBuffer As String, ByVal nSize As Long, Arguments As Any) _
    As Long

Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000

Const MyFunc As String = "MYFUNC"
Const MyDll As String = "mylib.dll"

Sub Main()

    ' Locate and load the DLL. This will run the DllMain method, if present
    Dim dllHandle As Long
    dllHandle = LoadLibrary(MyDll)

    If dllHandle = 0 Then
        MsgBox "Error loading DLL" & vbCrLf & ErrorText(Err.LastDllError)
        Exit Sub
    End If

    ' Find the procedure you want to call
    Dim procAddress As Long
    procAddress = GetProcAddress(dllHandle, MyFunc)

    If procAddress = 0 Then
        MsgBox "Error getting procedure address" & vbCrLf & ErrorText(Err.LastDllError)
        Exit Sub
    End If

    ' Finally, call the procedure
    CallWindowProc procAddress, 0&, "Dummy message", ByVal 0&, ByVal 0&

End Sub

' Gets the error message for a Windows error code
Private Function ErrorText(errorCode As Long) As String

    Dim errorMessage As String
    Dim result As Long

    errorMessage = Space$(256)
    result = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0&, errorCode, 0&, errorMessage, Len(errorMessage), 0&)

    If result > 0 Then
        ErrorText = Left$(errorMessage, result)
    Else
        ErrorText = "Unknown error"
    End If

End Function
like image 80
roomaroo Avatar answered Nov 03 '22 10:11

roomaroo


The .dll must be in the current "working" directory (or registered), otherwise at run-time the application can't find it.

Do:

MsgBox "The current directory is " & CurDir

And then compare that with what you were expecting. The .dll would need to be in that directory.

like image 4
B. Nadolson Avatar answered Nov 03 '22 09:11

B. Nadolson


My standard first go-to approach to this issue is to break out ProcMon (or FileMon on XP). Setup the filters so that you can see where exactly it's searching for the file. It is possible that it's looking for the file elsewhere or for a different file name.

like image 1
AngryHacker Avatar answered Nov 03 '22 09:11

AngryHacker


Private Declare Sub MYFUNC Lib "mylib.dll" ()

Firstly you are declaring a Sub, not a function. These don't have return values:

(vb6) Sub() == (vc++) void Sub()
(vb6) Func() as string == (vc++) string Func()

The path you have declared is local to the running environment. Thus when running is debug mode using VB6.exe, you'll need to have mylib.dll in the same directory as VB6.exe.

As you are using private declare, you might want to consider a wrapper class for your dll. This allows you to group common dll access together but allowing for reuse. Then methods of the class are used to access the exposed function.

So you can use all the code provided above, copy it into a class

MyClass code:

Option Explicit

'Private Declare Sub MYFUNC Lib "mylib.dll" ()
'<all code above Main()>

Private Sub Class_Initialize()
    'initialise objects
End Sub

Private Sub Class_Terminate()
    'Set anyObj = Nothing
End Sub

Public Sub ClassMethod()
    On Error Goto errClassMethod
    'Perhaps look at refactoring the use of msgbox

    '<code body from Main() given above>

    exit sub
errClassMethod:
    'handle any errors
End Sub

'<all code below main>

Apartment threading model loads ALL modules when the application is started. Using a class will only "load" the dll when the class is instantiated. Also results in neater calling code without the surrounding obfuscation of windows API calls: (ie. modMain):

Sub Main()
    Dim m_base As MyClass
    Set m_base = New MyClass
    MyClass.ClassMethod()
End Sub
like image 1
Ian Lowson Avatar answered Nov 03 '22 09:11

Ian Lowson