0
votes

This question is going to be poorly-posed, but I'm spinning my wheels big-time, and I'm not sure how to express it better.

I need to write a DLL, using C, which is to be called from VB6. I'm using Visual Studio Express 2013. I know...VB6 is ancient, and I think the managers of the code are now convinced they should junk it. But in the meantime, this needs to be done.

To start, I am trying to write a DLL with a single function that does nothing but print a message, and that works fine when calling the DLL using VB.NET.

Here's what I have for TinyDll.h.

#ifdef TINYDLL_EXPORTS
#define TINYDLL_API __declspec(dllexport)
#else
#define TINYDLL_API __declspec(dllimport)
#endif

extern TINYDLL_API void __stdcall testdll();

And here's TinyDll.cpp

#include "stdafx.h"
#include "TinyDll.h"
#include <stdexcept>

using namespace std;

void __stdcall testdll()
{
  printf("Got into the dll.\n");
}

Incidentally, I've tried this with and without __stdcall. I also tried using a .def file to de-mangle the name in the dll, but it's not clear to me now that should work. The examples I've found indicate that this should work

LIBRARY TinyDll
Exports
  testdll

but it does not. The name in the dll is still in mangled form: ?testdll@@YGXXZ.

My test code is fairly trivial, and it works perfectly with VB.NET, but it won't work with VB6. The problem has something to do with the VB6 process not being able to find the dll and/or find the functions inside of it. For the record, here's the VB6 code being used to test

Declare Sub testdll Lib "TinyDll.dll" Alias "?testdll@@YGXXZ" ()

Sub Main()
  testdll()
End Sub    

First, am I correct that it is not necessary to call regsvr32 or regasm? The dll is not a COM object -- VB6 is calling my C code, not the other way around. When we try to do either of these two things, either "the entry-point DllRegisterServer was not found" or "The specified module could not be found."

To top it off, the VB6 development environment is too old to run on my Windows 7 machine and the person who is trying to test my DLL is in another state and is strictly a VB programmer.

I've read everything I can find by googling, so I'm hoping that someone knows of a website that clearly lays out the facts or shows a working example.

1
That looks like C++ name mangling - have you tried extern "C" to suppress that?Notlikethat
The earlier question is similar (and I read it ages ago), but there was no clear answer beyond the fact that the original person figured it out himself. I've also followed the link he gives, but it was written in 2004 and I'm using Visual Studio 2013. The information in that link no longer seems to solve the problem.Randall Fairman
Does the target W7 machine have the VS2013 DLLs? Is the C program compiled with /MD or /MT? When you test on VB.net, was that on the target or on your machine?cup
You dont show the the test code, but a VB.NET test isnt going to tell you much about VB6 because of the differences in them. For instance with C DLL calls in Vb6 you would use a Long, in NET you use Integer. Be sure the data types are correct as well as ByVal/ByRefŇɏssa Pøngjǣrdenlarp

1 Answers

0
votes

As is so often the case with this kind of question (i.e., one involving a crusty old environment being used to do stuff it was never meant for), it now works, but it's not clear exactly why.

We think that without a full path to the dll, VB6 can't find it. If we declare the function with

Declare Function TestTinyDLL Lib "e:\full\path\down\to\TinyDll.dll" Alias "_testdll@0"

then the call works, but it doesn't work if the full path isn't given, no matter where the dll resides. We also found that we can avoid providing the full path by "initializing" the dll as follows (thanks to: File not found when loading dll from vb6 for this clue):

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

Declare Function BogusCall Lib "otherdll.dll" Alias "_BogusCall@0" () As Integer

Declare Function TestTinyDLL Lib "TinyDLL.dll" Alias "_testdll@0" () As Integer

Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000


Sub Main()

    InitializeDLL App.Path & "\" & "otherdll.dll", "_BogusCall@0"

    InitializeDLL App.Path & "\" & "TinyDLL.dll", "_testdll@0"

    Dim Version As String

    Version = "TinyDLLTestProgram" & vbCrLf & vbCrLf

    Version = Version & "BogusCall = " & CStr(BogusCall ()) & vbCrLf

    Version = Version & "TestTinyDLL= " & CStr(TestTinyDLL()) & vbCrLf

    Form1.txtOutput.Text = CStr(Version)

End Sub

Sub InitializeDLL(myDLL As String, myFunc As String)

    ' 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

Why this works is a mystery, but it does. I hope this helps anyone else faced with old VB6 code!