Ошибка определения типа пользователя

Я разрабатываю систему для (моего) малого бизнеса. У меня около 20 файлов данных (клиенты/поставщики/магазины/основные средства/аренда/сотрудники... и т.д.). Каждая запись этих файлов определяется с помощью оператора Type и написана или прочитана с помощью оператора Put или Get.
Каждый файл данных поддерживается или увеличивается с помощью отдельной рабочей книги. У меня также есть отдельные книги для управления повседневными процессами компании. (Продажа/аренда/движение магазина и т.д.). Эти "рабочие" книги в значительной степени зависят от записей из файлов данных. Они также создают дополнительные файлы данных для ежедневных движений.
Система управляется одной книгой под названием Menu.xlsm, которая позволяет пользователю выбрать нужную книгу. Menu.xlsm содержит все инструкции типа, общие процедуры, функции и формы. Он упоминается во всех других книгах и всегда открыт. Пользователь ограничен двумя открытыми книгами - Меню и другое.
Система находится на сетевом сервере и написана таким образом, что пользователь может открыть только книги "только для чтения". Пользователь NEVER сохраняет книгу, они всегда сохраняют данные в файле данных.
В основном у меня есть система баз данных, и я использую Excel в качестве интерфейса.

Оператор My Type

Public Type CLocDesc
   Atv As String * 3
   CadName As String * 10
   CadDate As Date
   EditName As String * 10
   EditDate As Date
   Empresa As String * 10
   OSNo As Integer
   ClNo As Integer
   Fantasia As String * 30
   Cidade As String * 40
   UF As String * 2
   PedClient As String * 30
   InsCid As String * 30
   InsUF As String * 2
   DtStart As Date
   DtEnd As Date
   QtMod As Integer
   QtAr As Integer
   QtOut As Integer
   LocMods As Single
   LocAr As Single
   LocOther As Single
   LocVenc As Integer
End Type
Public CLoc As CLocDesc  ' This appears at the top of the module.

Я с абсолютной уверенностью знаю, что Len (CLoc) = 223
Этот конкретный файл контролирует договоры аренды компании. Мы сдаем в аренду нашим клиентам. Я английский, но сделал Бразилию своим домом. Таким образом, некоторые имена элементов являются португальцами.
Всякий раз, когда пользователь открывает Рабочую книгу по аренде, этот файл (Rental.rnd) загружается автоматически стандартной процедурой модуля (LoadData()), вызванной workbook_open().
Это процедура LoadData. Некорректный код не указывается. (Кондиционная нагрузка /% нагрузка/таблица)

'                                                      LOAD  DATA  .
Sub LoadData()
Open Range("MDP") + "Rental.rnd" For Random As #1 Len = Len(Cloc)
Nitems = LOF(1) / Len(Cloc)       ' Number of records
J = 0                             ' Line counter for data table
With Range("DataTable")
   For I = 1 To Nitems
   '                      On Error Resume Next
   Get #1, I, Cloc               ' This command  : Error 59 - Bad record length.
   '                      On Error GoTo 0
   J = J + 1
   .Cells(J, 1) = I
   .Cells(J, 2) = Trim(Cloc.CadName)
   .Cells(J, 3) = Cloc.CadDate
   .Cells(J, 4) = Trim(Cloc.EditName)
   .Cells(J, 5) = Cloc.EditDate
   .Cells(J, 6) = Trim(Cloc.Atv)
   .Cells(J, 7) = Trim(Cloc.Empresa)
   .Cells(J, 8) = Cloc.OSNo
   .Cells(J, 9) = Cloc.ClNo
   .Cells(J, 10) = Trim(Cloc.Fantasia)
   .Cells(J, 11) = Trim(Cloc.Cidade)
   .Cells(J, 12) = Trim(Cloc.uf)
   .Cells(J, 13) = Trim(Cloc.PedClient)
   .Cells(J, 14) = Trim(Cloc.InsCid)
   .Cells(J, 15) = Trim(Cloc.InsUF)
   .Cells(J, 16) = Cloc.DtStart
   .Cells(J, 17) = Cloc.DtEnd
   .Cells(J, 18) = Cloc.QtMod
   .Cells(J, 19) = Cloc.QtAr
   .Cells(J, 20) = Cloc.QtOut
   .Cells(J, 21) = Cloc.LocMods         ' Bad read starts here
   .Cells(J, 22) = Cloc.LocAr
   .Cells(J, 23) = Cloc.LocOther
   .Cells(J, 24) = Cloc.LocOther + Cloc.LocAr + Cloc.LocMods
   .Cells(J, 25) = Cloc.LocVenc
   Next I
End With
Close
End Sub

