【问题标题】:Difference between array of c_char inside and outside a structure in python for fortran library用于fortran库的python中结构内部和外部的c_char数组之间的区别
【发布时间】:2020-02-06 10:39:47
【问题描述】:

我正在使用 c_types 将 fortran 库与 python 连接。我在python中初始化结构,将它们传递给填充它们的fortran,然后在python中读回它们。数字数组一切正常,但现在我被字符串数组接口卡住了。

我试过像one 这样的例子,这没问题,但在这种情况下,c_char 数组不在结构中。因此,我尝试修改前面的示例,将 c_char 数组放入结构中。这是我使用的代码,有和没有结构:

Python 代码:

    from ctypes import *
    lib = CDLL("./libf.so")

    if 1:
        print(">>> Without structure")
        func = getattr(lib, "fortran2py_")
        nstring = pointer(c_long(2))
        carr = (c_char * 255)()
        func.argtypes = [POINTER(c_long), POINTER(c_char)]

        print(type(carr))
        print('before:',carr)
        func(nstring, carr)
        str1, str2 = ''.join([v.decode("utf-8") for v in carr]).rstrip("\x00").split("\x00")
        print(str1, str2)


    class Struct0(Structure):
        _fields_ = [
            ("name", c_char * 255),
        ]

    if 1:    
        print(">>> With structure")
        func = getattr(lib, "fortran2pystr_")
        nstring = pointer(c_long(2))
        carr = Struct0()
        func.argtypes = [POINTER(c_long), POINTER(Struct0)]
        print(type(carr.name))
        print('before:',carr.name)
        func(nstring, byref(carr))
        print('after:',carr.name)

Fortran 代码:

    module c_interop

        use iso_c_binding
        implicit none
        integer, parameter :: STRLEN = 64

        type, bind(c) :: charStr
           character(c_char)  :: name(255)
        end type charStr

        contains

        subroutine fortran2py(nstring, cstring_p) bind(C, name="fortran2py_")
            integer(c_int), intent(in) :: nstring
            character(c_char), dimension(*), intent(inout) :: cstring_p
            integer :: i, j, ks, kf, n
            character(len=STRLEN) :: mystr(2)

            mystr(1) = "This is the first string."
            mystr(2) = "Wow. Fortran + Python + Strings = Pain !"
            ks = 1
            do i = 1, nstring
                n = len_trim(mystr(i))
                kf = ks + (n - 1)  
                cstring_p(ks:kf) = transfer(mystr(i)(1:n), cstring_p(ks:kf))
                cstring_p(kf + 1) = c_null_char
                ks = ks + n + 1
            enddo
        end subroutine fortran2py

        subroutine fortran2pystr(nstring, cstring_p) bind(C, name="fortran2pystr_")
            integer(c_int), intent(in) :: nstring
            type(charStr), intent(inout) :: cstring_p
            integer :: i, j, ks, kf, n
            character(len=STRLEN) :: mystr(2)

            mystr(1) = "This is the first string."
            mystr(2) = "Wow. Fortran + Python + Strings = Pain !"
            ks = 1
            do i = 1, nstring
                n = len_trim(mystr(i))
                kf = ks + (n - 1)  
                cstring_p%name(ks:kf) = transfer(mystr(i)(1:n), cstring_p%name(ks:kf))
                cstring_p%name(kf + 1) = c_null_char
                ks = ks + n + 1
            enddo
        end subroutine fortran2pystr

    end module c_interop

我没有得到任何错误,除了在修改部分,Fortran 应该填充 c_char carr.name 循环在 mystr 的元素上的数组,但结果字符串只包含第一个元素。当carr不是结构体而是直接c_char数组时,python可以读取mystr的所有内容。

输出:

>>> Without structure
<class '__main__.c_char_Array_255'>
before: <__main__.c_char_Array_255 object at 0x151b3b092bf8>
This is the first string. Wow. Fortran + Python + Strings = Pain !
>>> With structure
<class 'bytes'>
before: b''
after: b'This is the first string.'

正如您所见,carr 和 carr.name 的类型也不相同。您知道我修改后的代码有什么问题吗?谢谢!

