Передача массива символов из 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, и они это сделали! Кроме того, целые числа больше не передаются неправильно! Жучок из зала славы был проштампован.