Передача массива символов из VBA в Fortran DLL через Тип повреждает другие члены типа
Верьте или нет, это название примерно так же коротко, как я мог бы сделать это и все еще описать проблему, которую я имею!
Итак, вот сценарий: я вызываю Fortran DLL из VBA, и DLL использует пользовательские типы или любое имя 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
Примечание: это 32-разрядная версия gfortran, так как я использую 32-разрядная версия Excel.
сторона VBA:
во-первых, зеркальные операторы simpleType и declare:
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)
Странное, Багги Поведение:
теперь, когда мы прошли настройку, я могу описать, что происходит:
(я играю с длиной массива символов, оставляя длину отдельного персонажа символы установлены в один. Во всех случаях я сопоставляю параметры массива символов с обеих сторон.)
тестовый пример: длина CharacterArray = 1
для этого первого случая все отлично работает, я передаю simpleTypeIn и simpleTypeOut из VBA, DLL Fortran принимает его и копирует simpleTypeIn в simpleTypeOut, а после вызова VBA возвращает simpleTypeOut с идентичными атрибутами CharacterArray, FirstInteger и т. д.
тестовый пример: длина CharacterArray = 2
здесь все становится интересным.
перед вызовом simpleTypeIn был определен. Сразу после звонка, simpleTypeIn.ThirdInteger изменился с 3 до 65! Еще более странно, 65-это значение ASCII для символа A, который является 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, что является значением ASCII для B.
не быть превзойденным, simpleTypeOut.SecondInteger вышел как 16961 и simpleTypeOut.ThirdInteger был 17475. Другие значения успешно скопированы (я decommented в B, C и D характер заданий, чтобы соответствовать массива размер.)
замечания:
это странное повреждение кажется линейным по отношению к байтам в массиве символов. Я сделал некоторое тестирование, которое я каталогизирую, если кто-то хочет в понедельник с отдельными символами длины 2 вместо 1, и повреждение произошло, когда массив имел размер 1, а не ожидание, пока размер не будет 2. Он также не "пропускал" дополнительное повреждение, когда размер массива был 3, как размер = 1 случай делавший.
Это легко Зал славы ошибка для меня; я уверен, вы можете себе представить, сколько сосредоточенного удовольствия это было изолировать в крупномасштабной программе с тонной атрибутов типа. Если у кого-нибудь есть какие-либо идеи, это было бы очень признательно!
если я не вернусь к вам сразу, это потому, что я звоню в день, но я попытаюсь контролировать свой почтовый ящик.
2 ответов
(этот ответ основан на понимании 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
в этом случае я поместил оба модуля и основную процедуру в один и тот же файл. Как правило, их можно хранить в отдельных файлах, но для этого примера это нормально. Мне также пришлось установить 8 элементов CharacterArray
чтобы проявить ошибку, но точный размер зависит от параметры системы, компилятора и оптимизации. Запуск этого на моей машине дает:
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)
похоже, что я (редактировать: нет) сбор моей собственной щедрости сегодня!
корень этой проблемы заключается в том, что VBA занимает 2 байта на символ, в то время как Fortran ожидает 1 байт на символ. Искажение памяти вызвано тем, что массив символов занимает больше места в памяти, чем ожидает Fortran. Способ отправки 1 байтовых символов в Fortran таков:
тип Определение:
Type simpleType
CharacterArray(3) As Byte
FirstInteger As Integer
SecondInteger As Integer
ThirdInteger As Integer
End Type
преобразование из символа VBA в байтовые значения:
Dim tempByte() As Byte
tempByte = StrConv("A", vbFromUnicode)
simpleTypeIn.CharacterArray(0) = tempByte(0)
tempByte = StrConv("B", vbFromUnicode)
simpleTypeIn.CharacterArray(1) = tempByte(0)
tempByte = StrConv("C", vbFromUnicode)
simpleTypeIn.CharacterArray(2) = tempByte(0)
tempByte = StrConv("D", vbFromUnicode)
simpleTypeIn.CharacterArray(3) = tempByte(0)
этот код успешно передает строки, переданные в качестве аргументов функции StrConv. Я проверил, что они переведены на правильные символы ASCII в Fortran DLL, и они это сделали! Кроме того, целые числа больше не передаются неправильно! Жучок из зала славы был проштампован.