Fortran 库的 python 结构内部和外部 c_char 数组的区别

Difference between array of c_char inside and outside a structure in python for fortran library

我正在使用 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 3.Docs]: ctypes - A foreign function library for Python.

原因是 CTypes 微妙的行为。 c_char(以及 c_wchar)数组被静默转换为 字节 (或 str)当它们作为字段出现在结构中时。这是通过 c_char_p(或 c_wchar_p)完成的,即 NUL 终止,这意味着如果NUL ( 0x00) char 会遇到,这正是你的情况。您可以通过查看字段类型来检查。
不知道这是为什么(可能是为了方便使用),但在某些情况下弊大于利。它只能用 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\Whosebug\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.

备注:

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