Передача символьного массива из VBA в Fortran DLL через тип приводит к повреждению других членов типа - PullRequest
0 голосов
/ 22 сентября 2018

Хотите верьте, хотите нет, этот заголовок настолько короткий, насколько я мог бы его описать, и все же опишите проблему, с которой я столкнулся!

Итак, вот сценарий: я звоню в DLL Фортрана из VBA,и DLL использует определяемые пользователем типы или любое другое имя Fortran для этого (structs?) в качестве аргумента и копирует тип обратно вызывающей стороне для проверки.

Тип имеет массив символов фиксированной длины и некоторый ряд целых чисел.

Я заметил странное поведение в любых атрибутах, определенных после этого массива символов, которые я расскажу ниже, сразу после того, как я опишу свою настройку тестирования в режиме «сворачивания»:


Сторона Фортрана:

Вот основная программа:

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 и объявление:

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)

Странное, багги поведение:

Теперь, когда мы прошли настройку, я могу описать, что происходит:

(я играю сдлина массива символов, оставляя длину отдельных символов равной 1. Я сопоставляю параметры массива символов с обеих сторон во всех случаяхs.)


Контрольный пример: длина 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 был преобразован, но не так!1069 * Контрольный пример: длина CharacterArray = 3

Этот случай был идентичен случаю 2, как ни странно.


Контрольный пример: длина CharacterArray = 4

В этом также странном случае после вызова simpleTypeIn.SecondInteger изменилось с 2 на 65, а simpleTypeIn.ThirdInteger изменилось с 3 на 66, что является значением ASCII для B.

Notчтобы быть превзойденным, simpleTypeOut.SecondInteger получился как 16961, а simpleTypeOut.ThirdInteger был 17475. Остальные значения были успешно скопированы (я разложил назначения символов B, C и D, to соответствовать размеру массива.)


Замечания:

Это странное искажение кажется линейным относительно байтов в массиве символов.Я провел некоторое тестирование, которое я внесу в каталог, если кто-то захочет в понедельник с отдельными символами длины 2 вместо 1, и повреждение произошло, когда массив имел размер 1, в отличие от ожидания, пока размер не стал равным 2. Это также нене пропускайте дополнительное искажение, когда размер массива был равен 3, как в случае с размером = 1.


Это легко для меня ошибка в зале славы;Я уверен, что вы можете себе представить, насколько это было весело, чтобы изолировать в крупномасштабной программе с кучей атрибутов Type.Если у кого-то есть какие-либо идеи, это будет с благодарностью!

Если я сразу не вернусь к вам, это потому, что я звоню один день, но я постараюсь отслеживать свой почтовый ящик.

Ответы [ 2 ]

0 голосов
/ 25 сентября 2018

Похоже, я ( Редактировать : нет) собираю свою награду сегодня!

Корень этой проблемы заключается в том, что VBA занимает 2 байта на символ, а Fortran ожидает 1 байт на символ.Искажение памяти вызвано тем, что массив символов занимает больше места в памяти, чем ожидает Фортран.Способ отправки 1-байтовых символов в Фортран таков:


Определение типа:

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

0 голосов
/ 22 сентября 2018

(Этот ответ основан на понимании Fortran, но не VBA)

В этом случае и в большинстве случаев Fortran не будет автоматически изменять размеры массивов для вас.Когда вы ссылаетесь на второй элемент символьного массива (с simpleTypeIn.CharacterArray(1) = "B"), этот элемент не существует и не создается.

Вместо этого код пытается установить, какая память будет в месте расположения второго элемента массива символов, если он существует.В этом случае эта память используется для хранения целых чисел.

Вы можете увидеть то же самое, если полностью забудете о VBA.Вот пример кода, полностью на Фортране, чтобы продемонстрировать похожее поведение:

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)
...