python for fortran库中结构内外的c_字符数组之间的差异

2024-05-19 08:57:58 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在使用c_类型将fortran库与python连接起来。我用python初始化结构,将它们传递给fortran,由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应该填充mystr元素上循环的c_char carr.name数组,但是结果字符串只包含第一个元素。当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的类型也不相同。你知道我修改过的代码有什么问题吗?谢谢大家!


Tags: nametypeinteger数组结构funcprintks
1条回答
网友
1楼 · 发布于 2024-05-19 08:57:58

清单[Python 3.Docs]: ctypes - A foreign function library for Python

原因是一种微妙的行为c_char(以及c_wchar)数组在结构中以字段形式出现时,会自动转换为字节(或str)。这是通过c\u char\u p(或c\u wchar\u p)完成的,它们是num终止的,这意味着如果遇到num0x00)字符,“数组”将被截断,这正是您的情况。您可以通过查看字段类型来检查这一点
我不知道这是为什么(可能是为了方便使用),但在某些情况下,它弊大于利。它只能用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.

注释

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

相关问题 更多 >

    热门问题