为什么这个Delphi DLL在从Python调用时会抛出一个WindowsError?

时间:2015-02-20 15:54:06

标签: python delphi dll enums ctypes

环境:Windows 7(x64)上的Python 2.7.9(32位)

我在Python中使用ctypes来访问名为“tdbaccess.dll”(found here)的DLL,我假设它是用Delphi编写的,基于作者的文档和this info on the error I'm getting。遗憾的是,我无法访问DLL的源代码。 DLL用于打开旧的Madden NFL PC游戏所使用的“数据库”名单文件(我使用的是2008版本的名单 - 不要判断:P)。

我的代码包含以下两个结构:

class tdbTableProperties(Structure):
    _fields_ = [
        ('Name', c_char_p),
        ('FieldCount', c_int),
        ('Capacity', c_int),
        ('RecordCount', c_int),
        ('DeletedCount', c_int),
        ('NextDeletedRecord', c_int),
        ('Flag0', c_bool),
        ('Flag1', c_bool),
        ('Flag2', c_bool),
        ('Flag3', c_bool),
        ('NonAllocated', c_bool),
        ('HasVarchar', c_bool),
        ('HasCompressedVarchar', c_bool),
    ]

class tdbFieldProperties(Structure):
    _fields_ = [
        ('Name', c_char_p),
        ('Size', c_int),
        ('FieldType', c_int),
    ]

这些结构的实例通过引用传递给DLL中的两个函数,这两个函数又填充了里面的字段。首先,将tdbTableProperties的实例附加到列表中并按原样传递:

# Create a list to hold all our table properties structs.
listTdbTableProperties = []

# Loop over the tables and get their properties.
for i in range(intNumberOfTables):
    listTdbTableProperties.append(tdbTableProperties(Name="ASDF")) #Initialize Name field per docs
    boolGotTableProperties = tdbaccessDLL.TDBTableGetProperties(intDBIndex, i, byref(listTdbTableProperties[i]))
    if boolGotTableProperties:
        print("listTdbTableProperties[%d].Name = %r" % (i, listTdbTableProperties[i].Name))
        print("listTdbTableProperties[%d].FieldCount = %d\n" % (i, listTdbTableProperties[i].FieldCount))

这很好,并提供以下输出:

listTdbTableProperties[0].Name = 'CITY'
listTdbTableProperties[0].FieldCount = 21

listTdbTableProperties[1].Name = 'COCH'
listTdbTableProperties[1].FieldCount = 68

[...]

listTdbTableProperties[5].Name = 'INJY'
listTdbTableProperties[5].FieldCount = 5

listTdbTableProperties[6].Name = 'PLAY'
listTdbTableProperties[6].FieldCount = 110

[...]

我真的只对“PLAY”表感兴趣,所以现在我尝试使用tdbFieldProperties实例进行类似的操作,以获取该表中每个字段的属性......

# A list to hold all our field properties structs for the player table.
listPlayerTableTdbFieldProperties = []

# Get the properties of each of the fields for the PLAY table (index 6 in listTdbTableProperties).
for i in range(listTdbTableProperties[6].FieldCount):
    listPlayerTableTdbFieldProperties.append(tdbFieldProperties(Name="Blah")) #Initialize Name field per docs
    print("listPlayerTableTdbFieldProperties[%d].Name BEFORE = %r" % (i, listPlayerTableTdbFieldProperties[i].Name))
    boolGotTableFieldProperties = tdbaccessDLL.TDBFieldGetProperties(intDBIndex, listTdbTableProperties[6].Name, i, byref(listPlayerTableTdbFieldProperties[i]))
    if boolGotTableFieldProperties:
        print("listPlayerTableTdbFieldProperties[%d].Name AFTER = %r" % (i, listPlayerTableTdbFieldProperties[i].Name))

......然而,它最终失败了,总是在第六个表的110个字段中的第70个:

listPlayerTableTdbFieldProperties[0].Name BEFORE = 'Blah'
listPlayerTableTdbFieldProperties[0].Name AFTER = 'TRV1'
listPlayerTableTdbFieldProperties[1].Name BEFORE = 'TRV1'
listPlayerTableTdbFieldProperties[1].Name AFTER = 'TEZ1'
listPlayerTableTdbFieldProperties[2].Name BEFORE = 'TEZ1'
listPlayerTableTdbFieldProperties[2].Name AFTER = 'TRV2'
[...]
listPlayerTableTdbFieldProperties[68].Name BEFORE = 'TAth'
listPlayerTableTdbFieldProperties[68].Name AFTER = 'TAss'
listPlayerTableTdbFieldProperties[69].Name BEFORE = 'TAss'
Traceback (most recent call last):
  File "DLLtest2.py", line 70, in <module>
    boolGotTableFieldProperties = tdbaccessDLL.TDBFieldGetProperties(intDBIndex, listTdbTableProperties[6].Name, i, byref(listPlayerTableTdbFieldProperties[i]))
WindowsError: [Error 250477278] Windows Error 0xEEDFADE

根据tdbaccess.dll的文档,在Delphi或Visual Basic .NET实现中,tdbFieldProperties结构将FieldType作为枚举,在Python中没有精确的表示。来自文档:

Delphi Syntax  
  type
    TtdbFieldProperties = packed record
      Name: PWideChar;
      Size: Integer;
      FieldType: TtdbFieldType;
  end;

