从 VB6 调用 C dll,其中 dll 是使用 Visual Studio 2013 编写的

Posted

技术标签:

【中文标题】从 VB6 调用 C dll,其中 dll 是使用 Visual Studio 2013 编写的【英文标题】:Calling C dll from VB6, where the dll is written using Visual Studio 2013 【发布时间】:2014-01-25 22:44:44 【问题描述】:

这个问题将是不好提出的,但我一直在旋转我的***,我不知道如何更好地表达它。

我需要使用 C 语言编写一个 DLL,该 DLL 将从 VB6 中调用。我正在使用 Visual Studio Express 2013。我知道...VB6 是古老的,我认为代码的管理人员现在确信他们应该把它扔掉。但与此同时,这需要完成。

首先,我尝试使用一个函数编写一个 DLL,该函数只打印一条消息,并且在使用 VB.NET 调用 DLL 时工作正常。

这是我为 TinyDll.h 准备的内容。

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

extern TINYDLL_API void __stdcall testdll();

这里是 TinyDll.cpp

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

using namespace std;

void __stdcall testdll()

  printf("Got into the dll.\n");

顺便说一下,我在有和没有 __stdcall 的情况下都试过了。我还尝试使用 .def 文件来消除 dll 中的名称,但现在我不清楚这应该可以工作。我发现的例子表明这应该可以工作

LIBRARY TinyDll
Exports
  testdll

但事实并非如此。 dll 中的名称仍然是乱码形式:?testdll@@YGXXZ。

我的测试代码相当简单,它与 VB.NET 完美配合,但不适用于 VB6。该问题与 VB6 进程无法找到 dll 和/或找到其中的函数有关。作为记录,这是用于测试的 VB6 代码

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

Sub Main()
  testdll()
End Sub    

首先,我是否正确没有必要调用 regsvr32 或 regasm? dll 不是 COM 对象——VB6 正在调用我的 C 代码,而不是相反。当我们尝试做这两件事中的任何一个时,要么是“找不到入口点 DllRegisterServer”,要么是“找不到指定的模块”。

最重要的是,VB6 开发环境太旧,无法在我的 Windows 7 机器上运行,而试图测试我的 DLL 的人处于另一种状态,并且严格来说是 VB 程序员

我已经阅读了通过谷歌搜索可以找到的所有内容,因此我希望有人知道一个清楚地列出事实或展示工作示例的网站。

【问题讨论】:

Calling DLL functions from VB6 的可能重复项 这看起来像是 C++ 名称修改 - 你试过 extern "C" 来抑制它吗? 前面的问题是类似的(我很久以前读过),但除了原来的人自己想出来之外,没有明确的答案。我也关注了他提供的链接,但它是 2004 年编写的,我使用的是 Visual Studio 2013。该链接中的信息似乎不再解决问题。 目标 W7 机器是否有 VS2013 DLL? C 程序是用 /MD 还是 /MT 编译的?当您在 VB.net 上进行测试时,是在目标上还是在您的机器上? 您没有显示测试代码,但是 VB.NET 测试不会告诉您太多关于 VB6 的信息,因为它们之间存在差异。例如,在 Vb6 中使用 C DLL 调用时,您将使用 Long,在 NET 中,您使用 Integer。确保数据类型以及 ByVal/ByRef 正确 【参考方案1】:

就像这类问题经常出现的情况一样(即,一个涉及一个硬皮的旧环境被用来做它从未想过的事情),它现在可以工作了,但具体原因尚不清楚。

我们认为如果没有完整的 dll 路径,VB6 就无法找到它。如果我们用

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

然后调用有效,但如果没有给出完整路径,则无论 dll 位于何处,它都不起作用。我们还发现,我们可以通过“初始化”dll 来避免提供完整路径,如下所示(感谢:File not found when loading dll from vb6 提供此线索):

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

为什么会这样是个谜,但确实如此。我希望这可以帮助其他面对旧 VB6 代码的人!

【讨论】:

这个工作方式不同——它使用后期绑定而不是直接链接。如果您希望使用直接链接,函数需要以 __declspec(dllexport) 为前缀。如果您不想修改名称,可以添加 extern "C"。 它应该可以在没有路径的情况下工作,前提是 DLL 在 Windows 可以找到它的位置。例如。 system32 目录,在与 EXE 相同的目录或当前工作目录中。一个复杂的问题是,当从 VB6 IDE 执行时,“与 EXE 相同的目录”意味着 VB6 IDE EXE。我们有时会在调用 DLL 之前使用 chdir 将当前目录更改为 app.path,以便在从 IDE 执行时可以找到它们。 Windows 查找 DLL 的规则记录在这里 msdn.microsoft.com/en-us/library/windows/desktop/… VB6 文档ChDir statement。您还需要 ChDrive,以防当前驱动器发生变化。显示常用对话框(打开、另存为)将更改当前驱动器/目录。

以上是关于从 VB6 调用 C dll,其中 dll 是使用 Visual Studio 2013 编写的的主要内容,如果未能解决你的问题,请参考以下文章

找不到从 VB6 调用的 C++ DLL 程序

从 VB6 调用一个简单的 VC DLL

是否可以同时调试 VB6 和 C++ COM dll?

从 VB6 访问 C++ DLL 中的位图资源

尝试从C#调试到VB6会产生TYPE_E_CANTLOADLIBRARY?

如何在不通过 COM 的情况下从 VB6 调用 C++ DLL?