Visual2000 · Архив статей А.Колесова & О.Павловой

Советы тем, кто программирует на Visual Basic

Андрей Колесов, Ольга Павлова

© 1998, Андрей Колесов, Ольга Павлова
Авторский вариант. Статья была опубликована c незначительной журнале "КомпьютерПресс" N 09/98, c. 160-164.


Совет 144а. Как принудительно изменить атрибут файла

Читатель присал нам такой вопрос:

Мне нужно после создания файла принудительно изменить его атрибут - дату создания - на предопределенный заранее. Полагаю, что правильно было бы использовать вызов API-функции SetFileTime. Я пытаюсь это сделать так (в этом примере мы убрали вспомогательный код программы с описанием переменных и процедур. - Прим. авт.):

Open FName For Output As #1
If SetFileTime(1, FileDate, FileDate, FileDate) <>0 Then ...
Close #1

Но что-то не получается...

Наш ответ:

В данном случае наш читатель допустил весьма характерную ошибку: смешал два варианта доступа к файлам - с помощью встроенных операторов VB и функций API. Действительно, возможности программиста при работе с файлами могут быть существенно расширены за счет использования соответствующего набора API-функций (как это ранее делалось в DOS с помощью функций DOS/BIOS).

Среди таких полезных операций можно упомянуть, например, возможность чтения за одно обращение больших массивов (а не по отдельным элементам), фиксацию состояния файлов без выполнения закрытия/открытия, коррекцию атрибутов и многое другое. При этом идентификация файлов выполняется с так называемыми описателями (handle). По смыслу они аналогичны понятию "логический номер" (ЛН), но вот нумерация их принципиально различается: для handle она ведется на уровне всей ОС, а для ЛН - отдельного приложения. Если сразу после запуска программы вы затребуете свободный номер, то для ЛН получите 1, а для handle это значение может быть что-то типа 49.

В данном случае после открытия файла с ЛН=1 было сделано обращение к функции SetFileTime для handle=1. При этом не понятно, к какому файлу он относится, и к тому же он вообще не был открыт в данной программе. Вывод таков: при работе с конкретным файлом можно пользоваться только одним типом доступа - либо операторами VB, либо API.

Примечание автора. Уже после публикации этого совета мне попадалась информация о том, что можно получить номер описателя handle для файла, открытого с помощью ЛН. Поэтому, возможно, вполне реально использование одновременно двух режимов доступа. Нужно проверять...

В качестве примера решения проблемы читателя предлагаем такой вариант:

' функции преобразования формата даты
Private Declare Function SystemTimeToFileTime& Lib _
  "kernel32" (lpSystemTIME As SYSTEMTIME, _
  lpFileTime As FILETIME)

Private Declare Function FileTimeToSystemTime& Lib _
  "kernel32" (lpFileTime As FILETIME, lpSystemTIME _
  As SYSTEMTIME)

' функции работы с файлами
Private Declare Function lopen& Lib "kernel32" _
  Alias "_lopen" (ByVal lpFileName As String, _
  ByVal wReadWhite As Long)

Private Declare Function lclose& Lib "kernel32" _
  Alias "_lclose" (ByVal hFile As Long)

Private Declare Function SetFileTime& Lib "kernel32" _
  (ByVal hFile As Long, lpCreationTime As FILETIME, _
  lpLastAccessTime As FILETIME, lpLastWriteTime As FILETIME)

' функция для анализа ошибок
Private Declare Function GetLastError& Lib "kernel32" ()

' для хранения даты во внутреннем формате
Private Type FILETIME 
  dwLowDateTime As Long
  dwHighDateTime As Long
End Type

' для хранения даты в системном формате
Private Type SYSTEMTIME 
  wYear As Integer
  wMonth As Integer
  wDayOfWeek As Integer
  wDay As Integer
  wHour As Integer
  wMinute As Integer
  wSecond As Integer
  wMilliseconds As Integer
End Type 

Private Sub Form_Load()
  Dim SysTime As SYSTEMTIME, NowTime As FILETIME
  Dim FileName$, handleF&, wReadWrite&, k&, k1&
  ' дата в системном формате
  SysTime.wYear = 1997
  SysTime.wMonth = 10
  SysTime.wDay = 3
  ' преобразование даты во внутренний двоичный формат
  k& = SystemTimeToFileTime(SysTime, NowTime)
  '
  ' имя файла - он должен существовать
  FileName$ = "d:\d.txt"
  ' Работа с файлами только средствами функций API
  ' ВНИМАНИЕ! Для изменения атрибутов файла,
  ' он должен  быть открыт в режиме "разрешения
  ' записи", например:  режим "чтение-запись"
  ' Const OF_READWRITE& = 2
  wReadWrite& = 2
  ' Открытие файла
  handleF& = lopen&(FileName$, wReadWrite&)
  ' запись новых атрибутов даты
  k& = SetFileTime&(handleF&, NowTime, NowTime, NowTime)
  ' была ли ошибка? Можно проверить
  k1& = GetLastError ' код ошибки
  ' закрытие файла
  Call lclose(handleF&)
End Sub

В начало статьи

Совет 144. Для тех, кто занимается геометрическими расчетами

Возможно, вам пригодятся две процедуры, которые приведены в модуле XY_TESTC.BAS (см. ниже). Они сохранились у нас еще со времен Basic/DOS, поэтому их текст и имеет такой вид (например, все ключевые слова записаны заглавными буквами). Процедура CircleTestXY определяет местоположение точки относительно фигуры-многоугольника (внутри или снаружи), CircleSquare вычисляет площадь многоугольника. Следует обратить внимание на то, что одна из вершин многоугольника задана в массиве дважды - в качестве начальной и конечной точки.

