信不信由你,这个标题几乎可以做到,而且仍然可以描述我遇到的问题!
因此,这是一种情况:我正在从VBA调用Fortran DLL,并且该DLL使用用户定义的类型或该名称的任何Fortran名称(结构?)作为自变量,然后将类型复制回调用方以进行验证。
该类型具有固定长度的字符数组和一些整数。
我注意到,在描述了简化的测试设置之后,我将在下面介绍的该字符数组之后定义的任何属性中都有一些有趣的行为:
Fortran方面:
这是主程序:
SUBROUTINE characterArrayTest (simpleTypeIn, simpleTypeOut)
use simpleTypeDefinition
!GCC$ ATTRIBUTES STDCALL :: characterArrayTest
type(simpleType), INTENT(IN) :: simpleTypeIn
type(simpleType), INTENT(OUT) :: simpleTypeOut
simpleTypeOut = simpleTypeIn
END SUBROUTINE characterArrayTest
这是simpleTypeDefinition模块文件:Module simpleTypeDefinition
Type simpleType
character (len=1) :: CharacterArray(1)
!The length of the array is one here, but modified in tests
integer (kind=2) :: FirstInteger
integer (kind=2) :: SecondInteger
integer (kind=2) :: ThirdInteger
End Type simpleType
End Module simpleTypeDefinition
编译步骤:
gfortran -c simpleTypeDefinition.f90 characterArrayTest.f90
gfortran -shared -static -o characterArrayTest.dll characterArrayTest.o
注意:这是gfortran的32位版本,因为我使用的是32位版本的Excel。VBA方面:
首先,镜像的simpleType和clarify语句:
Type simpleType
CharacterArray(0) As String * 1
'The length of the array is one here, but modified in tests
FirstInteger As Integer
SecondInteger As Integer
ThirdInteger As Integer
End Type
Declare Sub characterArrayTest Lib "characterArrayTest.dll" _
Alias "characterarraytest_@8" _
(simpleTypeIn As simpleType, simpleTypeOut As simpleType)
接下来,调用代码:Dim simpleTypeIn As simpleType
Dim simpleTypeOut As simpleType
simpleTypeIn.CharacterArray(0) = "A"
'simpleTypeIn.CharacterArray(1) = "B"
'simpleTypeIn.CharacterArray(1) = "C"
'simpleTypeIn.CharacterArray(3) = "D"
simpleTypeIn.FirstInteger = 1
simpleTypeIn.SecondInteger = 2
simpleTypeIn.ThirdInteger = 3
Call Module4.characterArrayTest(simpleTypeIn, simpleTypeOut)
怪胎, buggy 行为:
现在我们已经完成了设置,我可以描述发生了什么:
(我正在研究字符数组的长度,同时将各个字符的长度设置为1。在所有情况下,我都在两侧匹配字符数组参数。)
测试用例:CharacterArray长度= 1
对于第一种情况,一切工作正常,我从VBA传入了simpleTypeIn和simpleTypeOut,Fortran DLL接受了它,并将simpleTypeIn复制到simpleTypeOut,然后在调用VBA之后,返回具有相同属性CharacterArray,FirstInteger等的simpleTypeOut。
测试用例:CharacterArray长度= 2
这就是事情变得有趣的地方。
在调用之前,simpleTypeIn已定义。通话后,simpleTypeIn.ThirdInteger从3更改为65!甚至更奇怪的是,65是字符A的ASCII值,它是simpleTypeIn.CharacterArray(0)。
我通过将“A”更改为“(”(具有ASCII值40)来测试这种关系,并且可以肯定的是,simpleTypeIn.ThirdInteger更改为40。很奇怪。
在任何情况下,都可以期望simpleTypeOut将成为simpleTypeIn变形为任何奇怪的东西的副本,但事实并非如此! simpleTypeOut是simpleTypeIn的副本,但simpleTypeOut.ThirdInteger为16961!
测试用例:CharacterArray长度= 3
这个案例与案例2相同,这很奇怪。
测试用例:CharacterArray长度= 4
在这种同样奇怪的情况下,在调用simpleTypeIn.SecondInteger从2更改为65之后,并且simpleTypeIn.ThirdInteger从3更改为66(这是B的ASCII值)之后。
不甘示弱,simpleTypeOut.SecondInteger的输出为16961,simpleTypeOut.ThirdInteger的输出为17475。其他值已成功复制(我建议对B,C和D字符赋值以匹配数组大小。)
观察值:
对于字符数组中的字节,这种奇怪的损坏似乎是线性的。我进行了一些测试,如果有人在星期一想要用长度为2而不是1的单个字符进行分类,那么损坏发生在数组的大小为1时发生,而不是等到大小为2时发生。当数组的大小为3时(例如size = 1情况下),不会“跳过”其他损坏。
对我来说,这很容易成为名人堂的 bug 。我敢肯定,您可以想象一下,在具有大量Type属性的大型程序中,隔离会带来多少乐趣。如果有人有任何想法,将不胜感激!
如果我没有马上回复您,那是因为我每天调用我,但是我会尝试监控我的收件箱。
最佳答案
(此答案基于对Fortran的理解,而不是基于VBA的理解)
在这种情况下,在大多数情况下,Fortran不会自动为您调整数组大小。当您引用字符数组的第二个元素(使用simpleTypeIn.CharacterArray(1) = "B"
)时,该元素不存在,也不会创建。
相反,代码将尝试设置字符数组第二个元素(如果存在)处的任何内存。在这种情况下,该内存似乎用来存储整数。
如果您完全忘记了VBA,则可以看到同一件事。这是完全在Fortran中的示例代码,以演示类似的行为:
enet-mach5% cat main.f90
! ===== Module of types
module types_m
implicit none
type simple_t
character(len=1) :: CharacterArray(1)
integer :: int1, int2, int3
end type simple_t
end module types_m
! ===== Module of subroutines
module subroutines_m
use types_m, only : simple_t
implicit none
contains
! -- Subroutine to modify first character, this should work
subroutine sub1(s)
type(simple_t), intent(INOUT) :: s
s%CharacterArray(1) = 'A'
end subroutine sub1
! -- Subroutine to modify first and other (nonexistent) characters, should fail
subroutine sub2(s)
type(simple_t), intent(INOUT) :: s
s%CharacterArray(1) = 'B'
s%CharacterArray(2:8) = 'C'
end subroutine sub2
end module subroutines_m
! ===== Main program, drives test
program main
use types_m, only : simple_t
use subroutines_m, only : sub1, sub2
implicit none
type(simple_t) :: s
! -- Set values to known
s%int1 = 1
s%int2 = 2
s%int3 = 3
s%CharacterArray(1) = 'X'
! -- Write out values of s
write(*,*) 'Before calling any subs:'
write(*,*) 's character: "', s%CharacterArray, '"'
write(*,*) 's integers: ', s%int1, s%int2, s%int3
! -- Call first subroutine, should be fine
call sub1(s)
write(*,*) 'After calling sub1:'
write(*,*) 's character: "', s%CharacterArray, '"'
write(*,*) 's integers: ', s%int1, s%int2, s%int3
! -- Call second subroutine, should overflow character array and corrupt
call sub2(s)
write(*,*) 'After calling sub2:'
write(*,*) 's character: "', s%CharacterArray, '"'
write(*,*) 's integers: ', s%int1, s%int2, s%int3
write(*,*) 'complete'
end program main
在这种情况下,我将模块和主例程放在了同一文件中。通常,可以将它们保存在单独的文件中,但是在此示例中可以。我还必须设置
CharacterArray
的8个元素来显示错误,但是确切的大小取决于系统,编译器和优化设置。在我的机器上运行它会产生:enet-mach5% gfortran --version
GNU Fortran (SUSE Linux) 4.8.3 20140627 [gcc-4_8-branch revision 212064]
Copyright (C) 2013 Free Software Foundation, Inc.
GNU Fortran comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of GNU Fortran
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING
enet-mach5% gfortran main.f90 && ./a.out
main.f90:31.20:
s%CharacterArray(2:8) = 'C'
1
Warning: Lower array reference at (1) is out of bounds (2 > 1) in dimension 1
Before calling any subs:
s character: "X"
s integers: 1 2 3
After calling sub1:
s character: "A"
s integers: 1 2 3
After calling sub2:
s character: "B"
s integers: 1128481603 2 3
complete
Gfortran足够聪明,可以标记一个编译时警告
s%CharacterArray(2)
超出范围。您可以看到字符数组没有调整大小,而int1
的值被破坏了。如果我使用更多的运行时检查进行编译,则会得到一个完整的错误:enet-mach5% gfortran -fcheck=all main.f90 && ./a.out
main.f90:31.20:
s%CharacterArray(2:8) = 'C'
1
Warning: Lower array reference at (1) is out of bounds (2 > 1) in dimension 1
Before calling any subs:
s character: "X"
s integers: 1 2 3
After calling sub1:
s character: "A"
s integers: 1 2 3
At line 31 of file main.f90
Fortran runtime error: Index '2' of dimension 1 of array 's' outside of expected range (1:1)