【问题讨论】:

    标签: python fortran ctypes


    【解决方案1】:

    上市[Python 3.Docs]: ctypes - A foreign function library for Python

    原因是 CTypes 微妙的行为。 c_char(以及 c_wchar)数组在以字段形式出现时会被静默转换为 bytes(或 str)在一个结构中。这是通过 c_char_p(或 c_wchar_p)完成的,它们是 NUL 终止,这意味着“数组”NUL (0x00) 字符,strong>将被截断,这正是你的情况。您可以通过查看字段类型来检查。
    不知道为什么会这样(也许是为了方便使用),但在某些情况下它弊大于利。它只能用 Python 代码重现。

    code00.py

    #!/usr/bin/env python
    
    import sys
    import ctypes as ct
    
    
    ARR_DIM = 10
    CharArr = ct.c_char * ARR_DIM
    
    
    class CharArrStruct(ct.Structure):
        _fields_ = [
            ("data", CharArr),
        ]
    
    
    def print_array(arr,  text, size=ARR_DIM):
        print(text)
        for i in range(size):
            print("{0:3d}".format(i), end=" - ")
            try:
                print(arr[i])
            except IndexError:
                print("IndexError!!!")
                break
        print()
    
    
    def main(*argv):
        arr = CharArr()
        sarr = CharArrStruct()
        print("Array (plain) type: {0:}".format(type(arr)))
        print("Array (in structure) type: {0:}".format(type(sarr.data)))
    
        string_separator = b"\x00"
        print("\nString separator: {0:}".format(string_separator))
        text = string_separator.join((b"abcd", b"efgh"))
        arr[0:len(text)] = text
        sarr.data = text
    
        print_array(arr, "Plain array:")
        print_array(sarr.data, "Structure with array:")
        print("Strings (in structure): {0:}".format(sarr.data.split(string_separator)))
    
        string_separator = b"\xFF"
        print("\nString separator: {0:}".format(string_separator))
        sarr.data = string_separator.join((b"abcd", b"efgh"))
    
        print_array(sarr.data, "Structure with array:")
        print("Strings (in structure): {0:}".format(sarr.data.split(string_separator)))
    
    
    if __name__ == "__main__":
        print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        main(*sys.argv[1:])
        print("\nDone.")
    

    输出

    e:\Work\Dev\StackOverflow\q060093054>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
    Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    Array (plain) type: <class '__main__.c_char_Array_10'>
    Array (in structure) type: <class 'bytes'>
    
    String separator: b'\x00'
    Plain array:
      0 - b'a'
      1 - b'b'
      2 - b'c'
      3 - b'd'
      4 - b'\x00'
      5 - b'e'
      6 - b'f'
      7 - b'g'
      8 - b'h'
      9 - b'\x00'
    
    Structure with array:
      0 - 97
      1 - 98
      2 - 99
      3 - 100
      4 - IndexError!!!
    
    Strings (in structure): [b'abcd']
    
    String separator: b'\xff'
    Structure with array:
      0 - 97
      1 - 98
      2 - 99
      3 - 100
      4 - 255
      5 - 101
      6 - 102
      7 - 103
      8 - 104
      9 - IndexError!!!
    
    Strings (in structure): [b'abcd', b'efgh']
    
    Done.
    

    注意事项

    • 如所见,data 字段类型已更改
    • 我想到的最简单的解决方案是将字符串分隔符从 NUL 替换为另一个 char 您确定 它不会出现在任何你的字符串。我选择了 0xFF (255)。我认为包含ctypes.POINTER(ctypes.c_char) 的结构也可以,但它会更复杂一些(另外,我没有测试它)
    • 我的 Fortran 知识非常接近 0,但是 看起来不太正确 >fortran2pystr。我不知道 Fortran 类型是如何构造的,但是传递一个包裹在 struct 指针中的 char 数组(实际上,它们具有相同的地址) 来自 Python 并像普通 char 数组一样处理它似乎是错误的。更改 结构,可能会导致灾难

    【讨论】:

    • 谢谢!更改字符串分隔符就可以了。奇怪的行为......而且我理解像这样处理 char 的风险,但现在它允许我只使用一个参数将很多变量传递给我的 fortran 代码。
    猜你喜欢
    • 2020-06-18
    • 2018-07-24
    • 1970-01-01
    • 1970-01-01
    • 2014-08-01
    • 2011-11-04
    • 2017-05-05
    • 2011-07-29
    • 2013-11-02
    相关资源
    最近更新 更多