Кстати. Раньше названия языков программирования и их ключевых слов было принято писать большими буквами. Однако в начале 90-х годов Международная Организация по Стандартам (ISO - International Standard Organization) приняла решение об изменении этого правила, С тех пор они пишутся так: первая буква - заглавная, остальные - прописные.

DECLARE SUB CircleTestXY (xyd!(), Np%, x0!, y0!, kz%)
DECLARE SUB CircleSquare (xyd!(), Np%, Square!)
DEFINT I-N
'**************************************************
'  Модуль XY_TESTC.BAS
'
' Процедуры:
' CircleTestXY - определение местоположения точки 
' относительно фигуры-многоугольника
' CircleSquare - вычисление площади многоугольника
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''"""""""""""""""""""""""""""""""""
' тестовый пример использования функций
  Np = 6: DIM xyd(Np, 2)   ' массив для пятиугольника
  xyp(1, 1) = 10: xyp(2, 1) = 20
  xyp(1, 2) = 0: xyp(2, 2) = 10
  xyp(1, 3) = -10: xyp(2, 3) = 20
  xyp(1, 4) = -10: xyp(2, 4) = -20
  xyp(1, 5) = 10: xyp(2, 5) = -20
  xyp(1, Np) = xyp(1, 1): xyp(2, Np) = xyp(2, 1)
  ' вычисление площади многоугольника
  CALL CircleSquare(xyp(), Np, Square)
  ' проверка - где находится заданная точка?
  x0 = 0: y0 = 0   ' координаты тестируемой точки
  CALL CircleTestXY(xyp(), Np, x0, y0, kz)
  PRINT "kz, Square = "; kz; Square
END

SUB CircleSquare (xyd(), Np, Square)
  ' Вычисление площади многоугольника 
  '————————————————————————————————
  ' ВХОД:
  ' xyd() - массив координат углов многоугольника 
  ' x = xyd(1,i), y = xyd(2,i) ; i = 1 to Np
  '  (Np-1) - количество узлов
  '  координаты 1-й точки = координатам N-й
  '
  ' ВЫХОД: Square - площадь многоугольника 
  '''''''''''''''''''''''''''''''''''''''''''''''""""""""""""""""""""""""""""""""""
  CONST pi = 3.141593
  Square = 0
  FOR k = 1 TO Np  ' Np + 1
    x2 = xyd(1, k): y2 = xyd(2, k)
    v2 = SQR(x2 * x2 + y2 * y2)
    ay2 = ABS(y2): ax2 = ABS(x2)
    IF ax2 * 10000 > ay2 THEN 
      alfa2 = ATN(ay2 / ax2)
    ELSE alfa2 = pi * .5
    END IF
    IF x2 < 0 THEN alfa2 = pi - alfa2
    IF y2 < 0 THEN alfa2 = -alfa2
    IF k > 1 THEN   ' проверка перехода
      Square = Square + .5 * SIN(alfa2 - alfa1) * v1 * v2
    END IF
    x1 = x2: y1 = y2: v1 = v2: alfa1 = alfa2
  NEXT
END SUB

SUB CircleTestXY (xyd(), Np, x0, y0, kz)
  '
  ' Проверка местонахождения точки на плоскости
  ' относительно многоугольника - внутри или снаружи
  '————————————————————————-
  ' ВХОД:
  '  xyd() - массив координат углов многоугольника
  '  x = xyd(1,i), y = xyd(2,i) ; i = 1 to Np
  '  (Np-1) - количество узлов
  '  координаты 1-й точки = координатам N-й точки
  '  x0,y0  - координаты тестируемой точки
  '
  ' ВЫХОД:  положение тестируемой точки
  ' kz = 0  - вне
  '      = -100  - на границе
  '      = -4  - внутри (обход по часовой стрелке)
  '      =  4   - внутри (против часовой стрелки)
  ''''''''''''''''''''''''''
  kz = 0
  FOR k = 1 TO Np   ' Np + 1
    ' IF l > Np THEN k = 1 ELSE k = l
    x2 = xyd(1, k) - x0: y2 = xyd(2, k) - y0
    '
    ' проверка четверти плоскости
    kv2 = 0
    IF x2 >= 0 AND y2 > 0 THEN kv2 = 1
    IF x2 < 0 AND y2 >= 0 THEN kv2 = 2
    IF x2 <= 0 AND y2 < 0 THEN kv2 = 3
    IF x2 > 0 AND y2 <= 0 THEN kv2 = 4
    IF kv2 = 0 THEN kz = -100: EXIT FOR
    '
    IF k > 1 THEN   ' проверка перехода
      IF kv2 <> kv1 THEN ' переход в другую четверть 
        kv = kv2 - kv1
        IF kv = 3 THEN kv = -1
        IF kv = -3 THEN kv = 1
        IF kv = 2 OR kv = -2 THEN ' переход через две четверти 
          IF x1 = x2 THEN kz = -100: EXIT FOR
          yb = (y2 * x1 - y1 * x2) / (x1 - x2)
          IF yb = 0 THEN kz = -100: EXIT FOR 
          kv = kv * SGN(yb)
          IF kv1 = 2 OR kv1 = 4 THEN kv = -kv
        END IF
        kz = kz + kv
      END IF
    END IF
    x1 = x2: y1 = y2: kv1 = kv2
  NEXT
END SUB

В начало статьи