Golang中VB6调用DLL出现"Bad DLL calling convention"错误的解决方案

Golang中VB6调用DLL出现"Bad DLL calling convention"错误的解决方案 我正在尝试制作一个能在VB6中工作的DLL。

我在DLL中有以下代码:

package main

// +build windows win32

import (
	"C"
	"fmt"
)

//export GetIntFromDLL
func GetIntFromDLL() int32 {
	return 42
}

//export PrintHello
func PrintHello(Name *C.char, Output **C.char) int32 {
	*Output = C.CString(fmt.Sprintf("From DLL: Hello, %s!", C.GoString(Name)))
	return int32(len(C.GoString(*Output)))
}

//export PrintBye
func PrintBye(Output **C.char) int32 {
	*Output = C.CString(fmt.Sprintf("From DLL: Bye!"))
	return int32(len(C.GoString(*Output)))
}

//export LenString
func LenString(Input *C.char) int32 {
	return int32(len(C.GoString(Input)))
}

//export OutString
func OutString() (Output *C.char) {
	buf := C.CString("I am out string from DLL")
	return buf
}

func main() {
	// main
}

set CC=mingw32-gcc set GOOS=windows set GOARCH=386 set CGO_ENABLED=1 go build -buildmode=c-archive main.go mingw32-gcc -shared -o testDLL.dll testDLL.c main.a -Wl,–add-stdcall-alias -lWinMM -lntdll -lWS2_32

DLL文件编译正常。 并且我创建了一个测试应用程序来在GO中使用这个dll:

package main

import (
	"C"
	"fmt"
	"syscall"
	"time"
	"unsafe"
)

var (
	dllTest           = syscall.NewLazyDLL("testDLL.dll")
	procGetIntFromDLL = dllTest.NewProc("GetIntFromDLL")
	procPrintHello    = dllTest.NewProc("PrintHello")
	procPrintBye      = dllTest.NewProc("PrintBye")
	procLenString     = dllTest.NewProc("LenString")
	procOutString     = dllTest.NewProc("OutString")
)

func main() {
	fmt.Println("usageDLL v1.1")

	start := time.Now()

	x1, _, _ := procGetIntFromDLL.Call()
	fmt.Println("GetIntFromDLL():", x1)
	buffer2 := make([]byte, 256)
	x2, _, _ := procPrintHello.Call(uintptr(unsafe.Pointer(syscall.StringBytePtr("Juan"))), uintptr(unsafe.Pointer(&buffer2)))
	fmt.Println("PrintHello('Juan',&buffer):", string(buffer2[0:x2]))
	buffer3 := make([]byte, 256)
	x3, _, _ := procPrintBye.Call(uintptr(unsafe.Pointer(&buffer3)))
	fmt.Println("PrintBye(&buffer):", string(buffer3[0:x3]))
	x4, _, _ := procLenString.Call(uintptr(unsafe.Pointer(syscall.StringBytePtr("12345678"))))
	fmt.Println("LenString('12345678'):", x4)
	x5, _, _ := procOutString.Call()
	fmt.Println("OutString():", C.GoString((*C.char)(unsafe.Pointer(x5))))
	elapsed := time.Since(start)

	fmt.Printf("Generated in %s\n", elapsed)

}

在GO中一切正常! 但我无法让它在VB6中工作,我是这样声明的:

Declare Function GetIntFromDLL Lib "testDLL.dll" () As Long
Declare Function PrintHello Lib "testDLL.dll" (ByVal Nombre As String, ByRef Output As String) As Long
Declare Function PrintBye Lib "testDLL.dll" (ByRef Output As String) As Long
Declare Function LenString Lib "testDLL.dll" (ByVal Nombre As String) As Long
Declare Function OutString Lib "testDLL.dll" () As String

在VB6中,只有 GetIntFromDLL()OutString() 能工作。其他的函数返回“错误的DLL调用约定”。

我听说过STDCALL,但我不知道如何实现它。

谢谢!


更多关于Golang中VB6调用DLL出现"Bad DLL calling convention"错误的解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

嗨,我仍然无法从Go语言创建用于VB6的DLL。 :frowning_face:

更多关于Golang中VB6调用DLL出现"Bad DLL calling convention"错误的解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


@Jonathan_Hecl,关于如何生成一个能在VB6中使用的DLL,有什么发现吗?

根据我的发现:

  • VB6 需要使用 stdcall 调用约定。它没有其他可用的选项。
  • go build --buildmode=c-shared 使用的是 cdecl 调用约定。

Dependency Walker 返回以下错误:

错误:至少一个必需的隐式或转发依赖项未找到。 警告:至少一个延迟加载依赖模块未找到。 警告:由于延迟加载依赖模块中缺少导出函数,至少一个模块存在未解析的导入。

您可能会发现下面这篇文章很有用,因为它描述了如何使用 “dumpbin” 工具来发现和处理 DLL 调用约定。

