第三方dll中未处理的delphi异常

时间:2018-09-10 07:30:04

标签: delphi go dll interop pinvoke

我有一个动态库,需要从该库中调用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: 好吧,这似乎也无关紧要,我知道对于任何人而言,信息太少,无法对正在发生的事情做出任何猜测。因此,我将尝试跟踪此异常的确切来源,然后再回到这个问题。

2 个答案:

答案 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 structs - 三个和两个字段对应。 因此,当您使用包含以下内容的变量的地址时 切片或字符串,这是第一个字段的地址 该结构的 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.Pointeruintptr

    我承认这不是显而易见的事情,但逻辑如下:

    • 对于Go GC,包含值的变量 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,所以我们在这里应该没事。