我有一个动态库,需要从该库中调用Go程序中的一个过程,但似乎无法正确执行。我有另一个用C#编写的程序,它使用此DLL并根据dnSpy dllimport编译为:
[DllImport("Library.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern void Calc(double[] Input, [MarshalAs(UnmanagedType.VBByRefStr)] ref string Pattern, [MarshalAs(UnmanagedType.VBByRefStr)] ref string Database_path, double[] Output);
因此,基于此,我试图像这样在我的代码中调用它:
func main() {
lib := syscall.NewLazyDLL("Library.dll")
proc := lib.NewProc("Calc")
input := [28]float64{0.741, 0.585, 2, 12, 1, 1, 1, 101325, 2500, 3, 20, 17.73, 17.11, 45, 1, 0, 80, 60, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0}
output := [20]float64{}
tubePattern := marshalAnsi("P8-16G-E145/028")
basePath, _ := filepath.Abs(".")
databasePath := marshalAnsi(basePath)
a1 := uintptr(unsafe.Pointer(&input))
a2 := uintptr(unsafe.Pointer(tubePattern))
a3 := uintptr(unsafe.Pointer(databasePath))
a4 := uintptr(unsafe.Pointer(&output))
ret, _, _ := proc.Call(a1, a2, a3, a4)
log.Println(ret)
log.Println(output)
}
func marshalAnsi(input string) *[]byte {
var b bytes.Buffer
writer := transform.NewWriter(&b, charmap.Windows1252.NewEncoder())
writer.Write([]byte(input))
writer.Close()
output := b.Bytes()
return &output
}
这导致未处理的delphi异常:
Exception 0xeedfade 0x31392774 0x32db04e4 0x7677ddc2
PC=0x7677ddc2
syscall.Syscall6(0x314af7d0, 0x4, 0x10ee7eb8, 0x10ed21a0, 0x10ed21d0, 0x10ee7d78, 0x0, 0x0, 0x0, 0x0, ...)
C:/Go32/src/runtime/syscall_windows.go:184 +0xcf
syscall.(*Proc).Call(0x10ed21e0, 0x10ed85e0, 0x4, 0x4, 0x10, 0x49bee0, 0x533801, 0x10ed85e0)
C:/Go32/src/syscall/dll_windows.go:152 +0x32e
syscall.(*LazyProc).Call(0x10ecb9c0, 0x10ed85e0, 0x4, 0x4, 0x0, 0x0, 0x4c6d70, 0x5491a0)
C:/Go32/src/syscall/dll_windows.go:302 +0x48
main.main()
C:/Users/Krzysztof Kowalczyk/go/src/danpoltherm.pl/dllloader/main.go:32 +0x2c0
eax 0x19f3f0
ebx 0x31392774
ecx 0x7
edx 0x0
edi 0x10ee7d78
esi 0x31392774
ebp 0x19f448
esp 0x19f3f0
eip 0x7677ddc2
eflags 0x216
cs 0x23
fs 0x53
gs 0x2b
我相信问题可能出在将这些字符串传递给过程的方式上,但是我看不出我的代码与C#应用程序有什么不同。
图书馆还有其他一些程序,我可以这样打电话给DLL_Version
,没有问题:
lib := syscall.NewLazyDLL("Library.dll")
verProc := lib.NewProc("DLL_Version")
var version float64
verProc.Call(uintptr(unsafe.Pointer(&version)))
log.Printf("DLL version: %f\n", version)
输出:
2018/09/10 09:13:11 DLL version: 1.250000
编辑: 我相信我发现了导致异常的原因。似乎从我的代码中调用此过程的第二个和第三个参数(应该是指向字符串缓冲区的指针),而是指向这些缓冲区上的指针的指针,如果我在过程的开头中断并手动对此进行修补,则代码将正确终止。我仍然没有找到发生这种情况的原因,以及如何解决它。
EDIT2: 显然,我之前对该案件的评估无效。我能够阻止程序引发异常,但是我对程序集进行了分析,结果发现这仅是由于程序在引发此异常之前退出了,而不是异常,我得到了有关无效字符串的正常错误代码。
虽然我不太擅长理解反汇编代码,但看来问题确实可能是由调用约定不匹配引起的。在过程代码的开头,当从工作程序EAX,EBX,ECX和EDX寄存器中调用时为空,同时,从我的代码中调用时,只有EAX和ECX为空,EDX保留了一些内容,但是EBX指向我的可执行文件中的Calc过程指针。在DLL的后面,有一部分检查EBX是否为空,如果没有则抛出异常。
编辑3: 好吧,这似乎也无关紧要,我知道对于任何人而言,信息太少,无法对正在发生的事情做出任何猜测。因此,我将尝试跟踪此异常的确切来源,然后再回到这个问题。
答案 0 :(得分:1)
好的,所以我终于找到了答案。 @kostix注意到需要go-ole
,因为DLL使用COM,所以我必须先调用CoInitializeEx。
但这只是其中的一部分,结果是过程的字符串参数被声明为UnicodeString,这需要我组合一个转换函数以正确适合文档中描述的布局:http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Unicode_in_RAD_Studio#New_String_Type:_UnicodeString
完成后,所有功能都将按预期工作。
答案 1 :(得分:0)
您一直在犯一个相当普遍的错误:将指针指向Go字符串或切片。
问题是切片和字符串
are in fact struct
s -
三个和两个字段对应。
因此,当您使用包含以下内容的变量的地址时
切片或字符串,这是第一个字段的地址
该结构的 not 容器中包含的第一项。
因此,第一个解决方法是替换所有位置 利用
var v []whatever
p := unsafe.Pointer(&v)
和
var s string
p := unsafe.Pointer(&s)
使用
var v []whatever
p := unsafe.Pointer(&v[0])
和
var s string
p := unsafe.Pointer(&s[0])
相应地。
还要考虑两点:
我很难弄清楚VBByRefStr
的确切语义
只是通过阅读措辞不佳的MSDN文档,但从粗略
阅读了一些我设法在Google上发布的帖子,
看来您的目标函数输入了这些参数
为PChar
。
如果我的评估正确,那么您通过的字符串 函数必须以零结尾,因为它们不是 Delphi本地字符串,并且没有在其中编码的长度字段 他们。
因此在准备字符串时,请确保您添加
末尾的\x00
字符。
请注意,在您的示例中,Go字符串为纯ASCII (请注意,UTF-8是ASCII干净的,即UTF-8编码 只包含7位ASCII字符的字符串 在将它们传递给任何想要查看ASCII数据的东西之前进行任何重新编码),并且Windows代码页也与ASCII兼容。 因此,对于您的情况来说,我不会打乱正确的字符集转换,而是会做类似
的操作func toPChar(s string) unsafe.Pointer {
dst := make([]byte, len(s)+1)
copy(dst, s)
dst[len(s)] = 0 // NUL terminator
return unsafe.Pointer(&dst[0])
}
…一旦将其与纯ASCII字符串一起使用, 在混合中添加字符转换。
不存储类型的转换结果
在变量中从unsafe.Pointer
到uintptr
!
我承认这不是显而易见的事情,但逻辑如下:
unsafe.Pointer
类型的内存构成对内存的实时引用
它指向(当然,如果值不是非nil
)。uintptr
类型专门定义为表示
不透明的指针大小的整数值,而不是指针。
因此,地址存储在类型为uintptr
的变量中
不会算作对该地址内存的实时引用。这些规则意味着您采用以下地址时
一些变量并将其存储在类型的变量中
uintptr
,这意味着您可能有零引用
到那个内存块。
例如,在此示例中
func getPtr() unsafe.Pointer
...
v := uintptr(getPtr())
GC可以自由地将内存回收为指针
由getPtr()
返回到。
作为一种特殊情况,Go保证可以使用
在表达式中将unsafe.Pointer
强制转换为uintptr
计算得出实际参数以调用函数。
因此,不要将中间指针存储为uintptr
而是
比unsafe.Pointer
并将它们投射到函数调用站点,
像
a1 := unsafe.Pointer(&whatever[0])
...
ret, _, _ := proc.Call(uintptr(a1), uintptr(a2),
uintptr(a3), uintptr(a4))
希望这会有所帮助。
看到实际的Delphi-native类型也很酷 调用的功能。
另一个问题可能是调用约定不匹配
但是IIUC P /调用默认为winapi
CC
(在Windows上等于stdcall
),并且由于您的P / Invoke
包装器没有明确列出任何抄送,我们希望它是
确实winapi
,所以我们在这里应该没事。