Visual Basic .NET Syntax
  <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
  Structure TdbFieldProperties
    Public Name As String
    Public Size As Integer
    Public FieldType As TdbFieldType
  End Structure

Delphi Syntax
  type
    TtdbFieldType = (tdbString = 0, tdbBinary = 1, tdbSInt = 2, tdbUInt = 3, tdbFloat = 4, tdbVarchar = $D, tdbLongVarchar = $E, tdbInt = $2CE);

Visual Basic .NET Syntax
  Enum TdbFieldType
    tdbString = 0
    tdbBinary = 1
    tdbSInt = 2
    tdbUInt = 3
    tdbFloat = 4
    tdbVarchar = &HD
    tdbLongVarchar = &HE
    tdbInt = &H2CE
  End Enum

因此,基于answers to this question,我尝试在Python中使用c_int,c_uint(以及几乎所有其他ctype)作为FieldType的类型。但无论我使用什么,我都会在第6个循环的循环的第70次迭代中得到Windows错误0xEEDFADE。不仅如此,我在tdbFieldProperties结构中为Name字段获取的值不是错误之前的值。如果我扩展我的代码以获取每个表中每个字段的字段属性,那么我查询的每个表都会以相同的顺序在“名称”字段中放置相同的值,所以即使在错误本身之前,显然有些错误:

# Loop over the fields in each tdbtpTableProperties and get the properties of each field.
tdbfpTableFieldProperties = [[] for x in range(intNumberOfTables)]

for i in range(intNumberOfTables):
    print("\n")
    for j in range(tdbtpTableProperties[i].FieldCount):
        tdbfpTableFieldProperties[i].append(tdbFieldProperties(Name="Blah"))
        print("tdbfpTableFieldProperties[%d][%d].Name BEFORE = %r" % (i, j, tdbfpTableFieldProperties[i][j].Name))
        boolGotTableFieldProperties = tdbaccessDLL.TDBFieldGetProperties(intDBIndex, tdbtpTableProperties[i].Name, j, byref(tdbfpTableFieldProperties[i][j]))
        print("tdbfpTableFieldProperties[%d][%d].Name AFTER = %r" % (i, j, tdbfpTableFieldProperties[i][j].Name))

给出了:

tdbfpTableFieldProperties[0][0].Name BEFORE = 'Blah'
tdbfpTableFieldProperties[0][0].Name AFTER = 'TRV1'
tdbfpTableFieldProperties[0][1].Name BEFORE = 'TRV1'
tdbfpTableFieldProperties[0][1].Name AFTER = 'TEZ1'
tdbfpTableFieldProperties[0][2].Name BEFORE = 'TEZ1'
tdbfpTableFieldProperties[0][2].Name AFTER = 'TRV2'
tdbfpTableFieldProperties[0][3].Name BEFORE = 'TRV2'
tdbfpTableFieldProperties[0][3].Name AFTER = 'TEZ2'
[...]
tdbfpTableFieldProperties[0][20].Name BEFORE = 'CGID'
tdbfpTableFieldProperties[0][20].Name AFTER = 'DGID'

tdbfpTableFieldProperties[1][0].Name BEFORE = 'DGID'
tdbfpTableFieldProperties[1][0].Name AFTER = 'TRV1'
tdbfpTableFieldProperties[1][1].Name BEFORE = 'TRV1'
tdbfpTableFieldProperties[1][1].Name AFTER = 'TEZ1'
tdbfpTableFieldProperties[1][2].Name BEFORE = 'TEZ1'
tdbfpTableFieldProperties[1][2].Name AFTER = 'TRV2'
tdbfpTableFieldProperties[1][3].Name BEFORE = 'TRV2'
tdbfpTableFieldProperties[1][3].Name AFTER = 'TEZ2'
[...]
tdbfpTableFieldProperties[1][67].Name BEFORE = 'TCTX'
tdbfpTableFieldProperties[1][67].Name AFTER = 'TAth'

tdbfpTableFieldProperties[2][0].Name BEFORE = 'TAth'
tdbfpTableFieldProperties[2][0].Name AFTER = 'TRV1'
tdbfpTableFieldProperties[2][1].Name BEFORE = 'TRV1'
tdbfpTableFieldProperties[2][1].Name AFTER = 'TEZ1'
tdbfpTableFieldProperties[2][2].Name BEFORE = 'TEZ1'
tdbfpTableFieldProperties[2][2].Name AFTER = 'TRV2'
tdbfpTableFieldProperties[2][3].Name BEFORE = 'TRV2'
tdbfpTableFieldProperties[2][3].Name AFTER = 'TEZ2'
[...]

我确定这不是DLL中的错误,因为我知道Visual C#项目在Python中尝试做同样的事情,没有任何问题。我有什么想法可能做错了吗?非常感谢提前!

1 个答案:

答案 0 :(得分:2)

第一个明显的错误是c_char_p是指向8位文本的指针。但Delphi和VB.net结构清楚地显示用户16位Unicode文本。将c_char_p替换为c_wchar_p

下一个问题涉及记录的布局。 Delphi和VB.net代码不同意。 Delphi记录已打包,VB.net结构已对齐。你的Python结构是对齐的。您可以在ctypes结构中尝试_pack_ = 1来打包记录。任何人都可以猜测记录是如何布局的。

我们不确定枚举类型有多大。它们可以是1,2或4个字节。我们也没有要检查的所有结构声明,所以除了我在这里写的内容之外,可能存在并且可能存在问题。