https://www.codeproject.com/Articles/6243/Step-by-Step-Calling-C-DLLs-from-VC-and-VB-Part

问题在于VB6默认使用stdcall调用约定,而Go默认导出的是cdecl调用约定。你需要使用//go:export指令配合syscall调用约定来导出函数。

以下是修正后的Go代码:

package main

// +build windows win32

import (
	"C"
	"fmt"
	"syscall"
	"unsafe"
)

//export GetIntFromDLL
func GetIntFromDLL() int32 {
	return 42
}

//export PrintHello
func PrintHello(Name *C.char, Output **C.char) int32 {
	*Output = C.CString(fmt.Sprintf("From DLL: Hello, %s!", C.GoString(Name)))
	return int32(len(C.GoString(*Output)))
}

//export PrintBye
func PrintBye(Output **C.char) int32 {
	*Output = C.CString(fmt.Sprintf("From DLL: Bye!"))
	return int32(len(C.GoString(*Output)))
}

//export LenString
func LenString(Input *C.char) int32 {
	return int32(len(C.GoString(Input)))
}

//export OutString
func OutString() (Output *C.char) {
	buf := C.CString("I am out string from DLL")
	return buf
}

func main() {
	// main
}

// 使用syscall.Syscall来确保stdcall调用约定
var (
	mod = syscall.NewLazyDLL("testDLL.dll")
	procGetIntFromDLL = mod.NewProc("GetIntFromDLL")
	procPrintHello = mod.NewProc("PrintHello")
	procPrintBye = mod.NewProc("PrintBye")
	procLenString = mod.NewProc("LenString")
	procOutString = mod.NewProc("OutString")
)

// 包装函数确保stdcall
func GetIntFromDLLWrapper() int32 {
	ret, _, _ := procGetIntFromDLL.Call()
	return int32(ret)
}

func PrintHelloWrapper(Name *C.char, Output **C.char) int32 {
	ret, _, _ := procPrintHello.Call(uintptr(unsafe.Pointer(Name)), uintptr(unsafe.Pointer(Output)))
	return int32(ret)
}

func PrintByeWrapper(Output **C.char) int32 {
	ret, _, _ := procPrintBye.Call(uintptr(unsafe.Pointer(Output)))
	return int32(ret)
}

func LenStringWrapper(Input *C.char) int32 {
	ret, _, _ := procLenString.Call(uintptr(unsafe.Pointer(Input)))
	return int32(ret)
}

func OutStringWrapper() *C.char {
	ret, _, _ := procOutString.Call()
	return (*C.char)(unsafe.Pointer(ret))
}

编译命令需要添加-ldflags "-s -w -H=windowsgui"来确保正确的调用约定:

set GOOS=windows
set GOARCH=386
set CGO_ENABLED=1
go build -buildmode=c-shared -ldflags "-s -w -H=windowsgui" -o testDLL.dll main.go

VB6声明也需要调整,添加AliasPtrSafe

Declare Function GetIntFromDLL Lib "testDLL.dll" Alias "GetIntFromDLL@0" () As Long
Declare Function PrintHello Lib "testDLL.dll" Alias "PrintHello@8" (ByVal Nombre As String, ByRef Output As String) As Long
Declare Function PrintBye Lib "testDLL.dll" Alias "PrintBye@4" (ByRef Output As String) As Long
Declare Function LenString Lib "testDLL.dll" Alias "LenString@4" (ByVal Nombre As String) As Long
Declare Function OutString Lib "testDLL.dll" Alias "OutString@0" () As String

对于字符串参数处理,VB6端需要这样调用:

Private Sub TestDLL()
    Dim result As Long
    Dim outputBuffer As String
    Dim bufferSize As Long
    
    ' GetIntFromDLL
    result = GetIntFromDLL()
    Debug.Print "GetIntFromDLL: " & result
    
    ' PrintHello
    outputBuffer = String$(255, 0)
    bufferSize = PrintHello("Juan", outputBuffer)
    If bufferSize > 0 Then
        Debug.Print "PrintHello: " & Left$(outputBuffer, bufferSize)
    End If
    
    ' PrintBye
    outputBuffer = String$(255, 0)
    bufferSize = PrintBye(outputBuffer)
    If bufferSize > 0 Then
        Debug.Print "PrintBye: " & Left$(outputBuffer, bufferSize)
    End If
    
    ' LenString
    result = LenString("12345678")
    Debug.Print "LenString: " & result
    
    ' OutString
    Dim outStr As String
    outStr = OutString()
    Debug.Print "OutString: " & outStr
End Sub

关键点是:

  1. Go导出函数时使用syscall.Syscall确保stdcall调用约定
  2. 编译时使用-buildmode=c-shared生成标准的Windows DLL
  3. VB6声明时使用正确的函数名修饰(name decoration),32位stdcall函数名格式为FunctionName@Bytes
  4. 字符串参数需要预分配缓冲区并正确处理长度

这样修改后,VB6就能正确调用所有DLL函数了。

回到顶部