Александр Кресин software |
Rus Eng
|
3. Отличия от Клиппера - краткое описание
Харбор создавался как полностью совместимый с Клиппером компилятор. Это значит,
что любая ваша программа, написанная на Клиппере, должна без проблем компилироваться Харбором
и работать так же, как если бы она была откомпилирована Клиппером. Во многих случаях так оно и есть,
но, конечно, могут возникать и проблемы - не все получается так, как задумано, Харбор не свободен от ошибок и недоделок.
Еще один возможный источник проблем при переходе с Клиппера на Харбор - это дополнительные
библиотеки, вставки на C и ассемблере, если вы их используете - 16-разрядные объектники не прилинкуются к 32-разрядному приложению.
Если у вас есть исходники этих модулей, то можно попробовать перекомпилировать их, хотя и тут успех не гарантирован - не все функции
16-разрядного C компилятора доступны в 32-разрядном.
Но, конечно же, Харбор не просто повторяет функциональность Клиппера, а существенно расширяет ее - иначе какой
был бы в этом смысл ?! Помимо расширений, связанных с переходом от 16 к 32 разрядному коду ( снятие ограничений по памяти, по размерности массивов
и длине строк ), Харбор предлагает и языковые расширения, и дополнительные функциональные возможности. Они и будут рассмотрены ниже.
Сразу хочу предупредить, что этот перечень будет неполным - о чем-то я могу забыть, что-то не написать просто из-за нехватки времени и терпения.
Начну с немногого, потом постепенно, по мере возможности, буду пополнять этот список и расширять описания.
Рекомендую вам также воспользоваться документацией с официального сайта Harbour,
a также с постоянно пополняющегося сборника на docs.google.com.
Она, правда, тоже не полная, и, к тому же, не на русском - но, даже если у вас с английским проблемы, что-нибудь полезное вы оттуда все равно почерпнете - хотя бы названия
новых функций, образцы синтаксиса, и т.д. Очень рекомендую также уже упоминавшийся здесь
англоязычный документ xhb-diff.txt, который есть любом дистрибутиве Harbour
в каталоге Doc. Здесь содержится описание наиболее важных языковых расширений
Harbour ( по сравнению с Clipper ) и отличие их реализаций от xHarbour.
И еще пара слов о языковых новшествах. Лично я использую их только при самой крайней необходимости. Не забывайте, что чем дальше вы от традиционного Clipper-кода, тем больше проблем у вас возникнет при необходимости перейти на какой-либо другой компилятор - кто знает, что в этой жизни может понадобиться. Даже при переходе Harbour <--> xHarbour могут появиться проблемы, т.к. многие новшества по-разному там реализованы - см. xhb-diff.txt. И если переход на другой компилятор может выглядеть весьма отдаленной перспективой, то потребность просмотреть/переконвертировать/модифицировать ваш dbf сторонними средствами может появиться в любой момент - именно поэтому я никогда не использую новые типы полей в dbf - файлах, тем более, что всю необходимую функциональность можно реализовать без них.
3.2 Некоторые языковые расширения
Здесь мы рассмотрим несколько новых операторов, появившихся в Harbour. Начнем с FOR EACH:
FOR EACH var1 [,var255] IN expr1 [,expr255] [DESCEND] [EXIT] [LOOP] ... NEXT- expr - может быть обычным массивом, хэш-массивом или текстовой строкой;
var<n>
( var1, ... ) ( enumerator - так мы дальше будем ее называть ) хранится ссылка на элемент массива или строки (если строка передана по ссылке - см.пример ниже ) expr<n>,
таким образом, при присвоении ей какого-либо значения, мы изменяем соответствующий элемент массива или строку;Local c := "alles", s := "abcdefghijk" FOR EACH c IN @s IF c $ "aei" c := UPPER( c ) ENDIF NEXT ? s // AbcdEfghIjk ? c // alles- enumerator имеет свойства, к которым можно обращаться, используя ООП синтаксис:
expr<n>
;Еще один новый оператор - WITH OBJECT:
WITH OBJECT expression ... ENDWITHОна позволяет при разумном использовании упростить код:
// можно написать WITH OBJECT myobj:a[1]:myitem :message( 1 ) :value := 9 ENDWITH // вместо myobj:a[1]:myitem:message( 1 ) myobj:a[1]:myitem:value := 9
Ну и, наконец, SWITCH:
SWITCH expr CASE value [EXIT] ... OTHERWISE ENDSWITCHЭто выглядит примерно так:
SWITCH a CASE 1 ? "FOUND: 1" CASE "2" ? "FOUND: 2" OTHERWISE ? "other" ENDЭто C-подобный оператор, он похож на Клипперовские DO CASE ... ENDCASE и IF ... ELSEIF ... ELSE ... ENDIF, но не позволяет в качестве условия использовать выражения, зато благодаря особенностям реализации работает гораздо быстрее, так что его имеет смысл использовать в циклах.
В Harbour существенно расширены возможности использования макросов. Так, теперь в макросах ( и в кодоблоках ) можно использовать операторы ++, --, +=, -= и т.п., вложенные кодоблоки, выражения произвольной длины. Также допустимо использовать списки:
cMacro := "1, 150, 'Alexey Ivanov', 20, .T." { &cMacro } // массив SomeFun( &cMacro ) // Список аргументов cMacro := "1,10" SomeArray[ &cMacro ] // индексы массиваМакросы можно использовать для имен переменных объектов:
Procedure main() memvar var local o := errorNew(), msg := "cargo" private var := "CAR" o:&msg := "/cargo/" o:&( upper( msg ) ) += "/value/" ? o:&var.go
В Harbour реализованы расширенные кодоблоки. Такой кодоблок представляет собой многострочный фрагмент текста, в котором допустимы любые языковые элементы, в том числе Local и Static переменные и является, по сути, функцией, вложенной в функцию:
Function Main Local b1, b2 b1 := Fnc1( Seconds() ) ? Eval( b1 ) ? "Press any key" inkey(0) b2 := Fnc1( Seconds() ) ? Eval( b2 ) ? "Press any key" inkey(0) ? Eval( b1,Seconds() ) ? "Press any key" inkey(0) ? Eval( b1 ) ? Return Nil Function Fnc1( nSec ) Local bCode := {|p1| Local tmp := nSec IF p1 != Nil nSec := p1 ENDIF Return tmp } Return bCode
Этот пример не так прост, как кажется на первый взгляд, он демонстрирует интересную особенность реализации кодоблоков, которая существовала и в Клиппере, но в полной мере смогла раскрыться в этих расширенных кодоблоках. Обратите внимание на параметр nSec, который является локальной переменной функции Fnc1. Он внешний по отношению к кодоблоку bCode, но и после завершения работы Fnc1 продолжает "жить" вместе с bCode. На первый взгляд может показаться, что такая переменная - своеобразный аналог Static, но это не так - дело в том, что у каждого экземпляра кодоблока будет свой экземпляр этой переменной, инициализированный в процессе вызова и работы функции Fnc1. Это называется "замыкание" (closure) - см. статью в Википедии, элемент, хорошо известный по некоторым современным языкам программирования. В Javascript, например, он используется очень широко и считается одним из наиболее интересных и мощных его инструментов.
Элементы массива, хэш-массива и объекты в Harbour можно передавать по ссылке:
function main() Local arr := { 1,2,3 } ? arr[1], arr[2], arr[3] // 1 2 3 p( @arr[2] ) ? arr[1], arr[2], arr[3] // 1 12 3 return nil Function p( x ) x += 10 return nil
Функции с переменным количеством параметров. Harbour позволяет декларировать в объявлении функции и поименованные параметры, и затем неименованные. Эти неименованные параметры могут затем использоваться при помощи оператора "...":
// как элементы массива Procedure main() AEval( F( "1", "2", "A", "B", "C" ), {|x, i| qout( i, x ) } ) func f( p1, p2, ... ) ? "P1:", p1 ? "P2:", p2 ? "other parameters:", ... return { "X", ... , "Y", ... "Z" } // как индексы массива: Procedure main() local a := { { 1, 2 }, { 3, 4 }, 5 } ? aget( a, 1, 2 ), aget( a, 2, 1 ), aget( a, 3 ) func aget( aVal, ... ) return aVal[ ... ] // как параметры функции: Procedure main() info( "test1" ) info( "test2", 10, date(), .t. ) Procedure info( msg, ... ) qout( "[" + msg +"]: ", ... )Функция hb_aParams() возвращает список полученных параметров, и поименованных, и непоименованных.
// hb_arrayToParams() -> [ 1 ] [, [ N ] ] // Например: Procedure main( ... ) local aParams := hb_aParams(), n /* убираем параметры, начинающиеся с '--' */ n := 1 while n < len( aParams ) if left( aParams[ n ], 2 ) == "--" hb_adel( aParams, n, .t. ) else ++n endif enddo ? "Public parameters:", hb_arrayToParams( aParams ) return
Новый тип данных - TIMESTAMP, он определен и в RDD (т.е. может использоваться как тип поля в dbf), и в VM (виртуальной машине). Для переменной типа TIMESTAMP функция Valtype вернет "T", с ней можно производить операции сложения, вычитания и сравнения. Константы этого типа данных можно декларировать в программе следующим образом:
// { ^ [ YYYY-MM-DD [,] ] [ HH[:MM[:SS][.FFF]] [AM|PM] ] } // t"YYYY-MM-DD [H[H][:M[M][:S[S][.f[f[f[f]]]]]]] [PM|AM]" // t"YYYY-MM-DDT[H[H][:M[M][:S[S][.f[f[f[f]]]]]]] [PM|AM]" Procedure main() Local t1 := {^ 2012-11-28 10:22:30 } // или {^ 2012/11/28 10:22:30 } Local t2 := t"2009-03-21 17:31:45.437" // или t"2009-03-21T17:31:45.437" ? t1 + 1 // 11/29/12 10:22:30.000 ? t1 - t2 // 1347.701905Кстати, раз речь зашла об объявлении констант, в Harbour можно объявлять константы типа ДАТА вот так:
Local d1 := 0d20121201 // Шаблон: 0dYYYYMMDDА строковые константы можно объявлять, как в C, используя литерал e"...":
Local str1 := e"Helow\r\nWorld \x21\041\x21\000abcdefgh"Целые числа можно указывать в шестнадцатиричном формате:
Local n := 0x1F
Еще одно интересное новшество в Harbour - указатели на функции. Их можно создавать на
этапе сборки приложения, используя выражение @<funcName>()
, или динамически во время
исполнения с помощью макроподстановки: &("@<funcName>()")
. Valtype()
возвращает для таких указателей S.
Procedure main() local funcSym funcSym := @str() ? funcSym:name, "=>", funcSym:exec( 123.456, 10, 5 ) funcSym := &( "@upper()" ) ? funcSym:name, "=>", funcSym:exec( "Harbour" ) return
Реализация ООП ( объектно-ориентированного программирования ) - это, пожалуй, первое, что добавили разработчики Harbour к стандарту языка. Клиппер последних версий содержал элементы ООП. Там было несколько предопределенных классов, можно было создавать их объекты, но свои классы создавать было нельзя. Были, правда некоторые недокументированные возможности, которые позволяли это делать, на них были основаны несколько 3rd party библиотек, реализующих ООП для Клиппера, наиболее известные из них - это Fivewin (точнее, Objects.lib из комплекта Fivewin) и Class(y). В Harbour для использования классов не требуется подключать дополнительные библиотеки, ООП является здесь частью языка и, поскольку соответствующие средства составляют часть ядра, Harbour предоставляет большие возможности, чем Клиппер со всеми сторонними библиотеками.
Итак, чтобы создать новый класс, используется команда CLASS ... ENDCLASS:
[CREATE] CLASS <cClassName> [ FROM | INHERIT <cSuperClass1> [, ... ,<cSuperClassN>] ] [ MODULE FRIENDLY ] [ STATIC ] [ FUNCTION <cFuncName> ] [HIDDEN:] [ CLASSDATA | CLASSVAR | CLASS VAR <DataName1>] [ DATA | VAR <DataName1> [,<DataNameN>] [ AS <type> ] [ INIT <uValue> ] [[EXPORTED | VISIBLE] | [PROTECTED] | [HIDDEN]] [READONLY | RO] ] ... [ METHOD <MethodName>( [<params,...>] ) [CONSTRUCTOR] ] [ METHOD <MethodName>( [<params,...>] ) INLINE <Code,...> ] [ METHOD <MethodName>( [<params,...>] ) BLOCK <CodeBlock> ] [ METHOD <MethodName>( [<params,...>] ) EXTERN <funcName>([<args,...>]) ] [ METHOD <MethodName>( [<params,...>] ) SETGET ] [ METHOD <MethodName>( [<params,...>] ) VIRTUAL ] [ METHOD <MethodName>( [<params,...>] ) OPERATOR <op> ] [ ERROR HANDLER <MethodName>( [<params,...>] ) ] [ ON ERROR <MethodName>( [<params,...>] ) ] ... [PROTECTED:] ... [VISIBLE:] [EXPORTED:] ... [FRIEND CLASS <ClassName,...>] [FRIEND FUNCTION <FuncName,...>] [SYNC METHOD <cSyncMethod>] ENDCLASS [ LOCK | LOCKED ]
Класс может быть объявлен как наследник от одного или нескольких родителей
(особенности multiinheritance - множественного наследования мы рассмотрим позже)
при помощи служебного слова FROM или INHERIT. Это значит, что он
получает от родительского класса весь его набор переменных и методов (кроме помеченных как HIDDEN).
Любой из унаследованных методов может быть переопределен. При этом родительский
метод может быть вызван как Super:<methodName>()
или ::<cSuperClass>:<methodName>()
(если он не HIDDEN).
Отмечу еще одну интересную особенность, связанную с наследованием. Если в порожденном
классе есть переменная с тем же именем, что и в родительском, то каждый объект содержит
две переменных с этим именем. К одной из них следует обращаться obj:<varName>
,
к другой - obj:<cSuperClass>:<varName>
.
Класс может быть объявлен как STATIC - т.е. он не может использоваться вне текущего модуля.
Класс может быть объявлен как MODULE FRIENDLY - это значит, что все классы и функции, объявленные в текущем модуле, его друзья (friends), т.е. имеют доступ к его HIDDEN и PROTECTED переменным и методам. И раз уж мы заговорили о дружественных функциях, то обратите внимание на строчки FRIEND CLASS и FRIEND FUNCTION - они определяют дружественные для этого класса классы и функции.
Слова FUNCTION <cFuncName>
в объявлении класса определяют его как скалярный класс.
Предложения DATA или VAR - это определения переменных класса. При этом может быть
строго определен тип переменной AS <type>
(Character, Numeric, Date,
Logical, Codeblock, Nil), задано ее начальное значение по умолчанию INIT <uValue>
и определен Scope (Hidden,Protected,Exported,Readonly). Scope может быть
задан как для каждой переменной отдельно ( в объявлении DATA ), так и для
группы переменных и методов:
Переменные могут принадлежать объекту, а могут и всему классу - это переменные класса,
они объявляются как CLASSDATA, CLASSVAR или CLASS VAR. Такая переменная
доступна из любого объекта класса как obj:<DataName>
, но хранится она не в областях данных объектов,
а в одном месте, в области данных класса, и поэтому, естественно, имеет одно значение для всех объектов класса.
Есть одно отличие в реализации CLASSDATA с одной стороны, и CLASSVAR
или CLASS VAR с другой. Эти переменные могут быть объявлены как SHARED.
Это означает, что если в родительском классе есть такая переменная, то производные классы
используют ту же самую единственную ее копию, разделяют ее. Если же SHARED не
объявлен, то производные классы наследуют ее обычным образом, создавая новую копию
переменной для каждого класса-наследника. Так вот, для CLASSVAR и CLASS VAR
это работает, как было описано выше, а CLASSDATA переменные ведут себя как
SHARED независимо от того, используется это слово в объявлении или нет. Это было
сделано для обратной совместимости с Fivewin программами для Клиппера.
Предложения METHOD - это определения методов класса. В общем случае, если метод не INLINE, не BLOCK, и не VIRTUAL,после определения класса (после ENDCLASS) необходимо поместить реализацию метода:
METHODМетод должен возвращать ссылку на объект ( Self ), если он используется как конструктор.( [ ] ) CLASS ... RETURN Self
Ниже описаны разные способы объявления метода:
- INLINE позволяет поместить реализацию метода в той же строке, где он объявлен. Такой способ может применяться, когда соответствующий код достаточно невелик, это делает объявление метода более простым и наглядным.EXTERN <funcName>([<args,...>])
применяется, если внешняя функция
<funcName>() реализует то, что нужно этому методу. obj:<methodName> := <value>
.OPERATOR <op>
используется для перегрузки оператора <op>
( "+", "-", ... ) - т.е. когда в программе над объектом
совершается действие, обозначаемое <op>, это действие реализуется методом,
который указан со словом OPERATOR. В качестве параметра методу передается второй
объект, участвующий в операции <op>.Еще один способ объявления метода - задать его с помощью ERROR HANDLER или ON ERROR. Этот метод вызывается в том случае, если объекту класса, к которому он принадлежит, послано сообщение, не определенное для этого класса; иными словами, вызван метод или переменная, которой нет в этом классе. Можно, конечно, использовать такой метод по прямому назначению - для обработки ошибок, но есть и более интересные способы применения. Так, с его помощью можно динамически имитировать наличие методов и переменных объекта, которые не были определены при объявлении класса, причем у разных объектов одного и того же класса могут быть "сымитированы" разные методы и переменные.
SYNC METHOD применяется для синхронизации выполнения методов в многопотоковом приложении. Если метод помечен как SYNC, то он не будет выполняться двумя и более потоками одновременно для одного и того же объекта.
Опция LOCK или LOCKED предложения ENDCLASS предотвращает последующую модификацию этого класса, ее можно использовать при разработке больших проектов группой разработчиков, если вы не хотите, чтобы кто-то "хакнул ваш класс".
Для создания и проверки работы простейшего класса достаточно, например, написать:
#include "hbclass.ch" FUNCTION Main Local obj := myFirstClass():New(3) ? obj:x // 3 ? RETURN Nil CLASS myFirstClass VAR x METHOD New( n ) INLINE ( ::x := n, Self ) ENDCLASS
Я воспользовался здесь объявлением INLINE (встроенного) метода, чтобы обойтись только конструкцией CLASS ... ENDCLASS и не описывать метод New(n) отдельно. Здесь следует обратить внимание на следующие моменты:
- в prg, содержащий объявления классов, необходимо включить hbclass.ch;<имя_класса>():<метод>()
( obj := myFirstClass():New(3)
).
Теперь пример чуть посложнее:
#include "hbclass.ch" FUNCTION Main Local obj1, obj2 obj1 := mySecondClass():New( 10,"Alex" ) ? "Всего объектов mySecondClass:", mySecondClass():nKolObj obj2 := mySecondClass():New( 11,"Mike" ) ? "Всего объектов mySecondClass:", mySecondClass():nKolObj ? obj1:x, obj2:x ? RETURN Nil CLASS myFirstClass VAR x METHOD New( n ) INLINE ( ::x := n, Self ) ENDCLASS CLASS mySecondClass FROM myFirstClass HIDDEN: VAR cStringS EXPORTED: CLASS VAR nKolObj INIT 0 VAR cString1 INIT "Sample" METHOD New( n, s ) ENDCLASS METHOD New( n, s ) CLASS mySecondClass Super:New( n ) ::nKolObj ++ ::cStringS := s RETURN SelfЗдесь появились вот такие новые моменты:
METHOD <methodName> CLASS <className>
;Super:New(n)
для вызова одноименного метода из родительского класса для инициализации переменных объекта, унаследованных от родителя;CLASS VAR nKolObj
- переменная, принадлежащая не объекту, а всему классу, здесь она используется как счетчик объектов этого класса;Еще один пример, демонстрирующий объявления методов как BLOCK и EXTERN:
#include "hbclass.ch" function Main() Local o1 := HSamp1():New() Local o2 := HSamp2():New() ? o1:x, o2:x // 10 20 ? o1:Calc( 2,3 ), o2:Mul( 2,3 ) // 60 120 ? o1:Sum( 5 ) // 15 ? return nil CLASS HSamp1 DATA x INIT 10 METHOD New() INLINE Self METHOD Sum( y ) BLOCK {|Self,y|::x += y} METHOD Calc( y,z ) EXTERN fr1( y,z ) ENDCLASS CLASS HSamp2 DATA x INIT 20 METHOD New() INLINE Self METHOD Mul( y,z ) EXTERN fr1( y,z ) ENDCLASS Function fr1( y, z ) Local o := QSelf() Return o:x * y * zОбратите внимание на вызов функции QSelf(), она вовращает ссылку на объект, в контексте которого она находится, т.е., то, что в методах класса называется Self.
А вот пример использования ERROR HANDLER:
#include "hbclass.ch" FUNCTION Main LOCAL oTable oTable := Table():Create( "sample.dbf", { {"F1","C",10,0}, {"F2","N",8,0} } ) oTable:AppendRec() oTable:F1 := "FirstRec" oTable:F2 := 1 oTable:AppendRec() ? oTable:nRec // 2 oTable:nRec := 1 ? oTable:nRec // 1 ? oTable:F1, oTable:F2 // "FirstRec" 1 ? RETURN nil CLASS Table METHOD Create( cName, aStru ) METHOD AppendRec() INLINE dbAppend() METHOD nRec( n ) SETGET ERROR HANDLER OnError( xParam ) ENDCLASS METHOD Create( cName, aStru ) CLASS Table dbCreate( cName, aStru ) USE (cName) NEW EXCLUSIVE RETURN Self METHOD nRec( n ) CLASS Table IF n != Nil dbGoTo( n ) ENDIF RETURN Recno() METHOD OnError( xParam ) CLASS Table Local cMsg := __GetMessage(), cFieldName, nPos Local xValue IF Left( cMsg, 1 ) == '_' cFieldName := Substr( cMsg,2 ) ELSE cFieldName := cMsg ENDIF IF ( nPos := FieldPos( cFieldName ) ) == 0 Alert( cFieldName + " wrong field name!" ) ELSEIF cFieldName == cMsg RETURN FieldGet( nPos ) ELSE FieldPut( nPos, xParam ) ENDIF Return NilЗдесь демонстрируется предельно упрощенный класс Table, каждый объект которого oTable должен соответствовать вновь созданной или открытой ( метод Open здесь опущен ) таблице (dbf файлу). Мы хотим обращаться к полям этой таблицы как к свойствам (переменным) объекта oTable. Но мы не можем ввести эти имена полей в объявление класса, т.к. они, вообще говоря, неизвестны на момент написания класса, и они просто-напросто разные для разных таблиц. Здесь нам поможет ERROR HANDLER. Когда происходит обращение к несуществующей переменной ( или методу ) класса, генерируется ошибка и вызывается метод, определенный как ERROR HANDLER ( или ON ERROR ). Из этого метода при помощи функции __GetMessage() можно получить то самое несуществующее имя переменной, к которой было обращение. Причем, если была попытка записать что-то в эту переменную, то полученное имя предваряется символом "_" и методу передается записываемое значение как первый параметр. Все остальное, думаю, ясно из примера.
oTable:nRec := 1
), то методу передается устанавливаемое значение
как параметр и он производит перемещение по таблице, если же читаем
( ? oTable:nRec
), то просто возвращает результат Recno().
Далее следует список функций для манипуляции классами и объектами:
lExist := __objHasData( oObject, cName ) | Возвращает логическое значение, указывающее, есть ли у объекта oObject переменная с именем cName |
lExist := __objHasMethod( oObject, cName ) | Возвращает логическое значение, указывающее, есть ли у объекта oObject метод с именем cName |
aNames := __objGetMsgList( oObject, [lData], [nClassType] ) | Возвращает массив
имен всех переменных или методов объекта oObject; lData
определяет, что требуется, методы ( .F. ), или переменные ( .T., значение
по умолчанию ); nClassType определяет, какие переменные должны попасть в список.
Возможны 3 значения, определенные в hboo.ch:HB_MSGLISTALL 0 все переменные HB_MSGLISTCLASS 1 переменные класса CLASS DATA HB_MSGLISTPURE 2 переменные объекта DATA |
aNames := __objGetMethodList( oObject ) | Возвращает массив имен всех методов объекта oObject |
aData := __objGetValueList( oObject, [aExcept] ) | Возвращает двумерный массив всех переменных объекта oObject ( имя и значение ); aExcept - массив имен переменных, которые не надо включать в результат. |
oObject := __ObjSetValueList( oObject, aData ) | Устанавливает значения переменных объекта oObject, aData - двумерный массив имя / значение |
oObject := __objAddMethod( oObject, сMethodName, nFuncPtr ) | Добавляет метод в
уже существующий класс, к которому принадлежит объект oObject;
сMethodName - имя метода, nFuncPtr - указатель на функцию, реализующую
этот метод ( про указатели на на функции см. в подразделе 3.2:oHappy := HBClass():New( "THappy" ) __objAddMethod( oHappy, "Smile", @MySmile() ) ? oHappy:Smile( 1 ) Static Function MySmile( nType ) Return Iif( nType==0,":)",":):)" ) |
oObject := __objAddInline( oObject, cInlineName, bInline ) | Добавляет inline метод в уже существующий класс, к которому принадлежит объект oObject; сInlineName - имя метода, bInline - кодоблок, реализующий этот метод. |
oObject := __objAddData( oObject, cDataName ) | Добавляет переменную в уже существующий класс, к которому принадлежит объект oObject; cDataName - имя новой переменной |
oObject := __objModMethod( oObject, сMethodName, nFuncPtr ) | Заменяет реализацию метода в уже существующем классе, к которому принадлежит объект oObject; сMethodName - имя метода, nFuncPtr - указатель на функцию, реализующую этот метод ( см. описание функции __objAddMethod() ) |
oObject := __objAddInline( oObject, cInlineName, bInline ) | Заменяет реализацию inline метода в уже существующем классе, к которому принадлежит объект oObject; сInlineName - имя метода, bInline - кодоблок, реализующий этот метод. ( см. описание функции __objAddInline() ) |
oObject := __objDelMethod( oObject, сMethodName ) | Удаляет метод, или inline метод с именем сMethodName из класса, к которому принадлежит объект oObject |
oObject := __objDelInline( oObject, сMethodName ) | Удаляет метод, или inline метод с именем сMethodName из класса, к которому принадлежит объект oObject |
oObject := __objDelData( oObject, сDataName ) | Удаляет переменную с именем сDataName из класса, к которому принадлежит объект oObject |
lIsParent := __objDerivedFrom( oObject, xSuper ) | Возвращает логическое значение, указывающее, является ли класс xSuper (который может быть задан как объект или имя класса ), родителем для класса, к которому принадлежит объект oObject |
oNew := __objClone( oSource ) | Клонирует объект. |
xResult := __objSendMsg( oObject, cName [,xParams...] ) | Посылает сообщение объекту oObject: может использоваться для получения значения переменной объекта cName или для присвоения ей нового значения ( в этом случае перед ее именем надо поставить префикс "_", а также для вызова метода объекта. |
Эта подсистема предназначена для вывода аварийных сообщений и результата
функций CMonth(), CDow() - но, в отличие от аналогичных средств в Клиппере, она позволяет
менять язык программы во время исполнения.
Чтобы подключить ее к вашему приложению, надо указать в link - скрипте библиотеку hblang.lib
и включить в главный prg файл предложения REQUEST с именами тех языков, которые
вы предполагаете использовать, например:
REQUEST HB_LANG_RU866 REQUEST HB_LANG_RUWIN
Hb_LangSelect( cLangID ) --> cLangID | Устанавливает язык программы, возвращает ID предыдущего языка. |
Hb_LangName() --> cLangID | Возвращает название языка программы. |
Hb_LangErrMsg( nError ) --> cError | Возвращает текст ошибки по номеру. |
Hb_LangMessage( nMessage ) --> cMessage | Возвращает текст аварийного сообщения по номеру. |
Эта подсистема обеспечивает поддержку национальных кодовых страниц для ваших данных. Чтобы подключить ее к вашему приложению, надо указать в link - скрипте библиотеку hbcpage.lib и включить в главный prg файл предложения REQUEST с именами тех кодовых страниц, которые вы предполагаете использовать, например:
REQUEST HB_CODEPAGE_RU866 REQUEST HB_CODEPAGE_RUKOI8 REQUEST HB_CODEPAGE_RU1251
Далее, следует определить главную кодовую страницу приложения. Именно эта кодовая страница будет
использоваться для функций IsUpper(), IsLower(), IsAlpha(), Upper(), Lower(), Transform() и при сравнении строк. В консольном
приложении, по-видимому, следует использовать "RU866", а в Windows GUI приложении - "RU1251".
Для установки главной кодовой страницы используется функция
hb_cdpSelect( sCodepage ) например: hb_cdpSelect( "RU866" )
Подобная возможность есть и в Клиппере ( путем подключения специальных obj ), но в Харборе, кроме того, можно определить кодовую страницу для любого открываемого файла данных - при этом все строки будут автоматически транслироваться в главную кодовую страницу приложения при чтении и обратно - при записи, транслироваться будут и строки в dbSeek(), dbLocate(), dbSetFilter(). Достаточно указать кодовую страницу при открытии файла - и вся последующая работа с ним будет выглядеть так, как будто данные находятся в основной кодовой странице:
USE file_name ... CODEPAGE "RU1251" или dbUseArea( .T.,,file_name,,.T.,.F.,"RU1251" )
По умолчанию файл открывается с главной кодовой страницей приложения.
Эту возможность ( определение кодовой страницы файла ) следует использовать только с
родными RDD - DBFNTX и DBFCDX.
ADS RDD предоставляет для этих целей другие средства ( настройка ini файла или SET CHARTYPE TO OEM/ANSI ).
Codepage API включает также функцию для трансляции строк из одной кодовой страницы в другую:
Hb_Translate( sData, sCodepageIN, sCodepageOUT ) например: Hb_Translate( "Привет", "RU1251", "RU866" )
и набор функций для utf8:
hb_StrToUtf8( sData, sCodepageIN ) | Трансляция sData из sCodepageIN в utf8 |
hb_Utf8ToStr( sData, sCodepageOUT ) | Трансляция sData из utf8 в sCodepageOUT |
hb_Utf8Len( sData ) | Эта и последующие функции - аналоги |
hb_utf8Chr( n ) | стандартных строковых функций Len(), |
hb_utf8Asc( sData ) | Chr(), Asc() и т.д., но для строк в |
hb_utf8Substr( sData, n1, n2 ) | кодировке utf8. Как известно, в utf8 |
hb_utf8Left( sData, n1 ) | один символ может кодироваться больше |
hb_utf8Right( sData, n1 ) | чем одним байтом. Эти функции оперируют |
hb_utf8Stuff( sData, n1, n2, cNew ) | количеством символов, а не байт. |
hb_utf8Peek( sData, n1 ) | |
hb_utf8Poke( sData, n1, n ) |
Я уже отмечал здесь, что Харбор может вместо C файла создать
из вашего prg особый тип файла с расширением hrb, который содержит p-code вашей программы и
может быть исполнен утилитой Hbrun.exe.
Но этот hrb может быть вызван на исполнение и непосредственно из вашей программы - для
этого предусмотрены специальные функции:
hb_hrbRun( hrbName, ... (параметры)) | Исполняет hrb файл с именем hrbname, передает ему список параметров и возвращает результат. |
handle := hb_hrbLoad( [ nOptions, ] hrbName [, xparams,... ] ) | Загружает в память p-code из hrb файла с именем hrbname, возвращает указатель на эти инструкции;
xparams - параметры, которые передаются INIT PROCEDURE этого hrb-модуля; nOptions - необязательный параметр (если его нет, первым параметром будет hrbname), который может иметь следующие значения (константы определены в hbhrb.ch): HB_HRB_BIND_DEFAULT 0x0 /* Не переопределяет никакие функции, игнорирует PUBLIC функцию в загружаемом модуле, если функция с таким именем уже определена */ HB_HRB_BIND_LOCAL 0x1 /* Не переопределяет никакие функции, но оставляет локальные ссылки, т.е., если модуль содержит функцию FOO и такая функция уже определена, то в HRB она конвертируется в STATIC */ HB_HRB_BIND_OVERLOAD 0x2 /* Переопределяет уже существующие PUBLIC функции */ HB_HRB_BIND_FORCELOCAL 0x3 /* Конвертирует все PUBLIC функции в STATIC */ HB_HRB_BIND_LAZY 0x4 /* Не проверяет ссылки, позволяет загружать HRB с неразрешенными к моменту загрузки ссылками */ |
hb_hrbDo( handle, ... (параметры)) | Исполняет p-code из hrb файла, предварительно загруженного с помощью hb_hrbLoad(), на которые указывает handle, передает им список параметров и возвращает результат. |
hb_hrbUnload( handle ) | Выгружает p-code, на который указывает handle, из памяти. |
hb_hrbGetFunsym( handle, functionName ) | Получает указатель
на функцию с именем functionName, определенную в hrb файле; предварительно надо
загрузить этот hrb в память с помощью handle := hb_HrbLoad( hrbName ) .
Выполнить эту функцию можно при помощи Do( hFun, ... (параметры)) , где hFun - указатель
полученный от hb_hrbGetFunsym.
|
aFuncs := hb_hrbGetFunList( handle, nType ) | Возвращает список функций в hrb-модуле, nType определяет тип функций (константы определены в hbhrb.ch):
HB_HRB_FUNC_PUBLIC 0x1 /* Локально определенные PUBLIC функции */ HB_HRB_FUNC_STATIC 0x2 /* Локально определенные STATIC функции */ HB_HRB_FUNC_LOCAL 0x3 /* Локально определенные функции */ HB_HRB_FUNC_EXTERN 0x4 /* Внешние функции, вызываемые в HRB модуле */ |
Необходимо проследить, чтобы приложение было собрано со всеми функциями, которые могут вызываться из .hrb, иначе во время исполнения программа вылетит. Лучше всего это сделать, поставив в вашей программе
REQUEST <имя_функции>Можно включить #include "hbextern.ch" - в нем собраны REQUEST на все Харборовские функции. Размер программы в результате, конечно, увеличится, зато у вас будет гарантия, что все функции включены.
Обратите внимание, что параметр hrbname функций hb_hrbRun() и hb_hrbload() может содержать не только имя hrb файла, но и сам p-code, предварительно загруженный с помощью, например, Memoread(), или полученный в результате компиляции с помощью hb_compileBuf() или hb_compileFromBuf() (см. описание этих функций здесь):
FUNCTION Main() Local han, buf, cBuf := "" cBuf += "Procedure MM" +Chr(13)+Chr(10) cBuf += "? 'Just a test!'" +Chr(13)+Chr(10) buf := hb_compileFromBuf( cBuf, "harbour", "/n" ) hb_hrbRun( buf ) ? Return Nil
И еще один интересный момент. Я уже отмечал, что hrb файлы очень похожи по функциональности на p-code dll. И действительно, функция hb_hrbLoad() загружает p-code в пространство вашего приложения так же, как функция hb_libLoad() подгружает динамическую библиотеку. А значит, функции из hrb файла можно вызывать таким же образом, т.е. напрямую, без всяких hb_hrbGetFunsym() и Do(). Для этого, как и в случае с использованием p-code dll, надо предварительно объявить эти функции в вашем приложении как DYNAMIC:
DYNAMIC HRBFUNC1 FUNCTION Main() Local x, handle := hb_hrbLoad( "my.hrb" ) x := hrbFunc1() // hrbFunc1 - функция из my.hrb hb_hrbUnload( handle ) Return Nil
В Харборе есть возможность вставлять фрагменты C кода в prg файл. Это может быть
удобно, если вам лень из-за пары нужных C функций создавать .с файл и вставлять его в проект.
Делается это при помощи директив #pragma BEGINDUMP ... #pragma ENDDUMP
.
#pragma BEGINDUMP #include < extend.h > #include < math.h > HB_FUNC( SIN ) { double x = hb_parnd(1); hb_retnd( sin( x ) ); } #pragma ENDDUMP
Хэш массивы ( или Хэш-таблицы - см. статью в Википедии - это одна из реализаций структуры данных, известной под названием ассоциативные массивы, которая хранит пары ( ключ,значение ) и позволяет выполнять, как минимум, три операции: добавление новой пары, поиск по ключу и удаление по ключу. Поддержка ассоциативных массивов есть во многих интерпретируемых языках программирования высокого уровня, например, в Perl, PHP, Python, Ruby, и др. Теперь она реализована и в Harbour.
Создается новый хэш-массив функцией hb_hash(), ее можно использовать без параметров ( в этом случае инициализируется пустой хэш-массив ) и с параметрами, определяющими произвольное количество пар массива:
FUNCTION Main local harr := hb_Hash( "six", 6, "eight", 8, "eleven", 11 ) harr[10] := "str1" harr[23] := "str2" harr["fantasy"] := "fiction" ? harr[10], harr[23] // str1 str2 ? harr["eight"], harr["eleven"], harr["fantasy"] // 8 11 fiction ? len(harr) // 6 ? RETURN nilАльтернативный способ инициализации хэш-массива:
local harr := hb_Hash( "six" => 6, "eight" => 8, "eleven" => 11 )
Для хэш массива определены 4 флага:
- Autoadd - когда установлен в истину (по умолчанию), операции присвоения ( какharr["fantasy"] := "fiction"
в вышеприведенном примере )
приводят к добавлению новой пары, когда в ложь - к ошибке;Ниже приведена таблица функций для хэш массивов.
aHash := hb_hash( [ Key1, Value1 ], ..., [ KeyN, ValueN ] ) | Создание, инициализация хэш массива |
lExists := hb_hHaskey( aHash, Key ) | Возвращает логическое значение, указывающее, есть ли пара с ключом Key в массиве aHash |
xValue := hb_hGet( aHash, Key ) | Возвращает значение пары с ключом
Key в массиве aHash - то же самое, что xValue := aHash[Key]
|
xValue := hb_hGetDef( aHash, Key, DefaultVal ) | Возвращает значение пары с ключом Key в массиве aHash или DefaultVal, если ключ не найден |
hb_hSet( aHash, Key, xValue ) | Устанавливает значение пары с ключом
Key в массиве aHash - то же самое, что aHash[Key] := xValue
|
hb_hDel( aHash, Key ) | Удаляет пару с ключом Key из массива aHash |
nPosition := hb_hPos( aHash, Key ) | Возвращает индекс пары с ключом Key в массиве aHash |
Key := hb_hKeyAt( aHash, nPosition ) | Возвращает ключ пары в массиве aHash с индексом nPosition |
xValue := hb_hValueAt( aHash, nPosition, [NewValue] ) | Возвращает значение пары в массиве aHash с индексом nPosition и устанавливает новое NewValue, если оно задано |
array := hb_hPairAt( aHash, nPosition ) | Возвращает двумерный массив ключ/значение из пары в массиве aHash с индексом nPosition |
hb_hDelAt( aHash, nPosition ) | Удаляет пару из массива aHash с индексом nPosition |
aKeys := hb_hKeys( aHash ) | Возвращает массив всех ключей массива aHash |
aValues := hb_hValues( aHash ) | Возвращает массив всех значений массива aHash |
hb_hFill( aHash, xValue ) | Заполняет массив aHash значениями xValue |
aHash2 := hb_hClone( aHash ) | Возвращает копию массива aHash |
aHash2 := hb_hCopy( aHash2, aHash, [nStart], [nCount] ) | Копирует пары из массива aHash в aHash2. Можно указать nStart - стартовая позиция, с которой копировать и nCount - сколько пар копировать |
aHash2 := hb_hMerge( aHash2, aHash, bBlock | nPosition ) | добавляет пары из массива aHash в aHash2. bBlock - кодоблок, исполняемый для каждой пары источника, ему передаются ключ, значение и индекс. Если bBlock возвращает истину, пара копируется. nPosition - индекс пары, которая будет добавлена в aHash. |
aHash := hb_hEval( aHash, bBlock, [nStart], [nCount] ) | Выполняет кодоблок для каждой пары массива aHash, кодоблоку передаются ключ, значение и индекс. |
nPosition := hb_hScan( aHash, xValue, [nStart], [nCount], [lExact] ) | Ищет значение xValue в массиве aHash |
aHash2 := hb_hSort( aHash ) | Сортирует массив aHash |
lPrevFlag := hb_hCaseMatch( aHash, [lFlag] ) | Устанавливает флаг "case match" для массива aHash и возвращает его предыдущее значение. |
aHash := hb_hSetCaseMatch( aHash, [lFlag] ) | Устанавливает флаг "case match" для массива aHash |
lPrevFlag := hb_hBinary( aHash, [lFlag] ) | Устанавливает флаг "binary" для массива aHash и возвращает его предыдущее значение. |
aHash := hb_hSetBinary( aHash, [lFlag] ) | Устанавливает флаг "binary" для массива aHash |
nPrevFlag := hb_hAutoAdd( aHash, [lFlag] ) | Устанавливает флаг "auto add" для массива aHash и возвращает его предыдущее значение. |
aHash := hb_hSetAutoAdd( aHash, [lFlag] ) | Устанавливает флаг "auto add" для массива aHash |
nPrevFlag := hb_hKeepOrder( aHash, [lFlag] ) | Устанавливает флаг "keep order" для массива aHash и возвращает его предыдущее значение. |
aHash := hb_hSetOrder( aHash, [lFlag] ) | Устанавливает флаг "keep order" для массива aHash |
hb_hAllocate( aHash, nItems ) | Резервирует место для массива aHash в количестве nItems пар. |
xPrevDef := hb_hDefault( aHash, DefaultValue ) | Устанавливает значение по умолчанию для массива aHash и возвращает предыдущее. |
Многие думают ( и я так думал когда-то ), что регулярные выражения - это строки для поиска, где "*" обозначает несколько любых символов, а "?" - один любой символ, типа того что используются как маски для файлов. На самом деле это своеобразный язык, достаточно сложный - поначалу поисковые строки на нем производят шокирующее впечатление. Но по мере освоения и понимания логики этого языка постепенно привыкаешь к его виду и он перестает казаться неудобочитаемой абракадаброй.
Регулярные выражения - это очень мощное средство для поиска в тексте. Многие современные языки программирования ( Perl, Php, Python, Ruby, Javascript и др. ) имеют их встроенную поддержку. Детально познакомиться с регулярными выражениями можно, например, на pcre.ru.
Далее следует группа сходных по составу параметров функций. Здесь cRegEx - строка с регулярным выражением
( текст или уже откомпилированное ); cString - строка, в которой производится поиск;
lCase указывает, следует ли учитывать регистр символов ( по умолчанию - следует - .T. );
что означает параметр lNewLine, мне пока не удалось выяснить, если кто подскажет - буду рад;
nMaxMatches - максимальное количество соответствий, которые надо возвращать (по умолчанию - без ограничения - 0).
Функции ищут в строке cString фрагменты, соответствующие регулярному выражению,
заданному cRegEx. Регулярное выражение может состоять из частей, разделенных
круглыми скобками. В этом случае фрагмент строки, соответствующий всему выражению, мы
далее называем полным совпадением, а подстроку этого фрагмента, соответствующую части
регулярного выражения (выделенной круглыми скобками), мы будем называть суб-совпадением.
Так, например:
s := "Claabrok abbey" // Строка, где будем искать cRegex := "(a+)(b+)" // Символ '+' в регулярном выражении означает, что // предшествующий символ встречается 1 или более раз // Имеем 2 полных совпадения: // 1) 'aab', здесь 'aa' - первое суб-совпадение, 'b' - второе // 2) 'abb', здесь 'a' - первое суб-совпадение, 'bb' - второе
| Возвращает одномерный массив, включающий первое полное совпадение и все его суб-совпадения (только сами подстроки). |
lResult := hb_RegexLike( cRegEx, cString, [lCase], [lNewLine] ) | Возвращает истину, если строка cString соответствует выражению cRegEx. |
lResult := hb_RegexHas( cRegEx, cString, [lCase], [lNewLine] ) | Возвращает истину, если в строке cString найдено хотя бы одно соответствие выражению cRegEx. |
aResult := hb_RegexSplit( cRegEx, cString, [lCase], [lNewLine], [nMaxMatches] ) | Функция ищет совпадения в cString, исключает найденные подстроки и возвращает массив из оставшихся частей. Она разделяет строку на части, причем разделителем служат соответствующие cRegEx подстроки. |
aResult := hb_RegexAtx( cRegEx, cString, [lCase], [lNewLine] ) | Возвращает двумерный массив, включающий первое полное совпадение и все его суб-совпадения, для каждого - массив с соответсвующей подстрокой, начальной и конечной позициями. |
aResult := hb_RegexAll( cRegex, cString, [lCase], [lNewLine], [nMaxMatches], [nGetMatch], [lOnlyMatch] ) | Здесь первые пять параметров были описаны выше, nGetMatch определяет характер возвращаемого массива, если 0, то функция возвращает и полные совпадения и суб-совпадения, если 1 - только полные совпадения, если 2 - первые суб-совпадения, 3 - вторые суб-совпадения и т.д.; lOnlyMatch - если .T.(по умолчанию), в возвращаемы массив включаются только подстроки, если - .F., то еще и начальная и конечная позиции найденных подстрок. В зависимости от сочетания этих двух параметров возвращаемый массив может быть одномерным, двухмерным или трехмерным. Так, если nGetMatch не равен 0 и lOnlyMatch - истина, то возвращается одномерный массив из подстрок ( полных совпадений или суб-совпадений ). Если nGetMatch равен 0 и lOnlyMatch - истина, то возвращается двумерный массив, каждый элемент которого - массив с подстрокой - полным совпадением и подстроками - суб-совпадениями ). Если же nGetMatch равен 0 и lOnlyMatch - ложь, то возвращается трехмерный массив - вместо каждой подстроки предыдущего случая появляется массив, включающий подстроку, начальную и конечную ее позицию. |
И еще 3 функции:
pRegEx := hb_RegexComp( cRegEx, [lCase], [lNewLine] ) | Компилируется cRegEx - строка с регулярным выражением, возвращается откомпилированный код; lCase указывает, следует ли учитывать регистр символов ( по умолчанию - следует - .T. ). Это имеет смысл делать для ускорения операций поиска, если данное выражение будет использоваться больше одного раза. |
lGood := hb_isRegex( cRegEx ) | Возвращает истину, если cRegEx - скомпилированное регулярное выражение. |
cMatch := hb_Atx( cRegEx, cString, [lCase], [@nStart], [@nEnd] ) | Осуществляет поиск в строке cString соответствия выражению cRegEx; lCase указывает, следует ли учитывать регистр символов ( по умолчанию - следует - .T. ); nStart, nEnd - соответственно, с какой позиции начинать поиск и на какой заканчивать. Возвращает первую найденную подстроку ( или Nil, если ничего не найдено ). В nStart, если он передан по ссылке, записывается позиция найденной подстроки, в nEnd - ее длина. |
И, наконец, пример использования простейшего регулярного выражения, уже рассмотренного выше:
FUNCTION Main() Local s := "ClaAbrok aBbey", n1, n2 ? hb_isregex( hb_regexcomp( "a+b+" ) ) // .T. n1 := 1 n2 := Len( s ) ? hb_atx( "a+b+", s, .f., @n1, @n2 ), n1, n2 // aAb 3 3 ? hb_regexhas( "a+b+", s, .f. ) // .T. ? hb_regexhas( "a+b+", s ) // .F. ? hb_regexlike( "a+b+", s, .f. ) // .F. PrintArray( hb_regex( "a+b+",s,.f. ) ) // { aAb } PrintArray( hb_regex( "(a+)(b+)",s,.f. ) ) // { aAb aA b } PrintArray( hb_regexSplit( "a+b+",s,.f. ) ) // { Cl rok ey } PrintArray( hb_regexAtx( "a+b+",s,.f. ) ) // { { aAb 3 5 } } PrintArray( hb_regexAtx( "(a+)(b+)",s,.f. ) ) // { { aAb 3 5 } { aA 3 4 } { b 5 5 } } PrintArray( hb_regexAll( "a+b+",s,.f. ) ) // { { aAb aBb } } PrintArray( hb_regexAll( "(a+)(b+)",s,.f. ) ) // { { aAb aA b } { aBb a Bb } } PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,1 ) ) // { aAb aBb } PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,2 ) ) // { aA a } PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,0,.f. ) ) // { { { aAb 3 5 } { aA 3 4 } { b 5 5 } } { { aBb 10 12 } { a 10 10 } { Bb 11 12 } } } PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,1,.f. ) ) // { { aAb 3 5 } { aBb 10 12 } } ? Return Nil Function PrintArray( arr, lNoNewLine ) Local i IF lNoNewLine == Nil .OR. !lNoNewLine ? ENDIF ?? " {" FOR i := 1 TO Len( arr ) IF Valtype( arr[i] ) == "A" PrintArray( arr[i], .T. ) ELSE ?? " " + Iif( Valtype( arr[i] ) == "N", Ltrim(Str(arr[i])), arr[i] ) ENDIF NEXT ?? " }" Return Nil
Это базовый набор функций, предназначенный для реализации работы в сети по IP протоколу. С их помощью можно создать сокет, установить соединение с другим сокетом ( на этом же или удаленном компьютере ) и организовать прием/передачу данных. Это именно базовый набор, реализующий TCP/IP и UDP протокол. Используя его, можно реализовать обмен по протоколу более высокого уровня - HTTP, FTP, POP3, и т.д. Одним из примеров такой реализации является библиотека hbtip, исходники которой находятся в harbour/contrib/hbtip.
lResult := hb_inetInit() | Инициализирует INET подсистему, возвращает .T. в случае успеха. Должна вызываться перед вызовами других INET функций ( в начале программы, например ). |
hb_inetCleanup() | Освобождает ресурсы, занятые INET подсистемой. Ее надо вызывать в конце программы, использующей INET функции. |
hSocket := hb_inetCreate( [nTimeOut] ) | Создает
и возвращает хэндл сокета ( hSocket ) для последующего подключения к ресурсам в сети;
nTimeOut - значение таймаута для сокета в миллисекундах. Таймаут устанавливается для блокирующих
операций, таких как чтение и запись данных, т.е. тех, которые останавливают
программу, переводят ее в режим ожидания завершения текущей операции. Если время
ожидания превышает установленное значение таймаута, операция немедленно завершается
и hb_inetErrorCode(hSocket) возвращает -1. Обратите внимание, что речь идет именно
о единичной блокирующей операции, а не о функции чтения/записи hb_inet... Некоторые
функции, например, hb_inetRecvAll(), могут вызывать такие операции несколько раз,
поэтому их время выполнения может превысить установленный таймаут. По умолчанию значение таймаута равно -1, т.е. ограничение не установлено. |
nResult := hb_inetClose( hSocket ) | Закрывает ранее созданный сокет с хэндлом hSocket и соответствующее соединение. Возвращает 0 при успехе или -1 в случае ошибки. Если у вас есть другие потоки, которые используют этот сокет, их ожидание завершается и им возвращается ошибка. Эта функция не разрушает сокет, так что другие потоки могут обращаться к нему, чтобы проверить, не закрыт ли он ( и, если да, то завершить соответствующие операции ). |
fd := hb_inetFD( hSocket, [l] ) | |
nResult := hb_inetDestroy( hSocket ) | Закрывает и разрушает сокет, после вызова этой функции сокет уже не может использоваться. Возвращает 0 при успехе или -1 в случае ошибки. |
nResult := hb_inetStatus( hSocket ) | Возвращает 1, если сокет существует, или -1 - в обратном случае. |
cResult := hb_inetCRLF() | Возвращает последовательность CRLF ( возврат строки + новая строка ), используемую во многих протоколах. |
lResult := hb_inetIsSocket( hSocket ) | Возвращает .T., если переданный параметр - хэндл сокета. |
nMillis := hb_inetTimeout( hSocket [,nMillis] ) | Устанавливает новое, если задан второй параметр nMillis, и возвращает старое значение таймаута для сокета hSocket. Подробнее о таймауте см. в описании hb_inetCreate(). |
hb_inetClearTimeout( hSocket ) | Очищает ( устанавливает в -1 ) значение таймаута для сокета hSocket. Подробнее о таймауте см. в описании hb_inetCreate(). |
nMillis := hb_inetTimeLimit( hSocket [,nMillis] ) | Устанавливает новое, если задан второй параметр nMillis, и возвращает старое значение TimeLimit для сокета hSocket. TimeLimit работает, если установлен xCallBack - см. ниже описание hb_inetPeriodCallback(). |
hb_inetClearTimeLimit( hSocket ) | Очищает ( устанавливает в -1 ) значение TimeLimit для сокета hSocket. |
nResult := hb_inetErrorCode( hSocket ) | Возвращает код завершения последней операции, 0 - в случае успеха, 1 - соединение закрыто, остальные - стандартные коды ошибок, определенные в Winsock или Unixsockets. |
cString := hb_inetErrorDesc( hSocket ) | Возвращает строку с описанием ошибки, случившейся при исполнении последней операции. |
hb_inetClearError( hSocket ) | |
nResult := hb_InetCount( hSocket ) | Возвращает количество символов, прочитанных или записанных во время последней операции. |
cString := hb_InetAddress( hSocket ) | Возвращает адрес удаленного сервера ( или локальный адрес, если сокет принадлежит серверу ) в строковой форме - четыре числа, разделенные точками. |
cString := hb_InetPort( hSocket ) | Возвращает порт, к которому привязан сокет, или порт удаленного сокета, с которым установлена связь. |
xPrevCallback := hb_inetPeriodCallback( hSocket [,xCallback] ) |
Устанавливает xCallBack для сокета hSocket. Это кодоблок, массив или
что-то другое, что может быть исполнено функцией hb_execFromArray() (она описана ниже
в подразделе 3.14.15 ). xCallBack исполняется блокирующей операцией,
когда истекает таймаут, установленный для этого сокета. Если xCallBack возвращает .F.,
функция чтения/записи прекращает попытки запустить блокирующую опрерацию и возвращает
ошибку. Если же xCallBack возвращает .T., функция повторяет запуск блокирующей
операции чтения/записи - и так до тех пор, пока не истечет TimeLimit (см. выше hb_inetTimeLimit()), если
он установлен. Если TimeLimit не установлен, цикл продолжается до тех пор, пока
чтение/запись не осуществлятся или пока xCallBack не вернет .F.. Итак, чтобы xCallBack запускался, необходимо, чтобы был установлен таймаут для сокета; TimeLimit, если он установлен, должен быть больше, чем таймаут. В этом случае xCallBack будет запускаться с периодичностью, указанной значением таймаута, пока не завершится операция чтения/записи или пока не истечет TimeLimit, если он установлен. |
hb_inetClearPeriodCallback( hSocket ) | Удаляет xCallBack, установленный с помощью hb_inetPeriodCallback(). |
nResult := hb_inetGetSndBufSize( hSocket ) | Возвращает размер буфера записи, или -1 в случае ошибки. |
nResult := hb_inetGetRecvBufSize( hSocket ) | Возвращает размер буфера чтения, или -1 в случае ошибки. |
nResult := hb_inetSetSndBufSize( hSocket, nSize ) | Устанавливает размер буфера записи, или -1 в случае ошибки. |
nResult := hb_inetSetRecvBufSize( hSocket, nSize ) | Устанавливает размер буфера чтения, или -1 в случае ошибки. |
hSocket := hb_inetServer( port [,hSocket [,cBindAddr [,nListenLimit]]] ) | Создает сокет и возвращает хэндл hSocket. Это сокет сервера, он может принимать соединения от клиентов на порт port. Параметр cBindAddr служит для указания адреса конкретного интерфейса на компьютере, к которому должен быть привязан этот сервер. Это нужно в тех случаях, если на компьютере работают несколько логических интерфейсов ( 2 и более сетевых карты, PPP, loopback и пр. ) и вам надо, чтобы сервер отвечал на запросы толко по одному из них. nListenLimit обычно не требуется указывать; если на сокет придет nListenLimit попыток соединения от клиентов, а программа еще не успела обработать ни одной из них, то следующая попытка будет отвергнута ядром с сообщением, что сервер занят (busy). Обычно значение по умолчанию ( 10 ) достаточно даже для тяжело загруженного сервера. |
hSocket := hb_inetAccept( hSocketSrv ) | Ожидает, пока какой-нибудь клиент попытается соединиться с серверным сокетом hSocketSrv, созданным с помощью hb_inetServer(). Возвращает новый сокет, созданный специально для связи с этим клиентом. В случае ошибки возвращается Nil и в hSocketSrv устанавливается код ошибки. |
hSocket := hb_inetConnect( cAddress, nPort ) | Осуществляет подсоединение к порту nPort сервера ( локального или удаленного ) по адресу cAddress, который может быть передан как IP адрес из 4 чисел, разделенных точками ("192.168.0.1") или как DNS имя хоста ("www.kresin.ru"). Возвращает хэндл вновь созданного сокета hSocket. |
hb_inetConnect( cAddress, nPort, hSocket ) | Осуществляет подсоединение к порту nPort сервера ( локального или удаленного ) по адресу cAddress, который может быть передан как IP адрес из 4 чисел, разделенных точками ("192.168.0.1") или как DNS имя хоста ("www.kresin.ru"). В отличие от предыдущего варианта этой функции, использует предварительно подготовленный сокет hSocket. |
hSocket := hb_inetConnectIP( cAddress, nPort ) | Работает аналогично hb_inetConnect(), но в качестве адреса принимает только IP адрес и является thread safe, т.е. ее можно запускать одновременно из разных потоков. |
hb_inetConnectIP( cAddress, nPort, hSocket ) | Работает аналогично hb_inetConnect(), но в качестве адреса принимает только IP адрес и является thread safe, т.е. ее можно запускать одновременно из разных потоков. |
nResult := hb_inetRecv( hSocket, @cResult [,nAmount] ) | Читает принимаемые данные из сокета hSocket в предварительно подготовленную строку cResult в количестве, не превышающем nAmount, если этот параметр передан, или длину cResult в противном случае; возвращает количество прочитанных байт. Функция блокирует выполнение программы ( или потока ), пока какие-нибудь данные не будут прочитаны из сокета, или пока не произойдет какая-либо ошибка ( в том числе окончание таймаута ). Она не обязательно заполнит всю строку, не обязательно примет все данные, передаваемые в сокет - в этом случае надо будет вызывать ее опять до окончания полного приема данных. Чтобы заблокировать поток до полного окончания приема данных, используйте функцию hb_inetRecvAll(). |
nResult := hb_inetRecvAll( hSocket, @cResult [,nAmount] ) | Читает принимаемые данные из сокета hSocket в предварительно подготовленную строку cResult в количестве, не превышающем nAmount, если этот параметр передан, или длину cResult в противном случае; возвращает количество прочитанных байт. В отличие от hb_inetRecv() блокирует поток, пока требуемое количество данных не будет прочитано. |
cResult := hb_inetRecvLine( hSocket [,@nBytesRead [,nMaxLength [,nBufSize]]] ) | Блокирует поток, пока не будет считана последовательность CRLF и возвращает полученную строку. Если произошла ошибка, или сокет закроется до того, как будет считан CRLF, функция не возвратит ничего и будет установлен код ошибки. Возвращаемая строка не включает CRLF. В nBytesRead, если он передан, будет записано количество принятых байт, включая CRLF - т.е., при нормальном завершении это будет длина результирующей строки плюс 2. nMaxLength, если он передан, указывает максимальное количество байт, которое может быть принято, независимо от того, получен ли CRLF. nBufSize - размер буфера приема, по умолчанию - 80 байт. Если принимаемая строка больше, выделяется фрагмент памяти на nBufSize больше и т.д., т.е для длинной строки может много раз потребоваться перераспределение памяти, что не очень хорошо. Так что с помощью указания nBufSize вы можете этим управлять. Можно, например, задать nBufSize равный nMaxLength - тогда сразу будет выделен буфер размером nMaxLength и больше перераспределяться не будет. |
cResult := hb_inetRecvEndBlock( hSocket [,cBlock [,@nBytesRead [,nMaxLength [,nBufSize]]]] ) |
Эта функция ведет себя точно так же, как и hb_inetRecvLine(), но признаком завершения приема здесь
служит строка, передаваемая в параметре cBlock. По умолчанию cBlock == CRLF ,
так что, если этот параметр не задан, функция идентична hb_inetRecvLine().
|
nResult := hb_inetDataReady( hSocket [,nMillis] ) | Проверяет, есть ли в сокете данные, доступные для чтения и возвращает 1, если есть, 0 - если нет и -1 в случае ошибки. Если задан параметр nMillis, функция ждет появления данных в течение nMillis миллисекунд и возвращает результат сразу при появлении данных. Если этот параметр не задан, функция возвращает результат немедленно. |
nResult := hb_inetSend( hSocket, cBuffer [,nLength] ) | Посылает данные, содержащиеся в строке cBuffer через сокет hSocket, возвращает количество переданных байт, 0 - если сокет был закрыт или -1 в случае ошибки. Параметр nLength, если он передается, указывает, сколько байт надо передать. Учтите, что эта функция не гарантирует, что все данные будут записаны, поэтому надо проверять возвращаемое число. |
nResult := hb_inetSendAll( hSocket, cBuffer [,nLength] ) | Посылает данные, содержащиеся в строке cBuffer через сокет hSocket, возвращает количество переданных байт, 0 - если сокет был закрыт или -1 в случае ошибки. Параметр nLength, если он передается, указывает, сколько байт надо передать. В отличие от hb_inetSend() эта функция гарантирует, что все данные будут записаны, только после этого она завершит свою работу. |
aHosts := hb_inetGetHosts( cName ) | Возвращает массив IP адресов, связанных с хостом под названием cName. |
aHosts := hb_inetGetAlias( cName ) | Возвращает массив алиасов, связанных с хостом под названием cName. |
hb_inetInfo() |
hSocket := hb_inetDGram( [lBroadCast] ) | Создает и возвращает сокет для работы по UDP протоколу. Если lBroadCast установлен в .T., сокет сможет посылать и принимать широковещательные сообщения; в большинстве систем программа должна иметь для этого специальные привилегии. |
hSocket := hb_inetDGramBind( nPort [,cAddress [,lBroadCast]] ) | Создает и возвращает сокет для работы по UDP протоколу, привязанный к порту nPort и к определенному логическому интерфейсу, описанному cAddress (если он задан). Если lBroadCast установлен в .T., сокет сможет посылать и принимать широковещательные сообщения; в большинстве систем программа должна иметь для этого специальные привилегии. |
nBytesSent := hb_inetDGramSend( hSocket, cAddress, nPort, cBuffer [,nSize] ) | Посылает пакет данных, содержащихся в cBuffer, через сокет hSocket на IP адрес cAddress, порт nPort. nSize определяет количество передаваемых данных; если этот параметр опущен, передается все, что есть в cBuffer. Возвращается количество переданных байт или -1 в случае ошибки.. Поскольку функция не гарантирует, что все данные будут переданы, возвращаемое значение надо проверять. |
nBytesRead := hb_inetDGramRecv( hSocket, @cBuffer [,nSize] ) | Читает принимаемые данные из сокета hSocket в предварительно подготовленную строку cResult в количестве, не превышающем nSize, если этот параметр передан, или длину cBuffer в противном случае. Возвращает количество прочитанных байт или -1 в случае ошибки. |
Предлагаю вашему вниманию полезный :) пример использования описанных выше (INET) и ниже (многопоточность) функций. Итак, двухпоточное приложение, проверяющее каждые 2 минуты наличие обновлений на форуме clipper.borda.ru ( не забудьте, что его надо откомпилировать с поддержкой многопоточности, иначе оно не будет работать ):
#include "box.ch" REQUEST HB_CODEPAGE_RU866 REQUEST HB_CODEPAGE_RU1251 Static mutex1 Static aNew := {} Function Main() Local pThread, lEnd := .F., nkey hb_cdpSelect( "RU866" ) hb_inetInit() // Создаем mutex для синхронизации потоков при обращении к массиву aNew mutex1 := hb_mutexCreate() // Создаем поток для проверки обновлений с clipper.borda.ru pThread := hb_threadStart( @GetData(), @lEnd ) // Просто ждем пользовательского ввода. CLEAR SCREEN @ 24, 1 SAY "Нажмите Esc, чтобы завершить программу, F5 - изменения" read DO WHILE ( nKey := Inkey(0) ) != 27 IF nKey == -4 // F5 ShowUpd() ENDIF ENDDO // Посылаем посредством lEnd сигнал завершения потоку и ждем его. lEnd := .T. hb_threadJoin( pThread ) hb_inetCleanup() Return .T. // Эта функция вызывается по F5 и показывает изменения на сайте Function ShowUpd() Local arr, bufc, i, l // Вот тут нам понадобился mutex - мы читаем массив aNew, который мог бы в это // время модифицироваться вторым потоком hb_mutexLock( mutex1 ) IF ( l := !Empty( aNew ) ) arr := {} FOR i := 1 TO Len( aNew ) Aadd( arr, Padr( hb_translate( Trim(aNew[i,2])+": " ; +Trim(aNew[i,3])+" "+Trim(aNew[i,4]),"RU1251","RU866" ),64 ) ) NEXT ENDIF hb_mutexUnLock( mutex1 ) bufc := Savescreen( 7,7,13,73 ) @ 7,7,13,73 BOX B_DOUBLE_SINGLE + Space(1) IF l AChoice( 8,8,12,72,arr ) ELSE @ 10, 35 SAY "Ничего нового..." Inkey(1) ENDIF Restscreen( 7,7,13,73, bufc ) hb_dispOutAt( 0, 69, " ", "GR+/N" ) Return .T. // Эта функция анализирует главную страницу сайта и ищет там нужные изменения. // Чтобы лучше ее понять, просмотрите исходный текст главной страницы clipper.borda.ru Static Function CheckAns( cBuf ) Local nPos1 := 1, nPos2, aItems, aRes := {} Local aStru := { {"ID","C",2,0}, {"NAME","C",16,0}, {"TM","C",10,0}, {"LOGIN","C",16,0}, {"TITLE","C",32,0} } Local fname := "cl_borda.dbf", lFirstTime := .F. Field ID, NAME, TM, LOGIN, TITLE IF !File( fname ) dbCreate( fname, aStru ) lFirstTime := .T. ENDIF USE (fname) NEW EXCLUSIVE DO WHILE ( nPos1 := hb_At( "st(", cBuf, nPos1 ) ) != 0 IF ( nPos2 := hb_At( ")", cBuf, nPos1 ) ) != 0 aItems := hb_aTokens( Substr(cBuf,nPos1+3,nPos2-nPos1-3), ",", .T. ) aItems[1] := Padr( Substr( aItems[1], 2, Min( aStru[1,3],Len(aItems[1])-2 ) ), aStru[1,3] ) aItems[2] := Padr( Substr( aItems[2], 2, Min( aStru[2,3],Len(aItems[2])-2 ) ), aStru[2,3] ) aItems[5] := Padr( Substr( aItems[5], 2, Min( aStru[3,3],Len(aItems[5])-2 ) ), aStru[3,3] ) aItems[8] := Padr( Substr( aItems[8], 2, Min( aStru[4,3],Len(aItems[8])-2 ) ), aStru[4,3] ) aItems[9] := Padr( Substr( aItems[9], 2, Min( aStru[5,3],Len(aItems[9])-2 ) ), aStru[5,3] ) IF lFirstTime APPEND BLANK REPLACE ID WITH aItems[1], NAME WITH aItems[2], TM WITH aItems[5], ; LOGIN WITH aItems[8], TITLE WITH aItems[9] ELSE LOCATE FOR ID == aItems[1] IF !Found() APPEND BLANK REPLACE ID WITH aItems[1], NAME WITH aItems[2], TM WITH aItems[5], ; LOGIN WITH aItems[8], TITLE WITH aItems[9] Aadd( aRes, {aItems[1],aItems[2],aItems[8],aItems[9]} ) ELSEIF TM != aItems[5] .OR. LOGIN != aItems[8] .OR. TITLE != aItems[9] REPLACE TM WITH aItems[5], LOGIN WITH aItems[8], TITLE WITH aItems[9] Aadd( aRes, {aItems[1],aItems[2],aItems[8],aItems[9]} ) ENDIF ENDIF nPos1 := nPos2 + 1 ELSE EXIT ENDIF ENDDO USE Return aRes // Это 2-й поток Function GetData( lEnd ) Local nCount := 0, hSocket, cUrl, cServer, cBuf, aRes cServer := "clipper.borda.ru" cURL := "GET http://" + cServer + "/ HTTP/1.1" +Chr(13)+Chr(10) cURL += "Host: " + cServer + Chr(13)+Chr(10) cURL += "User-Agent: test_util"+Chr(13)+Chr(10) cUrl += Chr(13)+Chr(10) DO WHILE !lEnd IF nCount == 0 hb_dispOutAt( 0, 61, "Читаем.", "GR+/N" ) hSocket := hb_inetCreate() // создаем сокет hb_inetConnect( cServer, 80, hSocket ) // присоединяемся к сайту форума IF hb_inetErrorCode( hSocket ) != 0 hb_dispOutAt( 0, 61, "Сбой...", "GR+/N" ) ENDIF // Посылаем сформированный выше запрос и ждем ответа. IF hb_inetSendAll( hSocket, cURL ) > 0 .AND. !Empty( cBuf := hb_inetRecvEndBlock( hSocket, "main2(",,,4096 ) ) IF !Empty( aRes := CheckAns( cBuf ) ) hb_dispOutAt( 0, 69, "!! Есть !!", "GR+/N" ) // Используем mutex для безопасной модификации aNew hb_mutexLock( mutex1 ) aNew := aRes hb_mutexUnLock( mutex1 ) ENDIF hb_dispOutAt( 0, 61, " ", "GR+/N" ) ELSE hb_dispOutAt( 0, 61, "Сбой...", "GR+/N" ) ENDIF // Закрываем сокет hb_inetClose( hSocket ) ENDIF hb_idleSleep(2) IF ++nCount >= 60 nCount := 0 ENDIF ENDDO Return Nil
При смене движка форума эта программа может перестать работать, потому что она предполагает наличие определенных строк в html коде - ищет вызовы javascript функций main2() и st().
Многопоточность - это модель программирования, которая позволяет нескольким потокам выполняться в рамках одного процесса, одного приложения, взаимодействуя между собой, разделяя ресурсы этого приложения. Потоки выполняются операционной системой параллельно. Если система однопроцессорная, то это квазипараллельность, обеспечиваемая тем, что ОС распределяет время между потоками. В многопроцессорной системе потоки могут реально выполняться параллельно на разных процессорах, обеспечивая общее повышение производительности приложения. Часто бывает удобно использовать отдельные потоки для каких-то действий, связанных с длительными вычислениями или с длительным ожиданием какого-либо события, блокирующим выполнение программы ( например, при работе в сети с использованием сокетов ); при этом основной поток продолжает реагировать на действия пользователя.
Harbour - приложение может быть собрано для однопоточного выполнения и для многопоточного. В последнем случае необходимо использовать многопоточную версию виртуальной машины Harbour - библиотеку hbvmmt.lib вместо однопоточной hbvm.lib и библиотеку вашего C компилятора, содержащую функции для реализации многопоточности (для Borland C это cw32mt.lib). Самое простое решение - использовать для сборки вашего приложения hbmk2 с ключом -mt. Ничто не мешает вам собирать в многопоточном режиме и обычные однопоточные приложения, но так они будут работать чуть медленнее и займут чуть больше места на диске и в памяти.
При создании новый поток наследует от родителя:
- кодовую страницу, установленную hb_cdpSelect(),
- язык ( hb_langSelect() ),
- все SET установки,
- RDD по умолчанию,
- GT драйвер и консольное окно,
- установки I18N.
Эти установки инициализируются в начальные значения ( создается новая их копия ):
- публичная переменная Getlist := {},
- обработчик ошибок, инициализируемый вызовом Errorsys(),
- обработчик математических ошибок,
- установки макрокомпилятора ( устанавливаемые hb_setMacro() ),
- RDDI_* установки в стандартных RDD ( сторонние RDD могут использовать глобальные установки ),
- thread static переменные.
Отдельные Public и Private переменные могут передаваться потоку при его создании.
Следующие ресурсы используются совместно:
- функции и процедуры,
- определения классов,
- модули RDD,
- GT драйверы,
- языковые модули,
- модули кодовых страниц,
- статические переменные.
Локальные ресурсы потока:
- Public и Private переменные, кроме тех, что используются совместно,
- рабочие области (workareas),
- thread static переменные.
Ниже следует список функций, предназначенных для управления потоками, их созданием и завершением:
pThID := hb_threadStart( @sStart() | bStart | cStart [, params,... ] ) | Создает новый поток, возвращает указатель потока pThID, или Nil, если поток создать не удалось. Первым параметр hb_threadStart() определяет, какой код будет исполнять новый поток. Это может быть указатель на функцию, кодоблок или имя функции, передаваемое как текстовая строка. Далее следует список параметров, передаваемых этой функции. |
pThID := hb_threadStart( nThreadAttrs, @sStart() | bStart | cStart [, params,... ] ) | Этот
вариант использования hb_threadStart() отличается от предыдущего наличием первого параметра
nThreadAttrs, содержащего атрибуты потока. Эти атрибуты описаны в hbthread.ch:
#define HB_THREAD_INHERIT_PUBLIC 1 #define HB_THREAD_INHERIT_PRIVATE 2 #define HB_THREAD_INHERIT_MEMVARS 3 #define HB_THREAD_MEMVARS_COPY 4Если мы, например, пишем hb_threadStart( hb_bitor(HB_THREAD_INHERIT_PUBLIC,HB_THREAD_MEMVARS_COPY),@thFunc() ) ,
то поток получает копии всех Public переменных родителя, а если
hb_threadStart( HB_THREAD_INHERIT_MEMVARS,@thFunc() ) ,
то поток разделяет все Public и Private переменные с родителем, см. пример harbour/tests/mt/mttest08.prg.
|
pThID := hb_threadSelf() | Возвращает указатель потока, из которого вызывается эта функция. Может возвратить Nil, если поток создан не средствами Харбора. |
nThNo := hb_threadId( [ pThID ] ) | Возвращает идентификатор потока по его указателю. |
lOk := hb_threadJoin( pThID [, @xRetCode ] ) | Приостанавливает выполнение текущего потока, пока не завершится поток, на который указывает pThID. |
lOk := hb_threadDetach( pThID ) | Отключает поток, на который указывает pThID, делает его отдельным. Теперь его уже не надо присоединять функцией hb_threadJoin(), он освободит все свои ресурсы автоматически после завершения своей работы. |
lOk := hb_threadQuitRequest( pThID ) | Посылает операционной системе запрос завершить поток, на который указывает pThID и приостанавливает текущий поток, ожидая выполнения завершения потока pThID. Учтите, что при этом потока "убивается" извне, и результат его работы неопределен. |
hb_threadTerminateAll() | Посылает запрос на завершение всех потоков и ждет, пока это не произойдет. Функция может быть вызвана только из главного потока. |
hb_threadWaitForAll() | Ждет, пока не завершатся все потоки. |
nRes := hb_threadWait( pThID | apThID [, nTimeOut] [, lAll] ) | Ожидает nTimeOut секунд ( или неограниченное время, если этот параметр не задан ) завершения потока pThID, или одного из потоков в массиве указателей apThID, или всех потоков из этого массива, если указан параметр lAll и он равен .T.. Возвращает номер завершившегося потока, или количество потоков, завершившихся за nTimeOut секунд, если lAll равен .T.. |
lOk := hb_mtvm() | Возвращает истину, если ваша программа скомпилирована с возможностью создавать потоки. |
Пользуясь этими функциями, мы уже можем написать первые несложные многопоточные приложения, например вот такое, для вывода часов на экран:
FUNCTION Main() LOCAL cVar := Space( 20 ) CLEAR SCREEN IF !hb_mtvm() ? "Отсутствует поддержка многопоточности, часов не будет видно." WAIT ELSE hb_threadStart( @Show_Time() ) ENDIF @ 10, 10 SAY "Введите что-нибудь:" GET cVar READ SetPos( 12, 0 ) ? "Вы ввели -> [" + cVar + "]" WAIT RETURN Nil FUNCTION Show_Time() LOCAL cTime DO WHILE .T. cTime := Dtoc( Date() ) + " " + Time() hb_dispOutAt( 0, MaxCol() - Len( cTime ) + 1, cTime, "GR+/N" ) hb_idleSleep( 1 ) ENDDO RETURN nil
Мы создали поток, который начал выполнять функцию Show_Time(), постоянно выводящую текущее время в верхнем правом углу экрана и продолжаем заниматься своми делами. Обратите внимание, что для вывода времени на экран используется функция hb_dispOutAt(), она не изменяет текущего положения курсора и цвета в консоли.
Если бы использовалась друга функция, например, DispOut(), курсор перемещался бы из области ввода в основном потоке, при этом не помогло бы сохранение и восстановление его позиции в Show_Time(). Дело в том, что операционная система может прервать выполнение одного потока и передать управление другому в любой момент времени, посреди любой операции. Вы, например, восстанавливаете положение курсора функцией SetPos(), но на любой стадии ее выполнения ( а это длинная серия операций машинного кода ) операционная система может прервать ее и вернуть управление главному потоку или какому-либо другому, который в этот момент тоже модифицирует положение курсора - и результат будет непредсказуем. Поэтому в тех случаях, когда потоки используют какие-либо общие ресурсы ( чаще всего - переменные ), необходимы средства синхронизации работы потоков, чтобы они не обращались к общим ресурсам одновременно. Такими средствами являются семафоры и одна из их разновидностей - mutex ( их называют по-русски "мьютексы", но мне такое написание что-то не нравится ). Смотрим ниже описание соответствующих функций:
pMtx := hb_mutexCreate() | Создает mutex и возвращает его хэндл. |
lLocked := hb_mutexLock( pMtx [, nTimeOut] ) | Блокирует mutex pMtx для текущего потока. Если этот mutex уже заблокирован другим потоком, то текущий поток приостанавливается, пока тот поток не разблокирует mutex pMtx, или пока не истечет nTimeOut миллисекунд, если этот параметр передан. Возвращает .T., если pMtx заблокирован. |
lOk := hb_mutexUnlock( pMtx ) | Разблокирует предварительно заблокированный mutex pMtx. |
hb_mutexNotify( pMtx [, xVal] ) | Посылает уведомление очередному потоку, "подписавшемуся" на mutex pMtx с помощью функции hb_mutexSubscribe() или hb_mutexSubscribeNow() и, таким образом, дает ему возможность продолжить работу. Если на pMtx "подписаны" несколько потоков, операционная система сама выбирает, какому из них послать уведомление. Параметр xVal передается разблокируемому потоку, через функцию hb_mutexSubscribe() этого потока, которая записывает xVal в переданный ей по ссылке третий параметр. |
hb_mutexNotifyAll( pMtx [, xVal] ) | Аналогична hb_mutexNotify(), но, в отличие от нее, посылает уведомление не одному, а всем потокам, "подписанным" на mutex pMtx. |
lSubscribed := hb_mutexSubscribe( pMtx, [nTimeOut] [, @xVal] ) | Функция приостанавливает выполнение текущего потока на время nTimeOut миллисекунд ( или на неограниченное время, если этот параметр не указан ), пока другой поток не вызовет hb_mutexNotify() или hb_mutexNotifyAll() для того же mutex pMtx. В переданный по ссылке xVal записывается полученное от hb_mutexNotify() значение ( второй параметр hb_mutexNotify() ). Функция возвращает .T., если она завершилась в результате получения уведомления, и .F., если - по таймауту. |
lSubscribed := hb_mutexSubscribeNow( pMtx, [nTimeOut] [, @xSubscribed] ) | Аналогична предыдущей функции hb_mutexSubscribe(), но, в отличие от нее, игнорирует уведомления, посланные вызовами hb_mutexNotify() до момента начала работы этой функции. |
xResult := hb_mutexEval( pMtx, bCode | @sFunc() [, params,...] ) | Последовательно выполняет hb_mutexLock( pMtx ), действие, заданное кодоблоком bCode или указателем @sFunc() с параметрами params,... и hb_mutexUnlock( pMtx ). |
hb_mutexQueueInfo( pMtx, @nWaiters, @nEvents ) | Предоставляет информацию об очереди для mutex pMtx. В nWaiters записывается количество потоков, "подписанных" на pMtx, в nEvents - количество уведомлений, еще не дошедших до "подписавшихся". |
Итак, для предотвращения одновременого обращения разных потоков к одному и тому же ресурсу делаем следующее:
1) создаем предварительно mutex: pMtx := hb_mutexCreate()
2) в тех местах программы, где происходит обращение к защищаемым ресурсам, оборачиваем
соответствующий код блокировкой/разблокировкой mutex'а, т.е. ставим
перед перед ним hb_mutexLock( pMtx )
и после него hb_mutexUnLock( pMtx )
-
точно так же, как мы блокируем/разблокируем запись расшаренной базы данных при записи в нее.
Не забывайте, что, в отличие от rlock(), hb_mutexLock() приостанавливает дальнейшее выполнение потока,
если mutex уже чем-то заблокирован - при невнимательности можно получить deadlock -
ситуацию, когда несколько потоков взаимно заблокировали выполнение друг друга.
Правда, в нашем примере с часами, на тот случай, если бы мы использовали Setpos() и Dispout(), mutex'ы нам бы не помогли. В главном потоке управление курсором производится не непосредственно в нашей программе, а где-то внутри Харборовской RTL, в реализации функции Readmodal() - и вставить туда блокировку mutex'а мы не можем. Поэтому расширим немного этот пример, добавив туда установку параметров отображения даты:
#include "box.ch" Static mutex1 Static cOpt := "DD.MM.YY" FUNCTION Main() LOCAL cVar := Space( 20 ) CLEAR SCREEN IF !hb_mtvm() ? "Отсутствует поддержка многопоточности, часов не будет видно." WAIT ELSE mutex1 := hb_mutexCreate() hb_threadStart( @Show_Time() ) ENDIF SET KEY -4 TO Options @ 10, 10 SAY "Введите что-нибудь:" GET cVar READ SET KEY -4 TO SetPos( 12, 0 ) ? "Вы ввели -> [" + cVar + "]" WAIT RETURN Nil FUNCTION Show_Time() LOCAL cTime DO WHILE .T. hb_mutexLock( mutex1 ) cTime := Iif( !Empty( cOpt ), hb_Dtoc( Date(),cOpt ), Space(10) ) + " " + Time() hb_mutexUnLock( mutex1 ) hb_dispOutAt( 0, MaxCol() - Len( cTime ) + 1, cTime, "GR+/N" ) hb_idleSleep( 1 ) ENDDO RETURN nil Function Options Local bufc, n, aOpt := { "DD.MM.YYYY","DD/MM/YYYY","DD.MM.YY","YYYY.MM.DD"," " } bufc := Savescreen( 7,31,13,49 ) @ 7,31,13,49 BOX B_DOUBLE_SINGLE + Space(1) IF ( n := AChoice( 8,32,12,48,aOpt ) ) != 0 hb_mutexLock( mutex1 ) cOpt := aOpt[n] hb_mutexUnLock( mutex1 ) ENDIF Restscreen( 7,31,13,49, bufc ) Return Nil
Итак мы имеем переменную cOpt, которая содержит строку формата отображения даты, функцию Options(), которая вызывается по нажатию клавиши F5 и изменяет эту строку формата. Поскольку поток, выполняющий Show_Time(), использует эту строку, то для исключения коллизий мы используем mutex.
Еще один пример многопоточной программы с mutex'ами смотрите в предыдущем разделе.
Еще 2 функции для синхронизации действий между потоками:
lFirstCall := hb_threadOnce( @onceControl [, bAction] ) | Исполняет bAction один раз, onceControl - переменная, которая хранит статус выполнения, она должна быть инициализирована в Nil, обычно это статическая переменная. Когда bAction выполняется потоком, все остальные потоки, вызывающие в этот момент hb_threadOnce(), приостанавливаются, даже если они используют другую onceControl. Если поток пытается вызвать hb_threadOnce() с той же onceControl рекурсивно из bAction, то функция сразу завершается, не вызывая bAction и возвращает .F.. Функция возвращает логическое значения, показывающее, был ди это первый вызов hb_threadOnce() для этой onceControl. |
lInit := hb_threadOnceInit( @item, value ) | Эта функция похожа на hb_threadOnce(), она записывает value в nItem, если nItem равен Nil. |
Теперь о доступе к базам данных в многопоточных приложениях. Как уже отмечалось выше, в Harbour рабочие области (workareas) локальны для потока, т.е. каждый поток имеет свои независимые рабочие области и алиасы и, в общем случае, если вам в потоке "Б" нужна база данных, уже открытая в потоке "А", вам надо открыть ее в "Б" по новой, что нетрудно сделать в shared mode.
Но есть и другая возможность - вы можете передать рабочую область от одного потока другому, используя для этого известное пользователям xBase++ zero space. Для этого предназначены следующие 2 функции:
lOk := hb_dbDetach( [nWorkArea|cAlias] [, xCargo] ) | Отсоединяет рабочую область, заданную по номеру nWorkArea или по имени алиаса cAlias от текущего потока, перемещая ее в zero space. При этом можно передать информацию потокам, которые будут ее использовать, через xCargo. |
lOk := hb_dbRequest( [cAlias] [, lNewArea] [,@xCargo] [,lWait]) | Запрашивает рабочую область из zero space по имени алиаса cAlias; lNewArea указывает, использовать ли новую рабочую область ( как и в dbUseArea() ), lWait - следует ли ждать, пока запрошенная рабочая область станет доступна. Функция возвращает .T., если все прошло успешно. |
Этот механизм позволяет разным потокам совместно использовать рабочие области. Для иллюстрации введем 2 команды:
#xcommand UNLOCK WORKAREA [] => hb_dbDetach( ) #xcommand LOCK WORKAREA => hb_dbRequest( , .T.,, .T. )
После открытия таблицы командой Use поток выполняет UNLOCK WORKAREA
и,
т.о., перемещает ее в zero space. Теперь каждый поток, которому нужна эта таблица,
может получить к ней доступ, например:
LOCK WORKAREA "DOCUMENTS" COUNT TO nInvoices FOR year( DOCUMENTS->DATE ) == year( date() ) UNLOCK WORKAREA
Другие примеры многопоточных программ вы можете найти в harbour/tests/mt.
3.12 API файлового ввода/вывода (File IO API)
Как известно, Harbour унаследовал от Clipper набор функций FOpen(), FSeek(), FRead(), ...
, предназначенных для работы с файлами.
Этот набор пополнился новыми функциями, полный их список приведен в подразделе 3.14.2 Файловые функции.
Новое API файлового ввода/вывода - это набор функций с префиксом hb_vf, аналогичных по набору параметров и производимым операциям обычным файловым функциям. Изменена реализация этих функций, что позволило использовать заменяемые драйверы наподобие RDD. Теперь, если перед именем файла поставить префикс какого-либо из существующих драйверов с двоеточием в конце ( "mem:", "net:", ... ), обращение к этому файлу пойдет через этот драйвер. Вот этот вызов, например:
hb_vfCopyFile( "test.txt", "mem:test.txt" )создат копию файла "test.txt" в памяти (hbmemio драйвер).
Драйверы ввода/вывода - это дополнительные библиотеки Harbour, такие как hbnetio, hbmemio, hbgzio, hbbz2io, hbcomio и др.
Список функций:
lOk := hb_vfExists( cFileName, [ @cDestFileName ] ) | Проверяет, существует ли файл cFileName, аналог File() .
|
nResult := hb_vfErase( cFileName ) | Удаляет файл cFileName, аналог FErase() .
|
nResult := hb_vfRename( cFileSrc, cFileDst ) | Переименовывает файл cFileSrc в cFileDst, аналог FRename() .
|
nResult := hb_vfCopyFile( cFileSrc, cFileDst ) | Копирует файл cFileSrc в cFileDst, аналог FCopy() .
|
lExists := hb_vfDirExists( cDirName ) | Проверяет, существует ли каталог cDirName, аналог DirExists() .
|
nSuccess := hb_vfDirMake( cDirName ) | Создает каталог cDirName, аналог Makedir() .
|
nSuccess := hb_vfDirRemove( cDirName ) | Удаляет каталог cDirName, аналог DirRemove() .
|
aDirectory := hb_vfDirectory( [ cDirSpec ], [ cAttr ] ) | Возвращает массив с информацией о каталоге cDirName, аналог Directory() .
|
nFreeSpace := hb_vfDirSpace( cDirName, [ nInfoType ] ) | |
lOk := hb_vfAttrGet( cFileName, @nAttr ) | Записывает в переменную nAttr, передаваемую по ссылке, аттрибута файла с именем cFileName; аналог hb_fGetAttr() .
|
lOk := hb_vfAttrSet( cFileName, nAttr ) | Устанавливает атрибуты файла cFileName, указанные в nAttr; аналог hb_fSetAttr() .
|
lOk := hb_vfTimeGet( cFileName, @tsDateTime ) | Записывает в tsDateTime дату и время модификации файла cFileName; частичный аналог hb_fGetDateTime() .
|
lOk := hb_vfTimeSet( cFileName, tsDateTime ) | Устанавливает дату и время модификации файла cFileName; частичный аналог hb_fSetDateTime() .
|
nSuccess := hb_vfLink( cExistingFileName, cNewFileName ) | Создает жесткую ссылку на файл, аналог hb_fLink() .
|
nSuccess := hb_vfLinkSym( cTargetFileName, cNewFileName ) | Создает символическую ссылку cNewFileName на файл cTargetFileName, аналог hb_fLinkSym() .
|
cDestFileName := hb_vfLinkRead( cFileName ) | Возвращает значение (полный путь) символической ссылки, аналог hb_fLinkRead() .
|
pHandle := hb_vfOpen( [@]cFileName, [ nModeAttr ] ) | Открывает файл cFileName, аналог FOpen() . Но, отличие от FOpen() , возвращаемое значение - типа Pointer.
|
lOk := hb_vfClose( pHandle ) | Закрывает предварительно открытый файл, аналог FClose() .
|
lOk := hb_vfLock( pHandle, nStart, nLen, [ nType ] ) | Устанавливает блокировку на участок файла со смещения nOffsetFrom до nOffsetTo, аналог hb_FLock() .
|
lOk := hb_vfUnlock( pHandle, nStart, nLen ) | Снимает блокировку с участка файла со смещения nOffsetFrom до nOffsetTo, аналог hb_FUnLock() .
|
nPID := hb_vfLockTest( pHandle, nStart, nLen, [ nType ] ) | |
nRead := hb_vfRead( pHandle, @cBuff, [ nToRead ], [ nTimeOut ] ) | Читает фрагмент открытого файла, начиная с текущего положения, аналог FRead() .
|
cBuffer := hb_vfReadLen( pHandle, nToRead, [ nTimeOut ] ) | Читает nToRead байт из файла, возвращает прочитанный буфер, аналог hb_FReadLen() .
|
nWritten := hb_vfWrite( pHandle, cBuff, [ nToWrite ], [ nTimeOut ] ) | Производит запись в открытый файл, начиная с текущего положения, аналог FWrite() .
|
nRead := hb_vfReadAt( pHandle, @cBuff, [ nToRead ], [ nAtOffset ] ) | Читает фрагмент открытого файла, начиная с заданного положения nAtOffset. |
nWritten := hb_vfWriteAt( pHandle, cBuff, [ nToWrite ], [ nAtOffset ] ) | Производит запись в открытый файл, начиная с заданного положения nAtOffset. |
nOffset := hb_vfSeek( pHandle, nOffset, [ nWhence ] ) | Перемещает указатель на заданную позицию в открытом файле, аналог FSeek() .
|
lOk := hb_vfTrunc( pHandle, [ nAtOffset ] ) | |
nSize := hb_vfSize( pHandle | cFileName [, lUseDirEntry ] ) | Возвращает размер файла cFileName, аналог hb_FSize() .
|
lEof := hb_vfEof( pHandle ) | |
hb_vfFlush( , [ lDirtyOnly ] ) | |
hb_vfCommit( pHandle ) | |
nResult := hb_vfConfig( pHandle, nSet, [ nParam ] ) | |
nOsHandle := hb_vfHandle( pHandle ) | |
pHandle := hb_vfTempFile( @cFileName, [ cDir ], [ cPrefix ], [ cExt ], [ nAttr ] ) | Создает временный файл, аналог hb_fTempCreateEx() .
|
cFileBody := hb_vfLoad( cFileName, [ nMaxSize ] ) | Читает весь файл (но не больше, чем nMaxSize, если этот параметр указан), аналог hb_Memoread() .
|
3.13 Интернационализация (hbi18n)
Технология интернационализации приложений, основана на широко известной GNU gettext и обеспечивает сравнительно простой способ перевода приложений на разные языки.
Я попробую пошагово описать этот процесс. Итак,
1. Пометим специальным образом все строковые литералы, которые мы хотим перевести. Для этого мы вставляем в каждый исходный файл, в котором есть такие литералы, строчку типа
#xtranslate _I( <x,...> ) => hb_i18n_gettext( <x> )
hb_i18n_gettext()
, и все нужные строковые литералы заключаем в _I()
, например:
Alert( _I("Dangerous error!") )
Alert()
по-прежнему будет
выдавать сообщение "Dangerous error!".
2. Создать так называемые pot-файлы. Для этого надо скомпилировать Харбором наши исходники с ключом -j (ну и с другими ключами, которые мы используем при компиляции), например:
harbour myfile1.prg myfile2.prg -j -n -ic:\harbour\include
#: /myfile1.prg:230 #, c-format msgid "Dangerous error!" msgstr ""
3. Объединяем все .pot файлы нашего проекта. Для этого используется специальная программа, входящая в
комплект Харбора, hbi18n
. Запускаем ее с ключом -m (merge - объединение):
hbi18n -m -omyproject.pot myfile1.pot myfile2.pot
Alert( _I("Dangerous error!") )
, в myproject.pot будет только одно его вхождение.
4. Приступаем, собственно, к переводу. Для каждого msgid c текстом вставляем его перевод в msgstr. Теперь у нас в myproject.pot будут строки типа:
#: /myfile1.prg:230 #, c-format msgid "Dangerous error!" msgstr "Жуткая ошибка!"
5. Следующим шагом будет компиляция myproject.pot, для этого опять используем hbi18n
, теперь уже с ключом -g:
hbi18n -g myproject.pot
6. Для этого вставляем в программу следующие строки:
cBuff := hb_MemoRead( "myproject..hbl" ) IF hb_i18n_Check( cBuff ) hb_i18n_Set( hb_i18n_RestoreTable(cBuff) ) ENDIF
Ну хорошо, создали мы hbl-файлы, подгружаем их, все работает. Но мы меняем программу, добавляем новые литералы.
Создаем pot-файлы по новой, но они без перевода. Что, писать перевод опять? Нет, конечно! Нам послужит все та же
hbi18n
, но уже с ключом -a (add - добавить). Она добавляет переводы слов из сохраненного старого pot-файла:
hbi18n -a -omyproject.pot myproject.pot myproject_old.pot
В заключение приложу сюда в качестве примера мой рабочий bat-файл, который создает и модифицирует pot. Этот bat-файл взят из hbedit:
@set SRC_DIR=\papps\gitapps\hbedit\source c:\harbour\bin\harbour %SRC_DIR%\fdiff.prg %SRC_DIR%\fedit.prg %SRC_DIR%\fmenu.prg %SRC_DIR%\fview.prg %SRC_DIR%\hbcommander.prg -j -n -q -ic:\harbour\include c:\harbour\bin\hbi18n.exe -m -ohbedit_ru_1.pot fdiff.pot fedit.pot fmenu.pot fview.pot hbcommander.pot c:\harbour\bin\hbi18n.exe -a -ohbedit_ru_1.pot hbedit_ru_1.pot hbedit_ru866.pot mv hbedit_ru_1.pot hbedit_ru866.pot @del *.c @del fdiff.pot @del fedit.pot @del fmenu.pot @del fview.pot @del hbcommander.pot
Как видите, сначала собирается временный hbedit_ru_1.pot, потом в него добавляются переводы из основного hbedit_ru866.pot и после этого временный hbedit_ru_1.pot переименовывается в основной hbedit_ru866.pot
В этом разделе будут рассмотрены разнообразные новые функции, не попавшие в предыдущие тематические разделы.
3.14.1 Встроенный компилятор
Три функции, использующие встроенный компилятор (hbcplr.lib), основанный на том же коде, что и сам harbour.exe.
nRetCode := hb_compile( "harbour", cFileName, [...] ) | Компилирует файл cFileName с переданными ей параметрами компиляции. Результатом ее работы является .c файл. |
cHrb := hb_compileBuf( "harbour", cFileName, [...] ) | В отличие от hb_compile(), не создает .c файл, а возвращает созданный в результате компиляции p-code в виде текстовой строки, который можно сохранить как hrb файл или исполнить с помощью hb_hrbRun() |
cHrb := hb_compileFromBuf( cPrg, "harbour", [...] ) | Компилирует код из буфера cPrg, возвращает p-code, как и hb_compileBuf() |
FUNCTION Main( cFileName ) Local handle, buf, cBuf := "", i buf := hb_compilebuf( "harbour", cFileName, "/n","/w" ) // Компилируем hb_hrbRun( buf ) // исполняем handle := FCreate( Iif((i:=Rat('.',cFileName))=0,cFileName,Substr(cFileName,1,i-1)) + ".hrb" ) FWrite( handle, buf ) FClose( handle ) Return Nil
3.14.2 Файловые функции
lSuccess := hb_fGetAttr( cFileName, @nAttr ) |
Записывает в переменную nAttr, передаваемую по ссылке, аттрибута файла с именем cFileName. Константы для "расшифровки" nAttr определены в fileio.ch:#define HB_FA_READONLY 0x00000001 /* R */ #define HB_FA_HIDDEN 0x00000002 /* H */ #define HB_FA_SYSTEM 0x00000004 /* S */ #define HB_FA_LABEL 0x00000008 /* V */ #define HB_FA_DIRECTORY 0x00000010 /* D */ #define HB_FA_ARCHIVE 0x00000020 /* A */ #define HB_FA_DEVICE 0x00000040 /* I */ #define HB_FA_NORMAL 0x00000080 /* */ #define HB_FA_TEMPORARY 0x00000100 /* T HB_FA_FIFO */ #define HB_FA_SPARSE 0x00000200 /* P */ #define HB_FA_REPARSE 0x00000400 /* L HB_FA_LINK */ #define HB_FA_COMPRESSED 0x00000800 /* C */ #define HB_FA_OFFLINE 0x00001000 /* O */ #define HB_FA_NOTINDEXED 0x00002000 /* X */ #define HB_FA_ENCRYPTED 0x00004000 /* E */ # Unix/Linux: #define HB_FA_SUID 0x08000000 /* 4000 set user ID on execution */ #define HB_FA_SGID 0x04000000 /* 2000 set group ID on execution */ #define HB_FA_SVTX 0x02000000 /* 1000 sticky bit */ #define HB_FA_RUSR 0x01000000 /* 0400 read by owner */ #define HB_FA_WUSR 0x00800000 /* 0200 write by owner */ #define HB_FA_XUSR 0x00400000 /* 0100 execute/search by owner */ #define HB_FA_RGRP 0x00200000 /* 0040 read by group */ #define HB_FA_WGRP 0x00100000 /* 0020 write by group */ #define HB_FA_XGRP 0x00080000 /* 0010 execute/search by group */ #define HB_FA_ROTH 0x00040000 /* 0004 read by others */ #define HB_FA_WOTH 0x00020000 /* 0002 write by others */ #define HB_FA_XOTH 0x00010000 /* 0001 execute/search by others */ |
lSuccess := hb_fSetAttr( cFileName, nAttr ) |
Устанавливает атрибуты файла cFileName, указанные в nAttr."Расшифровку" атрибутов см. в примечании к hb_fGetAttr()
|
lSuccess := hb_fGetDateTime( cFileName, @dDate [, @cTime] ) | Записывает в dDate и cTime, соответственно, дату и время модификации файла cFileName. |
lSuccess := hb_fSetDateTime( cFileName, dDate [, cTime] ) | Устанавливает дату и время модификации файла cFileName. |
hb_fcommit( nHandle ) | |
hb_fisdevice( nHandle ) | |
nSuccess := hb_fLink( cExistingFileName, cNewFileName ) | Создает жесткую ссылку cNewFileName на файл cExistingFileName (работает в Unix/Linux и в Windows начиная с Vista). |
cDestFileName := hb_fLinkRead( cFileName ) | Возвращает значение (полный путь) символической ссылки (работает в Unix/Linux и в Windows начиная с Vista). |
nSuccess := hb_fLinkSym( cTargetFileName, cNewFileName ) | Создает символическую ссылку cNewFileName на файл cTargetFileName (работает в Unix/Linux и в Windows начиная с Vista). |
hb_flock( nHandle, nOffsetFrom, nOffsetTo ) | Устанавливает блокировку на участок файла со смещения nOffsetFrom до nOffsetTo. |
hb_funlock( nHandle, nOffsetFrom, nOffsetTo ) | Снимает блокировку с участка файла со смещения nOffsetFrom до nOffsetTo. |
cReadBuf := hb_freadlen( nHandle, nToRead ) | Читает nToRead байт из файла, возвращает прочитанный буфер. |
hb_fsetdevmode() | |
hb_fsize( cFileName[, lUseDirEntry] ) | Возвращает размер файла cFileName. |
nHandle := hb_fTempCreate( [cPath], [cPrefix], [nAttr], [@cFileName] ) | Создает временный файл,
с атрибутами nAttr, с префиксом cPrefix, путь к которому указан в cPath.
Функция возвращает handle открытого файла, а в cFileName, передаваемый
по ссылке, записывает его имя. Атрибуты файла ( их символьные имена определены в
fileio.ch ) могут иметь следующие значения:
FC_NORMAL 0 FC_READONLY 1 FC_HIDDEN 2 FC_SYSTEM 4 |
nHandle := hb_fTempCreateEx( [@cFileName], [cPath], [cPrefix], [cExt], [nAttr] ) | Делает то же, что и предыдущая функция, hb_fTempCreate(), отличается от нее тем, что позволяет задать и расширение создаваемого файла cExt. Ну и порядок следования аргументов другой. |
lResult := hb_FileDelete( cFileMask [, cAttr ] ) |
3.14.3 Функции для манипуляций с именами файлов
Группа функций, манипулирующих строкой с путем, именем, расширением файла.cRes := hb_FNameDir( cFullPath ) | Возвращает путь к файлу. |
cRes := hb_FNameName( cFullPath ) | Возвращает имя файла ( без пути и расширения ). |
cRes := hb_FNameExt( cFullPath ) | Возвращает расширение файла. |
cRes := hb_FNameNameExt( cFullPath ) | Возвращает имя и расширение файла |
cRes := hb_FNameExtSet( cFullPath, cExt ) | Устанавливает новое расширение файла, возвращает новый полный путь. |
cRes := hb_FNameExtSetDef( cFullPath, cExt ) | Устанавливает новое расширение файла если оно отсутствует, возвращает новый полный путь. |
3.14.4 Строковые функции
cString := hb_strFormat( cFormat, ... ) | Возвращает строку, форматированную в C-стиле, как в printf(). |
lResult := hb_strIsUtf8( cString ) | Проверяет является ли cString utf8-строкой. |
cResult := hb_strReplace( cString, cSource | acSource | hReplace [, cDest | acDest ] ) ) |
Эта функция позволяет легко заменить заданные полстроки на другие. Если 2-й параметр - строка, то каждый символ в cString который существует в cSource в n-й позиции, заменяется на соответствующий символ в n-й позиции в cDest, или на строку из массива acDest[n]. Если 2-й параметр - массив, то каждая подстрока в cString, которая есть в acSource в n-й позиции, заменяется на соответствующий символ в n-й позиции в cDest, или на строку из массива acDest[n]. Если 2-й параметр - хэш массив, 3-й параметр должен быть опущен. Не забывайте, что одномерный хэш массив - более компактный тип данных, чем массив строк, т.к. это только один GC item, тогда как каждый подмассив - это отдельный GC item. Усли n больше, чем Len( cDest ), или Len( acDest ), то соответствующий символ/подстрока удаляется из результата. Эта функция помогает заменить мновественные вызовы StrTran(). Примеры:
// encode XML value
|
cResult := hb_strShrink( cString[, nShrinkBy] ) |
Обрезает cString справа га nShrinkBy байт. Значение по умолчанию nShrinkBy - 1. Функция возвращает пустое значение при ошибке; полную строку, если nShrinkBy равен или меньше нуля.. Она призвана заменить Left() в таких случаях:
cString := Left( cString, Len( cString ) - 1 ) ->
|
nResult := hb_HexToNum( cHex ) | Функция аналогична Val(), но строка cHex содержит 16-ричное число. |
cHex := hb_NumToHex( num[, nLen] ) | Преобразует число num в строку длиной nLen в 16-ричной системе. |
nTokens := hb_TokenCount( cString, [cDelim], [lSkipStrings], [lDoubleQuoteOnly]) | Эта и последующие 3 функции
разбирают строку cString на элементы, разделителем служит cDelim ( по умолчанию - пробел " " ).
Пробел в качестве разделителя имеет ту особенность, что несколько пробелов подряд считаются как один.
Параметр lSkipStrings, если он установлен в .T., указывает, что при разбиении на элементы
следует пропускать строки, т.е., если разделитель находится между кавычками, двойными или одинарными, он
не считается как разделитель; lDoubleQuoteOnly уточняет, что при этом строкой является
только группа символов, заключенная в двойные кавычки. hb_TokenCount() возвращает количество элементов в строке согласно изложенным выше правилам. |
cToken := hb_TokenGet( cString, nToken, [cDelim], [lSkipStrings], [lDoubleQuoteOnly]) | Разбивает строку и возвращает элемент по номеру nToken в соответствии с правилами, изложенными в описании hb_TokenCount(). |
cToken := hb_TokenPtr( cString, @nSkip, [cDelim], [lSkipStrings], [lDoubleQuoteOnly]) | Работает как и hb_TokenGet(), но позволяет последовательно перебирать элементы, не производя разбиение каждый раз заново. Второй параметр nSkip, передаваемый по ссылке, указывает, сколько символов в строке надо пропустить, функция записывает в него новое значение, которое можно использовать при следующем ее вызове. |
aTokens := hb_ATokens( cString, [cDelim], [lSkipStrings], [lDoubleQuoteOnly]) | Разбивает строку и возвращает массив со всеми элементами в соответствии с правилами, изложенными в описании hb_TokenCount(). |
3.14.5 Функции для работы с массивами
aArray := hb_ADel( aArray, nPos [,lChangeSize] ) | Расширение стандартной функции ADel(). Удаляет заданный элемент массива, и, если lChangeSize установлен в .T., еще и уменьшает на 1 его размер - т.е., может использоваться вместо пары ADel(), ASize(). |
aArray := hb_AIns( aArray, nPos [,xItem] [,lChangeSize] ) | Расширение стандартной функции AIns(). Вставляет xItem в заданную позицию массива, и, если lChangeSize установлен в .T., еще и увеличивает на 1 его размер - т.е., может использоваться вместо пары AIns(), ASize(). |
nResult := hb_Ascan( aArray, [block], [nStart], [nCount], [lExact] ) | Расширение стандартной функции Ascan() - делает то же самое, но, если lExact установлен в .T., осуществляет точное сравнение. |
nResult := hb_RAscan( aArray, [block], [nStart], [nCount], [lExact] ) | Аналогична hb_Ascan(), но начинает поиск с конца массива. |
3.14.6 Управление запуском процессов
nResult := hb_ProcessRun( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] ) | |
handle := hb_ProcessOpen( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] ) | |
nResult := hb_ProcessValue( handle, [lWait] ) | |
lResult := hb_ProcessClose( handle, lGentle ) |
3.14.7 Битовые операции
z := hb_BitAnd( x, y ) | Возвращает результат битовой операции И x & y |
z := hb_BitOr( x, y ) | Возвращает результат битовой операции ИЛИ x | y |
z := hb_BitXor( x, y ) | Возвращает результат битовой операции Исключающее ИЛИ x ^^ y |
y := hb_BitNot( x ) | Возвращает результат битовой операции НЕ x |
y := hb_BitTest( x, n ) | Проверяет, установлен ли в 1 n-й ( начиная с 0 ) ,бит числа x |
y := hb_BitSet( x, n ) | Возвращает число с установленным в 1 n-м ( начиная с 0 ) битом числа x |
y := hb_BitReset( x, n ) | Возвращает число со сброшенным в 0 n-м ( начиная с 0 ) битом числа x |
z := hb_BitShift( x, y ) | Битовый сдвиг x << y, если надо x >> y, то y должен быть отрицательным |
y := hb_BitSwapI( x ) | Возвращает результат перестановки байтов в 16-разрядном числе x |
y := hb_BitSwapW( x ) | Возвращает результат перестановки байтов в 16-разрядном unsigned числе x |
y := hb_BitSwapL( x ) | Возвращает результат перестановки байтов в 32-разрядном числе x |
y := hb_BitSwapU( x ) | Возвращает результат перестановки байтов в 32-разрядном unsigned числе x |
3.14.8 Функции для манипуляции переменными
__mvPublic( cVarName ) | Создает Public переменную с именем cVarName |
__mvPrivate( cVarName ) | Создает Private переменную с именем cVarName |
nScope := __mvScope( cVarName ) | Возвращает scope
(диапазон действия, класс) переменной (константы определены в hbmemvar.ch):
HB_MV_NOT_FOUND = переменная не найдена HB_MV_UNKNOWN = переменная не существует, но есть в symbol table HB_MV_ERROR = информация недоступна ( ошибка памяти или аргумента ) HB_MV_PUBLIC = Public переменная HB_MV_PRIVATE_GLOBAL = Private переменная, определена за пределами текущей функции/процедуры HB_MV_PRIVATE_LOCAL = Private переменная, определена в текущей функции/процедуре |
__mvClear() | Освобождает все Public и Private переменные, реализует команду CLEAR MEMORY |
__mvDbgInfo( nScope [,nPosition [,@cVarName]] ) | Возвращает информацию о переменных, соответствующих диапазону nScope ( HB_MV_PUBLIC,... - см.описание __mvScope() ) для отладчика; nPosition - позиция запрашиваемой переменной в диапазоне nScope, cVarName передается по ссылке, в нее записывается имя переменной, если задан nPosition. Возвращаемое функцией значение зависит от передаваемых параметров. Если передан только первый параметр, она возвращает количество переменных заданного nScope, если и второй ( nPosition ), то - значение соответствующей переменной. Если запрашиваемой переменной не существует (nPosition) больше количества переменных, то возвращается Nil, а в cVarName записывается "?". |
lVarExist := __mvExist( cVarName ) | Возвращает .T., если переменная cVarName ( Private или Public ) существует |
xVal := __mvGet( cVarName ) | Возвращает значение переменной cVarName ( Private или Public ) |
xVal := __mvPut( cVarName, xVal ) | Устанавливает значение xVal переменной cVarName ( Private или Public ) |
lIsByRef := hb_IsByRef( @cVarName ) | Проверяет, передана ли cVarName по ссылке, в функцию hb_IsByRef() она должна передаваться по ссылке. |
__mvSetBase() | Это, как называет ее разработчик, hacking function, ее можно использовать очень осторожно и с полным пониманием того, что вы делаете, т.к. она ломает обычное, правильное поведение программы - изменяет базовое смещение списка Private переменных. Вызов этой функции приводит к тому, что Private переменные, созданные в текущей функции, не освобождаются по выходу из нее, а наследуются функцией, из которой текущая была вызвана. |
3.14.9 Функции сравнения по маске
lResult := hb_wildMatch( cPattern, cString, [lExact] ) | Функция сравнивает строку cString с образцом cPattern и возвращает результат - истину или ложь. Образец может содержать символы "?" и "*" ( как известно, на месте "?" в cString может быть один любой символ, на месте "*" - несколько любых символов ). Если lExact - .T., строка целиком должна соответствовать образцу, если - .F. (по умолчанию), то хотя бы ее начало. |
lResult := hb_wildMatchI( cPattern, cString ) | То же, что и hb_wildMatch(), но она нечувствительна к регистру и не имеет третьего параметра - требует точного сравнения. |
hb_filematch( cFile, cPattern ) |
3.14.10 Функции для упаковки/распаковки строк, использующие zlib
cVersion := hb_zlibVersion() | Возвращает строку с номером версии zlib, использующейся в Harbour. |
nBytes := hb_zCompressBound( cData | nDataLen ) | Возвращает максимальную длину упакованной строки, функции передается сама строка cData, которую следует упаковать, или ее длина. |
nBytes := hb_zunCompressLen( cPackedData, [@nResult] ) | Возвращает длину распакованной строки, функции передается упакованная строка cPackedData. Если передать по ссылке nResult, туда запишется результат операции ( 0 - все ОК ). Если возникла ошибка, то вместо длины распакованной строки возвращается -1. |
cPackedData := hb_zCompress( cData, [@cBuffer | nBufLen], [@nResult], [nLevel] ) | Возвращает упакованную строку, функции передается сама строка cData, которую следует упаковать, может также передаваться по ссылке буфер для упаковки (необходимый его размер может быть вычислен предварительно при помощи hb_zCompressBound(), уровень компрессии nLevel, который должен быть между 0 и 9 ( 0 - простое копирование без компрессии, 1 - наилучшая скорость, 9 - наилучшая компрессия. Если передать по ссылке nResult, туда запишется результат операции ( 0 - все ОК ). В случае ошибки функция возвращает Nil. |
cUnPackedData := hb_zunCompress( cPackedData, [@cBuffer | nBufLen], [@nResult] ) | Возвращает распакованную строку, функции передается сама строка cPackedData, которую следует распаковать, может также передаваться по ссылке буфер для распаковки (необходимый его размер может быть вычислен предварительно при помощи hb_zunCompressLen()). Если передать по ссылке nResult, туда запишется результат операции ( 0 - все ОК ). В случае ошибки функция возвращает Nil. |
cError := hb_zError( nError ) | Возвращает строку с описанием ошибки, возникшей при упаковке или распаковке, nError - код, который был записан в nResult при исполнении hb_zCompress()б hb_zunCompress() или hb_zunCompressLen(). |
3.14.11 Idle state
Idle state - это состояние ожидания, когда программа на Harbour ждет ввода от пользователя, с клавиатуры или мыши. Оно возникает как результат вызова функции Inkey() ( команда READ использует Inkey(), так что и при ожидании ввода GET-объектов возникает idle state ). В этом состоянии происходит автоматическая сборка мусора и могут выполняться какие-либо фоновые задачи. Для добавления/удаления этих фоновых задач и предназначены последующие функции (пример использования см. в harbour/tests/testidle.prg):
nHandle := hb_IdleAdd( bAction ) | Кодоблок bAction добавляется в список фоновых задач для исполнения во время idle state. Функция возвращает nHandle, который может быть использован для удаления задачи из списка. |
bAction := hb_IdleDel( nHandle ) | Задача с хэндлом nHandle удаляется из списка фоновых задач, nHandle - значение, возвращенное функцией hb_IdleAdd() при добавлении задачи. Функция возвращает соответствующий задаче кодоблок, или Nil - если такой задачи нет в списке. |
hb_IdleState() | В результате выполнения этой функции программа входит в idle state, выполняет сборку мусора и выполняет одну из фоновых задач списка. В результате последовательного вызова этой функции фоновые задачи выполняются по очереди. Эту функцию имеет смысл вызывать, если у вас идет длительный процесс без состояний ожидания и вам надо прерывать его для выполнения фоновых задач. |
hb_IdleSleep( nSeconds ) | В результате выполнения этой функции программа входит в idle state на nSeconds секунд. В отличие от Inkey(nSeconds) состояние ожидания не прерывается при вводе с клавиатуры. |
3.14.12 JSON - туда и обратно
cJSON := hb_jsonEncode( xValue [, lHuman] ) |
Возвращает JSON строку, полученную преобразованием xValue, lHuman ( по умолчанию - .F. ),
если его значение - .T., делает строку более "читабельной". Кодоблоки записываются в строку как "null", объекты - как массивы данных ( методы не записываются ). |
nLengthDecoded := hb_jsonDecode( cJSON, @xValue ) | Осуществляет обратное преобразование - из JSON строки cJSON в переменную xValue, передаваемую по ссылке. Возвращает количество обработанных символов строки. |
3.14.13 Шифрование
Blowfish | |
cBfKey := hb_blowfishKey( cPasswd ) | Генерирует из пароля cPasswd и возвращает ключ cBfKey для последующего использования в операциях шифрации/дешифрации |
cCipher := hb_blowfishEncrypt( cBfKey, cText [, lRaw ] ) | |
cText := hb_blowfishDecrypt( cBfKey, cCipher [, lRaw ] ) | |
cCipher := hb_blowfishEncrypt_CFB( cBfKey, cText [, cInitSeed ] ) | |
cText := hb_blowfishDecrypt_CFB( cBfKey, cCipher [, cInitSeed ] ) | |
MD5 - шифрование и хэширование | |
cCipher := hb_MD5Encrypt( cText, cPasswd ) | |
cText := hb_MD5Decrypt( cCipher, cPasswd ) | |
cDigest := hb_MD5( cText ) | |
cDigest := hb_MD5File( cFilename ) | |
SHA1, SHA2 | |
cDigest := hb_Sha1( cText [, lRaw ] ) | |
cDigest := hb_HMAC_Sha1( cText, cKey [, lRaw ] ) | |
cDigest := hb_HMAC_SHA224( cText, cKey [, lRaw ] ) | |
cDigest := hb_HMAC_SHA256( cText, cKey [, lRaw ] ) | |
cDigest := hb_HMAC_SHA384( cText, cKey [, lRaw ] ) | |
cDigest := hb_HMAC_SHA512( cText, cKey [, lRaw ] ) |
3.14.14 HiPer-SEEK (Fast Text Search)
Библиотека HiPer-SEEK (hbhsx) - это реализация для Harbour HiPer-SEEK от Six Drivers, которая, в свою очередь, основана на Fast Text Search компании Index Applications.
HiPer-SEEK - это набор функций, с помощью которого можно создать и использовать специальные индексные файлы, позволяющие быстрый поиск текста. Функции HiPer-SEEK могут использоваться для любого источника текстовых данных - dbf-файлов, текстовых файлов, массивов и пр.
Индексы HiPer-SEEK содержат ключи фиксированной длины которые отражают содержание текстовых записей. Текстовые записи могут содержать любые блоки текста. В случае использования .dbf они обычно представляют собой конкатенацию выбранных полей из записи файла. Это может быть также строка или параграф из текстового файла, или даже целый файл. В любом случае, у нас будет одна запись в индексе на каждую текстовую запись.
Индексный файл создается функцией hs_Create(). Она строит заголовок индекса и устанавливает атрибуты, но не добавляет записи в индекс. Это делается функцией hs_Add().
.hsx индекс - это не какое-то преобразование или сжатие текстовых данных. Его ключи "отслеживают" текстовые сигнатуры. Он идентифицирует совпадения как индексные записи, содержащие все сигнатуры или атрибуты строки поиска. Может получиться, что разные строки будут иметь одну и ту же сигнатуру. Это называется здесь алиасами. Поэтому надо проверять каждый результат, возвращаемый hs_Next(). Но, хотя .hsx индекс может ошибочно указать запись как соответствующую условию поиска, он никогда не пропустит действительное совпадение.
HiPer-SEEK позволяет строить системы поиска текста для самых разных приложений. Но надо учитывать следующие факторы. Если текстовые записи очень коротки, короче или почти такие же, как минимальный индексный ключ (16 байт), то индекс будет больше, чем исходные данные. Если же записи слишком велики - 64К, например, то из создания для них сигнатур (максимальная длина - 64 байта) ничего хорошего не выйдет.
n := hs_Add( handle, Expr, [lDel] ) |
Добавляет ключ для тексовой записи в HiPer-SEEK индекс. handle - целочисленный идентификатор HiPer-SEEK индекса.Expr - кодоблок, который будет выполнен для текущей записи .dbf-файла, чтобы извлечь текст для создания ключа, или готовый текст. lDel - устанавливает флаг DELETE на новую запись. Возвращает целое число, представляющее номер записи. В случае ошибки возвращается отрицательное число. Возвращаемые значения начинаются с единицы и последовательно растут. Это внутренний инкрементируемый счетчик указывает позицию записи в индексном файле. При первом после создания индекса вызове hs_Add() возвращает 1, при втором - 2, и т.д. Пример: USE test EXCL h := hs_Open( "SALES.HSX", 10, 1 ) bExpr := { || Trim( test->FIRST ) + " " + Trim( test->LAST ) } DO WHILE !eof() nRec := hs_Add( h, bExpr ) IF nRec < 1 .OR. nRec != RecNo() ? "Error adding record " + LTrim( Str( RecNo() )) + "!" ENDIF SKIP ENDDO |
hs_Close( handle ) |
Закрывает предварительно открытый HiPer-SEEK индекс. handle - целочисленный идентификатор HiPer-SEEK индекса.Все открытые HiPer-SEEK индексы должны быть закрыты с помощью hs_Close() до выхода из программы. |
hs_Create( cFileName, nBufSize, nKeySize, lCase, nFiltSet, xExpr ) |
Создает новый пустой HiPer-SEEK индекс. Если файл с заданным именем уже существует, он будет переписан. cFileName - строка с именем файла и, если надо, путем к нему.nBufSize - целое число, которое, будучи умноженным на 1024, определяет размер буфера, который функции HiPer-SEEK будут использовать для этого индекса. Например, значение 10 отдаст под буфер 10К или 10240 байт плюс еще чуть-чуть для служебной информации. nKeySize - целое число от 1 до 3, определяющее длину ключа индекса - 16, 32 и 64 байта. Чем больше это значение, тем больше будет размер индекса. Чем больше это значение, тем меньше будет количество "алиасов" - фальшивых срабатываний при поиске. Для большинства случаев оптимальным является число 2 ( 32 байта на ключ ). lCase - если .T., то поиск будет регистронезависимым и lower case будет соответствовать обоим lower и upper case. Если же .F., то регистр символов в строке поиска должен совпадать с искомыми. nFiltSet - число, которое определяет преобразование символов от оригинала к индексу. Если 1, то highorder bits будут маскироваться, а все непечатные символы будут преобразованы в пробелы. Очевидно, что это будет работать только для латинского алфавита. Если 2, то все передается как есть. xExpr - индексное выражение, или кодоблок, которое запоминается и позже может быть использовано функциями hs_add(), hs_replace(), hs_filter() и hs_verify() когда эти функции вызываются без строки/кодоблока. Если индексное выражение установлено как строка, то оно сохраняется в заголовке .hsx и может быть извлечено hs_open() Возвращает: при успехе, неотрицательное целое, HiPer-SEEK идентификатор индекса. В противном случае - отрицательное число, код ошибки. lCase устанавливается во время создания индекса и не может быть изменено позже. В отличие от lCase, nBufSize можно устанавливать в другое значение при открытии. nBufSize определяет размер буфера памяти для индекса, это не параметр индекса. Поэтому nBufSize есть в списке аргументов для обеих функций, hs_Create() и hs_Open(), а lCase - нет. В общем случае, чем больше буфер, тем быстрее будет построен индекс. Значение nKeySize влияет на скорость создания и извлечения индекса. При значении 2 каждый ключ занимает 32 байта в памяти. Если общее количество индексировнный записей * 32 меньше, чем nBufSize * 1024, то весь индекс уместится в буфере, что существенно ускорит добавление и поиск. В противном случае постоянно будет требоваться запись/чтение с диска. Пример: h := hs_Create( "myindex.hsx", 8, 3, .T., 2 ) IF h < 0 ? "Error creating HiPer-SEEK index!" ENDIF |
hs_Delete( handle, nVal ) |
Deletes specifed index record from HiPer-SEEK index file. handle - целочисленный идентификатор HiPer-SEEK индекса.nVal - Целое число, представляющее номер записи для удаления. Возвращает 1 при успешном завершении или отрицательный код ошибки. hs_Delete() устанавливает флаг удаления на конкретную запись индекса. Физически она не удаляется, только помечается на удаление. Hb_next() пропускает такую запись. Пример: USE sales EXCL h := hs_Open( "sales.hsx", 10, 1 ) DO WHILE !eof() IF deleted() nVal := hs_Delete( h, RecNo() ) IF nVal < 1 ? "HiPer-SEEK delete error: " + LTrim( Str( RecNo() )) ENDIF ENDIF SKIP ENDDO |
hs_Filter( cIndex, cVal, [cExpr], [nBufSize], [nOpenMode] ) |
Устанавливает фильтр, используя HiPer-SEEK индекс. Эта функция может использоваться только если индекс построен по .dbf файлу и требует RDD с record map (RM). cIndex - имя Hiper-SEEK индексного файла или идентификатор уже открытого индекса. Расширение по умолчанию - .hsxcVal - разделенная пробелами строка из одного или нескольких слов для поиска. cExpr - выражение (текстовое или кодоблок) для проверки результатов. nBufSize - Размер буфера памяти в килобайтах, по умолчанию - 8. nOpenMode - Режим открытия индекса, по умолчанию - 2. 0 = READ-WRITE + SHARED 1 = READ-WRITE + EXCLUSIVE 2 = READ-ONLY + SHARED 3 = READ-ONLY + EXCLUSIVE Возвращает количество записей, удовлетворяющих условию фильтра. Пример: #include "inkey.ch" LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL nCount := 0, lStrict := .F. LOCAL cSearch := "Steve John Robert" LOCAL GetList := {} CLS USE test EXCL IF !file("TEST.HSX") @ 0,0 "Building HiPer-SEEK Index..." hs_Index( "TEST.HSX", cExpr, 2 ) ?? "Done!" Inkey(1) CLS ENDIF WHILE .T. cSearch := PadR( cSearch, 59 ) @ 0,0 SAY "Search Values.....:" GET cSearch @ 1,0 SAY "Strict Match (Y/N):" GET lStrict PICTURE "Y" READ IF LastKey() == K_ESC CLS EXIT ENDIF cSearch := AllTrim( cSearch ) @ 3,0 SAY "Setting HiPer-SEEK Filter ..." nCount := hs_Filter( "TEST.HSX", cSearch, iif( lStrict, cExpr, "" )) ?? "Done!" @ 4,0 SAY LTrim( Str( nCount )) + " records meeting filter condition." @ 6,0 SAY "Press any key to browse the matching records..." Inkey(0) GO TOP Browse() CLS ENDDO |
hs_IfDel( handle, nVal ) |
Determines if a HiPer-SEEK record has been marked as deleted. handle - целочисленный идентификатор HiPer-SEEK индекса.nVal - Целое число, представляющее номер проверяемой записи. Возвращает 1 если запись помечена на удаление, 0 - если нет. Отрицательное значение указывает на ошибку. |
hs_Index( cFileName, cExpr, [nKeySize], [nOpenMode], [nBufSize], [lCase], [nFiltSet] ) |
Создает и заполняет новый HiPer-SEEK индекс. Эта функция может использоваться только если индекс построен по .dbf файлу. cFileName - строка с именем файла и, если надо, путем к нему.nKeySize - целое число от 1 до 3, определяющее длину ключа индекса - 16, 32 и 64 байта. Чем больше это значение, тем больше будет размер индекса. Чем больше это значение, тем меньше будет количество "алиасов" - фальшивых срабатываний при поиске. Для большинства случаев оптимальным является число 2 ( 32 байта на ключ ). nOpenMode - Режим открытия индекса - режим, в котором индекс будет работать после создания. Во время создания это всегда READ-WRITE + EXCLUSIVE. Режим по умолчанию - 1. 0 = READ-WRITE + SHARED 1 = READ-WRITE + EXCLUSIVE 2 = READ-ONLY + SHARED 3 = READ-ONLY + EXCLUSIVE nBufSize - целое число, которое, будучи умноженным на 1024, определяет размер буфера, который функции HiPer-SEEK будут использовать для этого индекса. Например, значение 10 отдаст под буфер 10К или 10240 байт плюс еще чуть-чуть для служебной информации. lCase - если .T., то поиск будет регистронезависимым и lower case будет соответствовать обоим lower и upper case. Если же .F., то регистр символов в строке поиска должен совпадать с искомыми. nFiltSet - число, которое определяет преобразование символов от оригинала к индексу. Если 1, то highorder bits будут маскироваться, а все непечатные символы будут преобразованы в пробелы. Очевидно, что это будет работать только для латинского алфавита. Если 2, то все передается как есть. Возвращает 1 при успешном завершении или отрицательный код ошибки. Пример: LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL h := 0 USE test EXCL // Build HiPer-SEEK index using 64 byte index record, READ-WRITE + SHARED // mode, using a 16k buffer. h := hs_Index( "TEST.HSX", cExpr, 3, 0, 16 ) hs_Close( h ) |
hs_KeyCount( handle ) |
Возвращает количество записей в HiPer-SEEK индексе. handle - целочисленный идентификатор HiPer-SEEK индекса.Возвращает целое число, большее или равное нулю - количество записей в индексе, включая удаленные. Отрицательное число указывает на ошибку. |
hs_Next( handle ) |
Вызывается после hs_Set() для получения результатов поиска по индексу. Первый вызов возвращает номер первой записи, соответствующей условию поиска, каждый следующий вызов - номер следующей записи, и т.д. Когда все подходящие записи будут перебраны, функция вернет 0. Пример: // Lists all record numbers that contain the word "John" anywhere in // either the FIRST, LAST, STREET, or CITY fields. Uses hs_Verify() // to prevent "fuzzy" matches. LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL bExpr := &( "{||" + cExpr + "}" ) LOCAL cVal := "John", h := 0, nRec := 0 CLS USE test EXCL IF !File("TEST.HSX") ? "Building HiPer-SEEK Index..." h := hs_Index( "TEST.HSX", cExpr, 2 ) ELSE h := hs_Open( "TEST.HSX", 8, 1 ) ENDIF hs_Set( h, cVal ) nRec := hs_Next( h ) DO WHILE nRec > 0 dbGoto( nRec ) IF hs_Verify( bExpr, cVal ) ? nRec ENDIF nRec := hs_Next( h ) ENDDO hs_Close( h ) |
hs_Open( cFileName, nBufSize, nOpenMode ) |
Открывает существующий HiPer-SEEK индекс. cFileName - строка с именем файла и, если надо, путем к нему.nBufSize - целое число, которое, будучи умноженным на 1024, определяет размер буфера, который функции HiPer-SEEK будут использовать для этого индекса. Например, значение 10 отдаст под буфер 10К или 10240 байт плюс еще чуть-чуть для служебной информации. Производительность будет максимальной, если весь индекс уместится в этот буфер. nOpenMode - Режим открытия индекса. HiPer-SEEK индекс может быть открыт в режимах чтения/записи (READ-WRITE) или только для чтения (READ-ONLY), в монопольном (EXCLUSIVE) или для совместного использования (SHARED). 0 READ-WRITE + SHARED 1 READ-WRITE + EXCLUSIVE 2 READ-ONLY + SHARED 3 READ-ONLY + EXCLUSIVE Возвращает: при успехе, неотрицательное целое, HiPer-SEEK идентификатор индекса. В противном случае - отрицательное число, код ошибки. Пример: USE test EXCL h := hs_Open( "LOOKUP.HSX", 16, 1 ) IF h < 0 ? "Error opening HiPer-SEEK index!" ENDIF |
hs_Replace( handle, Expr, nVal, lDel ) |
Заменяет индексную запись с номером nVal на новое значение Expr. There must be at least nVal records already indexed. handle - целочисленный идентификатор HiPer-SEEK индекса.Expr - кодоблок, который будет выполнен для текущей записи .dbf-файла, чтобы извлечь текст для создания ключа, или готовый текст. nVal - Целое число, представляющее номер записи для замены. lDel - устанавливает флаг DELETE на запись. Возвращает 1 при успешном завершении или отрицательный код ошибки. Пример: USE test EXCL h := hs_Open( "NAMES.HSX", 8, 1 ) GOTO 10 cStr := Trim( test->FIRST ) + " " + Trim( test->LAST ) IF hs_Replace( h, cStr, RecNo() ) < 1 ? "HiPer-SEEK replace error on record #" + LTrim( Str( RecNo() )) ENDIF |
hs_Set( handle, cExpr ) |
Подготавливает параметры поиска для последующего вызова hs_Next(). handle - целочисленный идентификатор HiPer-SEEK индекса.cExpr - строка, содержащая условия поиска, представляющая собой одну или несколько подстрок, разделенных пробелами. Например, строка "ed cat" будет искать все записи, содержащие оба слова "ed" и "cat" в любом месте и в любом порядке. Так, обе строки "ed has a cat" и "The catalog was spray painted" будут правильными результатами поиска. Для успешного поиска все подстроки в строке поиска должны присутствовать в изначальной индексированной строке. Возвращает 1 при успешном завершении или отрицательный код ошибки. Пример: h := hs_Open( "LOOKUP.HSX", 4, 1 ) hs_Set( h, "Thompson" ) nRec := hs_Next( h ) ? "The first record conaining 'Thompson' is #" + LTrim( Str( nRec )) hs_Close( h ) |
hs_UnDelete( handle, nVal ) |
Снимает пометку удаления с записи HiPer-SEEK индекса. handle - целочисленный идентификатор HiPer-SEEK индекса.nVal - Целое число, представляющее номер записи для восстановления. Возвращает 1 при успешном завершении или отрицательный код ошибки. |
hs_Verify( bSource, cValue ) |
Verify a target string against the .DBF source string, passed as a code block. Эта функция может использоваться только если индекс построен по .dbf файлу. bSource - проверочный кодоблок.cValue - значение, которое проверяется с помощью bSource - обычно это подстрока из строки поиска hb_Set(). Возвращает .T. если cValue содержится в Eval(bSource), иначе - .F. Пример: // Lists all record numbers that contain the word "John" anywhere in // either the FIRST, LAST, STREET, or CITY fields. Uses hs_Verify() // to prevent "fuzzy" matches. LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL bExpr := &( "{||" + cExpr + "}" ) LOCAL cVal := "John", h := 0, nRec := 0 CLS USE test EXCL IF !file("TEST.HSX") ? "Building HiPer-SEEK Index..." h := hs_Index( "TEST.HSX", cExpr, 2 ) ELSE h := hs_Open( "TEST.HSX", 8, 1 ) ENDIF hs_Set( h, cVal ) nRec := hs_Next( h ) DO WHILE nRec > 0 dbGoto( nRec ) IF hs_Verify( bExpr, cVal ) ? nRec ENDIF nRec := hs_Next( h ) ENDDO hs_Close( h ) |
hs_Version() |
Возвращает строку с информацией о версии. |
3.14.15 Разные функции - не знаю, в какую группу их включить
xResult := hb_ExecFromArray( cFuncName [,aParams] ) |
Возвращает результат исполнения функции, заданной по имени cFuncName с
параметрами, переданными в массиве aParams, например:
? hb_execFromArray( "Str", {11,6,3} ) |
xResult := hb_ExecFromArray( @funcName [,aParams] ) |
Возвращает результат исполнения функции, заданной с помощью указателя @funcName с
параметрами, переданными в массиве aParams, например:
hfunc := @Str() ? hb_execFromArray( hfunc, {11,6,3} ) |
xResult := hb_ExecFromArray( bCodeBlock [,aParams] ) |
Возвращает результат исполнения кодоблока bCodeBlock с
параметрами, переданными в массиве aParams:
? hb_execFromArray( {|n1,n2,n3|Str(n1,n2,n3)}, { 11,6,3 } ) |
xResult := hb_ExecFromArray( oObject, cMethodName [,aParams] ) | Возвращает результат исполнения метода cMethodName объекта oObject с параметрами, переданными в массиве aParams, метод задан по имени. |
xResult := hb_ExecFromArray( oObject, @msgName [,aParams] ) | Возвращает результат исполнения метода объекта oObject с параметрами, переданными в массиве aParams, метод задан с помощью указателя @msgName |
xResult := hb_ExecFromArray( aExecArray ) | В этом варианте
использования hb_ExecFromArray() параметры передаются в виде массива:
? hb_execFromArray( { "Str", 11,6,3 } ) ? hb_execFromArray( { hfunc, 11,6,3 } ) ? hb_execFromArray( { {|n1,n2,n3|Str(n1,n2,n3)},11,6,3 } ) и т.д. |
hb_ForNext( nStart, nEnd | bEnd, bCode [, nStep ] ) | Реализация цикла FOR ... NEXT в виде функции.
Кодоблок bCode, которому передается счетчик ({|nIndex|...code...} ), выполняется многократно
при инкременте счетчика от nStart до nEnd (или до того, как кодоблок bEnd вернет .T.) с шагом nStep.
|
cSep := hb_ps() | Возвращает символ, разделяющий каталоги в пути, принятый в ОС ( "/", "\") |
cSep := hb_osDriveSeparator() | |
cExePath := hb_Progname() | Возвращает путь и имя исполняемого файла. |
aDir := hb_Directory() | То же самое, что и Directory(), но возвращает значение типа DateTime вместо Date в третьем элементе субмассивов. |
cDirBase := hb_DirBase() | Возвращает базовый каталог - тот, где находится исполняемый файл. |
cDirName := hb_DirTemp() | Возвращает путь к системному временному каталогу. |
lResult := hb_DirExists( cDir ) | Возвращает логическое значение, .T. - если каталог cDir существует, .F. - если нет. |
nResult := hb_DirCreate( cDir ) | Создает каталог cDir. Если успешно, возвращает 0. |
lResult := hb_DirBuild( cDir ) | Создает каталог cDir и все родительские каталоги, если они не существуют. Если успешно, возвращает .T., если нет - .F.. |
nResult := hb_DirDelete( cDir ) | удаляет каталог cDir. Если успешно, возвращает 0. |
lResult := hb_DirRemoveAll( cDir ) | |
n := hb_Rand32() | Возвращает случайное целое число в диапазоне от 0 до 0xFFFFFFFF. |
n := hb_RandomInt( [n1,] [n2] ) | Возвращает случайное целое число. Необязательные параметры n1, n2 - диапазон; если n2 не задан, то возвращаемое значение - из диапазона между 0 и n1; если же оба параметра не заданы, то - из диапазона 0,1. |
n := hb_Random( [n1,] [n2] ) | Возвращает случайное вещественное число. Необязательные параметры n1, n2 - диапазон; если n2 не задан, то возвращаемое значение - из диапазона между 0 и n1; если же оба параметра не заданы, то - из диапазона между 0 и 1. |
hb_RandomSeed( n ) | Инициирует генерацию новой последовательности случайных чисел. Если параметр n равен 0, используется текущее время в миллисекундах и HVM stack address (в мультипоточном режиме), если же это другое число, то используется именно оно - на случай, если вам нужны повторяющиеся серии, возвращаемые hb_Random() и hb_RandomInt(). |