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 数组一样处理它似乎是错误的。更改 结构,可能会导致灾难
我正在使用 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 数组一样处理它似乎是错误的。更改 结构,可能会导致灾难