Когда ошибка не возникает, данные загружаются правильно.
Когда ошибка возникает, я раскомментирую команды On error и повторно запускаю программу. Программа заканчивается нормально, и данные в таблице показывают, что данные были правильно прочитаны до Cloc. QtOut и последующие элементы не читаются.
Похоже, что "Ошибка 59" Плохая длина записи "является результатом того, что" код разбора VBA "не может интерпретировать данные в байтах с 210 по 213 данных буфера CLC, считанных оператором Get.
Чтобы проверить это, я добавил этот код:

Type AllClocDesc
   StAll As String * 223
End Type
Dim AllCloc As AllClocDesc
...and ...
Get #1, I, AllCloc

Таким образом, у меня есть строка из 223 байтов (AllCloc.StAll), идентичная буферу, считываемому оскорбительными Get # 1, I, Cloc. Затем я написал проект для анализа этой строки и проверки данных на диске. Я могу отправить код, если хотите). Данные на диске CORRECT. Если я закрываю и снова открываю книгу, ошибка сохраняется.

Как я уже говорил выше, оператор типа и публичная декаляризация CLoc находятся в Menu.xlsm. Код LoadData и, следовательно, код, генерирующий ошибку, находятся в книге под названием Rentals.xlsm. Итак, я закрываю Rental.xlsm. В Menu.xlsm я вырезал "Public CLoc As CLocDesc" и вставлял его в несколько другое место. Затем отлаживайте/компилируйте и сохраняйте, но не закрывайте Menu.xlsm. Как будто магия LoadData() завершается нормально, с правильными данными.

Сохраненная копия Menu.xlsm должна быть идентична сохраненной копии. Закрыть Rental.xlsm, Закрыть Menu.xlsm. Reopen Menu.xlsm, Reopen Rental.xlsm. ПОТЕРПЕТЬ НЕУДАЧУ!! Ошибка 59 Плохая длина записи.

Я сказал выше, что пользователи открывают книги "только для чтения", поэтому два пользователя могут одновременно открывать книгу (почти). Для одного пользователя часто возникает ошибка 59, а другая нет. Одна и та же книга и те же данные!

У меня около 30 файлов произвольного доступа. Около 10 из них имеют в прошлом или в настоящее время дают идентичные проблемы. У меня 22 книги объемом 4.04 MB. Я прекратил добавлять просто, потому что пользователи больше не могут использовать систему.

Я думал об использовании модулей классов для данных. Но 30 классов классов вместо 30 операторов типа. Поговорите о кувалде, чтобы взломать орех. Когда я только начинал, я использовал print/write и delimiters. Я сдался очень быстро, когда пользователи начали включать в свои тексты комы, точки с запятой и кавычки. Я считаю, что Microsoft намеренно создала UDT/Get/Put для той цели, для которой я ее использую.

Здесь происходит очень очень странное событие.

Как я могу решить свою проблему?

Ян Симмонс

Это обновление для сообщений выше. Поскольку у моей компании есть подписка на Office 365, я решил обратиться за помощью к M microsoft. Первая проблема заключалась в том, чтобы найти зарегистрированного пользователя, у которого было разрешение открыть билет поддержки. Он оказался продавцом, который продал нам подписку (не мой ИТ-парень?). Обещанный 4-часовой возврат закончился 3 днями. Наконец, у нас был конференц-звонок - я/инженер/аналитик по микрософт и кто-то из продавца. Оба пытались объяснить мне, что, поскольку проблема связана с моим кодом, они (microsoft) не смогли помочь. Билет: SUP86188 - LATAM-BR-MSFT-O365-Solicitação Eng microsoft Чтобы открыть билет, я должен был сообщить подробности о проблеме продавцу, и я включил список должностей, которые я сделал. Конференц-связь потерпела неудачу несколько раз, наконец, инженер/аналитик по микрософт вызвал меня прямо и признал, что после консультаций с должностями он тоже был убежден, что это ошибка, и предложил сообщить об этом в Microsoft. Я спросил, почему ОН не мог сообщить об этом, и он ответил, что НЕ ДОПУСКАЕТСЯ. Хотелось бы, чтобы я записал этот разговор! Позже я получил электронное письмо от продавца, в котором указано, что билет был ПОСТАНОВЛЕН и закрыт. Это отвратительное поведение от многонационального. Я умышленно пропустил имена из этого сообщения - количество билетов достаточно, если кто-либо из Microsoft будет заинтересован. Любые предложения?

Ответ 1

Использование Open For Random не является идеальным, если он преобразует строки из BSTR/UTF16 на 2 байта в ANSI на 1 байт с потенциальной потерей в зависимости от символа. Тем не менее, ваша проблема может быть вызвана состоянием гонки или, возможно, процедура пытается загрузить поврежденную или другую запись.

Вместо этого используйте Open For Binary Shared для чтения/записи данных без преобразования и за один раз:

Private Declare PtrSafe Sub MemCpy Lib "kernel32" Alias "RtlMoveMemory" (dst As Any, src As Any, ByVal size As LongPtr)

Const path = "c:\temp\record.bin"

Sub AddRecord()

  ' dummy record '
  Dim record As CLocDesc
  record.Atv = "123"
  record.LocMods = 1.76

  ' to binary '
  Dim buffer() As Byte
  ReDim buffer(0 To LenB(record) - 1)
  MemCpy buffer(0), ByVal VarPtr(record), LenB(record)

  ' check file length is a multiple of the record length '
  If Len(Dir(path)) Then If FileLen(path) Mod LenB(record) Then _
    Err.Raise 5, , "Unexpected file length"

  ' to file '
  Dim f As Integer
  f = FreeFile
  Open path For Binary Shared As f
    Put f, FileLen(path) + 1, buffer
  Close

End Sub

Sub LoadRecords()

  ' check file length is a multiple of the record length '
  Dim record As CLocDesc
  If FileLen(path) Mod LenB(record) Then Err.Raise 5, , "Unexpected file length"

  ' load file to buffer '
  Dim f As Integer, p As Long, buffer() As Byte
  ReDim buffer(0 To FileLen(path) - 1)

  f = FreeFile
  Open path For Binary Shared As f
    Get f, 1, buffer
  Close

  ' to records '
  Dim records() As CLocDesc
  ReDim records(0 To FileLen(path) \ LenB(record) - 1)
  MemCpy ByVal VarPtr(records(0)), buffer(0), UBound(buffer) + 1

End Sub

Но работать с записями, хранящимися непосредственно в файле, будет больно, потому что вам придется вручную обновлять большинство из них, если в какой-то момент вам нужно добавить новое поле/столбец.

Лучшим решением будет настройка базы данных. Вы можете использовать базу данных Access или простой файл Excel, доступный с ADO-подключением.

Простой альтернативой было бы использовать Recordset для сохранения/загрузки записей в/из файла:

' Required reference: Microsoft ActiveX Data Objects '

Sub UsageRecordset()
  Dim rs As ADODB.Recordset, fields As ADODB.fields, i As Long

  ' create a recordset, define the fields and save it to a file '

  Set rs = New ADODB.Recordset
  rs.CursorLocation = adUseClient

  Set fields = rs.fields
  fields.Append "Id", adBSTR, 8
  fields.Append "Price", adDouble

  rs.Open
  rs.Save "c:\temp\records.dat"
  rs.Close

  ' add some records '

  Set rs = New ADODB.Recordset
  rs.CursorLocation = adUseClient
  rs.Open "c:\temp\records.dat"

  rs.AddNew
  rs("Id").value = "kt547865"
  rs("Price").value = 4.7

  rs.AddNew
  rs("Id").value = "kt986543"
  rs("Price").value = 2.3

  rs.Save
  rs.Close

  ' read all the records to a sheet '

  Set rs = New ADODB.Recordset
  rs.CursorLocation = adUseClient
  rs.Open "c:\temp\records.dat"

  rs.MoveFirst
  ActiveSheet.Range("A2").CopyFromRecordset rs

  rs.Close

  ' iterate all the records '

  Set rs = New ADODB.Recordset
  rs.CursorLocation = adUseClient
  rs.Open "c:\temp\records.dat"

  rs.MoveFirst
  For i = 1 To rs.RecordCount
    Debug.Print rs("Id").value
    Debug.Print rs("Price").value
    rs.MoveNext
  Next

  rs.Close

  ' find a specific record '

  Set rs = New ADODB.Recordset
  rs.CursorLocation = adUseClient
  rs.Open "c:\temp\records.dat", LockType:=adLockReadOnly

  rs.MoveFirst
  rs.Find "[Price] < 5", , 1, 2

  If Not rs.EOF Then
    Debug.Print rs("Id").value
    Debug.Print rs("Price").value
  End If

  rs.Close

End Sub

Ответ 2

@Ин Симмондс, в вашем тексте вопроса вы говорите, что попробовали это

Type AllClocDesc
   StAll As String * 223
End Type


Sub Test()
    '...
    Dim AllCloc As AllClocDesc
    '...and ...
    Get #1, I, AllCloc

End Sub

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

Type AllClocDesc2
   abAllBytes(0 To 222) As Byte
End Type

Sub Test2()
    Dim I, l


    'Dim AllCloc As AllClocDesc
    Dim AllCloc2 As AllClocDesc2
    '...and ...
    Get #1, I, AllCloc2


    LSet CLoc = AllCloc2

End Sub

LSet копирует байт для байта. Вы можете проверить, что копируется в ваш многопоточный тип, и вы можете проверить, что на самом деле находится на диске, просмотрев массив байтов. Надеюсь, это поможет.