From a6680919cfe75604df94a6532dc38f223097eec1 Mon Sep 17 00:00:00 2001 From: Przemyslaw Czerpak Date: Wed, 23 Jan 2013 07:56:21 +0000 Subject: [PATCH] 2013-01-23 08:56 UTC+0100 Przemyslaw Czerpak (druzus/at/poczta.onet.pl) * harbour/src/vm/classes.c * updated some comments * renamed hb_setClsHandle() to __objSetClassHandle() old function name covered by HB_LEGACY_LEVEL5 macro * harbour/src/rtl/itemseri.c + added support for deserialization xHarbour HB_SERIALIZE() output. All types except codeblocks are supported. I haven't added support for xHarbour serialized data with cyclic references. If it will be really necessary then I can implement it. I also added workaround for bug in xHarbour serialization code so now Harbour correctly decodes data with LONGLONG numbers though xHarbour cannot correctly decode its own stream. Now Harbour can deserialize xHarbour data encoded by HB_SERIALIZE() and stored somewhere. It can be important in migration process, i.e. SQLRDD uses HB_SERIALIZE() to encode data in memos so now SQLRDD port for Harbour should read old tables and decode xHarbour items correctly. The same is for any other tool which saved HB_SERIALIZE() output in xHarbour. * harbour/ChangeLog.txt * minor update --- harbour/ChangeLog.txt | 31 +++- harbour/src/rtl/itemseri.c | 291 +++++++++++++++++++++++++++++++++++-- harbour/src/vm/classes.c | 65 +++++---- 3 files changed, 341 insertions(+), 46 deletions(-) diff --git a/harbour/ChangeLog.txt b/harbour/ChangeLog.txt index c8876de807..879c49445d 100644 --- a/harbour/ChangeLog.txt +++ b/harbour/ChangeLog.txt @@ -10,6 +10,30 @@ * Change, ! Fix, % Optimization, + Addition, - Removal, ; Comment */ +2013-01-23 08:56 UTC+0100 Przemyslaw Czerpak (druzus/at/poczta.onet.pl) + * harbour/src/vm/classes.c + * updated some comments + * renamed hb_setClsHandle() to __objSetClassHandle() + old function name covered by HB_LEGACY_LEVEL5 macro + + * harbour/src/rtl/itemseri.c + + added support for deserialization xHarbour HB_SERIALIZE() output. + All types except codeblocks are supported. I haven't added support + for xHarbour serialized data with cyclic references. If it will be + really necessary then I can implement it. + I also added workaround for bug in xHarbour serialization code so + now Harbour correctly decodes data with LONGLONG numbers though + xHarbour cannot correctly decode its own stream. + Now Harbour can deserialize xHarbour data encoded by HB_SERIALIZE() + and stored somewhere. It can be important in migration process, i.e. + SQLRDD uses HB_SERIALIZE() to encode data in memos so now SQLRDD + port for Harbour should read old tables and decode xHarbour items + correctly. The same is for any other tool which saved HB_SERIALIZE() + output in xHarbour. + + * harbour/ChangeLog.txt + * minor update + 2013-01-23 00:59 UTC+0100 Viktor Szakats (harbour syenar.net) * utils/hbmk2/hbmk2.prg ! FindInPath(): fixed for filenames with an empty @@ -21719,12 +21743,12 @@ serialized by default serialization code used by both compilers to generate expressions. In Harbour it's done by hb_valToExp() function and - in xHarbour it's ValToPrg() is used. + in xHarbour ValToPrg() is used. ValToPrg() does not create valid macrocompiler expressions for arrays and objects so HBPersistent files created by xHarbour are broken and cannot be correctly deserialized. - It happens if objects has hash arrays in instance variables - and these hash arrays contain normal arrays or object + It happens if object has hash arrays in instance variables + and these hash arrays contain normal arrays or objects. If Harbour application restores such xHarbour HBPersistent file then RTE "Syntax error: &" is generated. ; xHarbour encapsulates deserialization code inside TRY/CATCH/END @@ -21749,6 +21773,7 @@ HBPersistent:LoadFromFile(): If necessary we can implement it though it's usable only if we want to ignore some wrong lines and process others. + [Update: support for have been added] ; Warning: Neither Harbour nor xHarbour supports arrays and objects with cyclic references in HBPersistent code - infinite loop appears in such case. diff --git a/harbour/src/rtl/itemseri.c b/harbour/src/rtl/itemseri.c index d74588a059..e42691e38c 100644 --- a/harbour/src/rtl/itemseri.c +++ b/harbour/src/rtl/itemseri.c @@ -55,6 +55,9 @@ #include "hbapicls.h" #include "hbapicdp.h" +#include "hbvm.h" +#include "hbstack.h" + /* HB_UCHAR [ 1 ] - item type @@ -67,7 +70,7 @@ HB_UCHAR [ 1 ] - item type 6. INT24 3 7. INT32 4 8. INT64 8 - 9. DOUBLE IEE754 LE 8 + 9. DOUBLE IEEE754 LE 8 10. DATE 3 11. STRING8 1+n 12. STRING16 2+n @@ -100,6 +103,27 @@ HB_UCHAR [ 1 ] - item type 39. TIMESTAMP 8 40. HASHFLAGS 2 41. HASHDEFAULT VALUE 0 + +xHarbour types HB_SERIAL_XHB_*: + 67. 'C' 8+n + 76. 'L' 'T'|'F' 1 + 78. 'N' 'I' 1+8 + 78. 'N' 'L' 1+8 + 78. 'N' 'X' 1+8 + 78. 'N' 'D'1+8 + 68. 'D' 8 + 84. 'T' 8 + 90. 'Z' 0 +complex ones: + 65. 'A' 8+n ,... + 66. 'B' 1+n (HB_SaveBlock()) + 72. 'H' 8+n ,... + 79. 'O' 8+n ,,... (__ClsGetPropertiesAndValues()) + 81. 'Q' 8+n ,HBPersistent:SaveToText(raw) + 82. 'R' 'A' 1+8 (index to array of arrays) + 82. 'R' 'O' 1+8 (index to array of objects) + 82. 'R' 'H' 1+8 (index to array of hashes) + 82. 'R' 'B' 1+8 (index to array of codeblock) */ #define HB_SERIAL_NIL 0 @@ -144,6 +168,20 @@ HB_UCHAR [ 1 ] - item type #define HB_SERIAL_TIMESTAMP 39 #define HB_SERIAL_HASHFLAGS 40 #define HB_SERIAL_HASHDEFVAL 41 +/* xHarbour types */ +#define HB_SERIAL_XHB_A 65 +#define HB_SERIAL_XHB_B 66 +#define HB_SERIAL_XHB_C 67 +#define HB_SERIAL_XHB_D 68 +#define HB_SERIAL_XHB_H 72 +#define HB_SERIAL_XHB_L 76 +#define HB_SERIAL_XHB_O 79 +#define HB_SERIAL_XHB_Q 81 +#define HB_SERIAL_XHB_R 82 +#define HB_SERIAL_XHB_N 78 +#define HB_SERIAL_XHB_T 84 +#define HB_SERIAL_XHB_Z 90 + #define HB_SERIAL_DUMMYOFFSET ( ( HB_SIZE ) -1 ) @@ -825,7 +863,7 @@ static HB_SIZE hb_deserializeItem( PHB_ITEM pItem, const HB_UCHAR * pBuffer, HB_SIZE nOffset, PHB_CYCLIC_REF pRef ) { - HB_SIZE nLen, ulPad, nSize; + HB_SIZE nLen, nPad, nSize; char * szVal; switch( pBuffer[ nOffset++ ] ) @@ -958,42 +996,42 @@ static HB_SIZE hb_deserializeItem( PHB_ITEM pItem, break; case HB_SERIAL_STRPAD8: nSize = pBuffer[ nOffset++ ]; - ulPad = pBuffer[ nOffset++ ]; + nPad = pBuffer[ nOffset++ ]; nLen = hb_cdpnDupLen( ( const char * ) &pBuffer[ nOffset ], nSize, cdpIn, cdpOut ); - szVal = ( char * ) hb_xgrab( nLen + ulPad + 1 ); + szVal = ( char * ) hb_xgrab( nLen + nPad + 1 ); hb_cdpnDup2( ( const char * ) &pBuffer[ nOffset ], nSize, szVal, &nLen, cdpIn, cdpOut ); - memset( szVal + nLen, ' ', ulPad ); - hb_itemPutCLPtr( pItem, szVal, nLen + ulPad ); + memset( szVal + nLen, ' ', nPad ); + hb_itemPutCLPtr( pItem, szVal, nLen + nPad ); nOffset += nSize; break; case HB_SERIAL_STRPAD16: nSize = HB_GET_LE_UINT16( &pBuffer[ nOffset ] ); nOffset += 2; - ulPad = HB_GET_LE_UINT16( &pBuffer[ nOffset ] ); + nPad = HB_GET_LE_UINT16( &pBuffer[ nOffset ] ); nOffset += 2; nLen = hb_cdpnDupLen( ( const char * ) &pBuffer[ nOffset ], nSize, cdpIn, cdpOut ); - szVal = ( char * ) hb_xgrab( nLen + ulPad + 1 ); + szVal = ( char * ) hb_xgrab( nLen + nPad + 1 ); hb_cdpnDup2( ( const char * ) &pBuffer[ nOffset ], nSize, szVal, &nLen, cdpIn, cdpOut ); - memset( szVal + nLen, ' ', ulPad ); - hb_itemPutCLPtr( pItem, szVal, nLen + ulPad ); + memset( szVal + nLen, ' ', nPad ); + hb_itemPutCLPtr( pItem, szVal, nLen + nPad ); nOffset += nSize; break; case HB_SERIAL_STRPAD32: nSize = HB_GET_LE_UINT32( &pBuffer[ nOffset ] ); nOffset += 4; - ulPad = HB_GET_LE_UINT32( &pBuffer[ nOffset ] ); + nPad = HB_GET_LE_UINT32( &pBuffer[ nOffset ] ); nOffset += 4; nLen = hb_cdpnDupLen( ( const char * ) &pBuffer[ nOffset ], nSize, cdpIn, cdpOut ); - szVal = ( char * ) hb_xgrab( nLen + ulPad + 1 ); + szVal = ( char * ) hb_xgrab( nLen + nPad + 1 ); hb_cdpnDup2( ( const char * ) &pBuffer[ nOffset ], nSize, szVal, &nLen, cdpIn, cdpOut ); - hb_xmemset( szVal + nLen, ' ', ulPad ); - hb_itemPutCLPtr( pItem, szVal, nLen + ulPad ); + hb_xmemset( szVal + nLen, ' ', nPad ); + hb_itemPutCLPtr( pItem, szVal, nLen + nPad ); nOffset += nSize; break; @@ -1077,6 +1115,153 @@ static HB_SIZE hb_deserializeItem( PHB_ITEM pItem, break; } + /* xHarbour types */ + case HB_SERIAL_XHB_C: + nSize = nLen = ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ); + nOffset += 8; + szVal = hb_cdpnDup( ( const char * ) &pBuffer[ nOffset ], &nLen, + cdpIn, cdpOut ); + hb_itemPutCLPtr( pItem, szVal, nLen ); + nOffset += nSize; + break; + case HB_SERIAL_XHB_L: + hb_itemPutL( pItem, pBuffer[ nOffset++ ] == 'T' ); + break; + case HB_SERIAL_XHB_N: + switch( pBuffer[ nOffset++ ] ) + { + case 'I': + hb_itemPutNI( pItem, ( int ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ) ); + break; + case 'L': + hb_itemPutNL( pItem, ( long ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ) ); + break; + case 'X': + hb_itemPutNInt( pItem, ( HB_MAXINT ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ) ); + /* this is workaround for bug in xHarbour serialization code */ + nOffset += 10; + break; + case 'D': + hb_itemPutND( pItem, HB_GET_LE_DOUBLE( &pBuffer[ nOffset ] ) ); + break; + default: + hb_itemClear( pItem ); + break; + } + nOffset += 8; + break; + case HB_SERIAL_XHB_D: + hb_itemPutDL( pItem, ( long ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ) ); + nOffset += 8; + break; + case HB_SERIAL_XHB_T: + hb_itemPutTD( pItem, HB_GET_LE_DOUBLE( &pBuffer[ nOffset ] ) ); + nOffset += 8; + break; + case HB_SERIAL_XHB_Z: + hb_itemClear( pItem ); + break; + case HB_SERIAL_XHB_A: + nLen = ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ); + nOffset = hb_deserializeArray( pItem, cdpIn, cdpOut, pBuffer, + nOffset + 8, nLen, pRef ); + break; + case HB_SERIAL_XHB_B: + nOffset = hb_deserializeItem( pItem, cdpIn, cdpOut, pBuffer, + nOffset, pRef ); + /* we do not support codeblock deserialization: HB_RestoreBlock( pItem ) */ + hb_itemClear( pItem ); + break; + case HB_SERIAL_XHB_H: + nLen = ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ); + nOffset = hb_deserializeHash( pItem, cdpIn, cdpOut, pBuffer, + nOffset + 8, nLen, pRef ); + hb_hashSetFlags( pItem, HB_HASH_KEEPORDER | HB_HASH_RESORT ); + break; + case HB_SERIAL_XHB_O: + { + HB_USHORT uiClass; + + nLen = ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ); + /* deserialize :className */ + nOffset = hb_deserializeItem( pItem, cdpIn, cdpOut, pBuffer, + nOffset + 8, pRef ); + /* find class handle */ + uiClass = hb_clsFindClass( hb_itemGetCPtr( pItem ), NULL ); + if( uiClass && hb_vmRequestReenter() ) + { + PHB_ITEM pMsg = hb_stackAllocItem(), + pVal = hb_stackAllocItem(); + + hb_clsAssociate( uiClass ); + hb_itemMove( pItem, hb_stackReturnItem() ); + + while( nLen-- ) + { + nOffset = hb_deserializeItem( pMsg, cdpIn, cdpOut, pBuffer, + nOffset, pRef ); + nOffset = hb_deserializeItem( pVal, cdpIn, cdpOut, pBuffer, + nOffset, pRef ); + if( hb_vmRequestQuery() == 0 ) + { + char szMsg[ HB_SYMBOL_NAME_LEN ]; + hb_snprintf( szMsg, sizeof( szMsg ), "_%s", hb_itemGetCPtr( pMsg ) ); + hb_objSendMsg( pItem, szMsg, 1, pVal ); + } + } + hb_stackPop(); + hb_stackPop(); + hb_vmRequestRestore(); + } + else + { + while( nLen-- ) + { + nOffset = hb_deserializeItem( pItem, cdpIn, cdpOut, pBuffer, + nOffset, pRef ); + nOffset = hb_deserializeItem( pItem, cdpIn, cdpOut, pBuffer, + nOffset, pRef ); + } + hb_itemClear( pItem ); + } + break; + } + case HB_SERIAL_XHB_Q: + { + HB_USHORT uiClass; + + nPad = ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ) + nOffset + 8; + /* deserialize :className */ + nOffset = hb_deserializeItem( pItem, cdpIn, cdpOut, pBuffer, + nOffset + 8, pRef ); + nLen = nPad - nOffset; + /* get serialized HBPERSISTENT text */ + szVal = hb_cdpnDup( ( const char * ) &pBuffer[ nOffset ], &nLen, + cdpIn, cdpOut ); + nOffset = nPad; + /* find class handle */ + uiClass = hb_clsFindClass( hb_itemGetCPtr( pItem ), NULL ); + hb_itemPutCLPtr( pItem, szVal, nLen ); + if( uiClass && hb_vmRequestReenter() ) + { + hb_clsAssociate( uiClass ); + hb_vmPushDynSym( hb_dynsymGetCase( "LOADFROMTEXT" ) ); + hb_vmPush( hb_stackReturnItem() ); + hb_vmPush( pItem ); + hb_vmPushLogical( HB_TRUE ); + hb_itemMove( pItem, hb_stackReturnItem() ); + hb_vmSend( 2 ); + hb_vmRequestRestore(); + } + else + hb_itemClear( pItem ); + break; + } + case HB_SERIAL_XHB_R: + /* nIndex = ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ nOffset ] ); */ + /* TODO: add support for references */ + nOffset =+ 9; + default: hb_itemClear( pItem ); break; @@ -1251,6 +1436,84 @@ static HB_BOOL hb_deserializeTest( const HB_UCHAR ** pBufferPtr, HB_SIZE * pnSiz nSize = 1; nLen = 2; break; + + /* xHarbour types */ + case HB_SERIAL_XHB_C: + nSize = 9 + ( nSize >= 9 ? ( HB_SIZE ) HB_GET_BE_UINT64( pBuffer ) : nSize ); + break; + case HB_SERIAL_XHB_L: + nSize = 2; + break; + case HB_SERIAL_XHB_N: + if( nSize >= 2 && *pBuffer == 'X' ) + /* this is workaround for bug in xHarbour serialization code */ + nSize = 20; + else + nSize = 10; + break; + case HB_SERIAL_XHB_D: + case HB_SERIAL_XHB_T: + nSize = 9; + break; + case HB_SERIAL_XHB_Z: + nSize = 1; + break; + case HB_SERIAL_XHB_A: + if( nSize >= 9 ) + { + nSize = 9; + nLen = ( HB_SIZE ) HB_GET_BE_UINT64( pBuffer ); + } + else + nSize++; + break; + case HB_SERIAL_XHB_B: + nSize = 1; + nLen = 1; + break; + case HB_SERIAL_XHB_H: + if( nSize >= 9 ) + { + nSize = 9; + nLen = ( HB_SIZE ) HB_GET_BE_UINT64( pBuffer ) << 1; + } + else + nSize++; + break; + case HB_SERIAL_XHB_O: + if( nSize >= 9 ) + { + nSize = 9; + nLen = ( ( HB_SIZE ) HB_GET_BE_UINT64( pBuffer ) << 1 ) + 1; + } + else + nSize++; + break; + case HB_SERIAL_XHB_Q: + if( nSize >= 18 && pBuffer[ 8 ] == HB_SERIAL_XHB_C ) + { + HB_SIZE nData = ( HB_SIZE ) HB_GET_BE_UINT64( pBuffer ); + if( nData >= 9 && nData - 9 >= + ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ 9 ] ) ) + nSize = 9 + nData; + else + nSize++; + } + else + nSize++; + nSize = 9 + ( nSize >= 9 ? ( HB_SIZE ) HB_GET_BE_UINT64( pBuffer ) : nSize ); + break; + case HB_SERIAL_XHB_R: + if( nSize >= 10 ) + { + nSize = 10; + /* nIndex = ( HB_SIZE ) HB_GET_BE_UINT64( &pBuffer[ 1 ] ); */ + /* TODO: add support for references */ + } + else + nSize++; + break; + default: nSize = 1; break; diff --git a/harbour/src/vm/classes.c b/harbour/src/vm/classes.c index b623af50c2..8066d711d7 100644 --- a/harbour/src/vm/classes.c +++ b/harbour/src/vm/classes.c @@ -3566,9 +3566,7 @@ static HB_USHORT hb_clsNew( const char * szClassName, HB_USHORT uiDatas, } /* - * hb_clsNew( , < ) -> - * - * := __clsNew( , , [], [], [] ) + * __clsNew( , , [], [], [] ) -> * * Create a new class * @@ -3618,9 +3616,9 @@ HB_FUNC( __CLSNEW ) } /* - * __clsAddFriend( , ) + * __clsAddFriend( , ) * - * Add friend function + * Add friend function */ HB_FUNC( __CLSADDFRIEND ) { @@ -4112,7 +4110,7 @@ HB_FUNC( __CLSASSOCTYPE ) } /* - * __ClsCntClasses() -> + * __clsCntClasses() -> * * Return number of classes */ @@ -4274,7 +4272,7 @@ HB_FUNC( __CLSPARENT ) hb_clsIsParent( ( HB_USHORT ) hb_parni( 1 ), szParentName ) ); } -/* __Sender() -> | NIL +/* __sender() -> | NIL * returns sender object */ HB_FUNC( __SENDER ) @@ -4356,7 +4354,7 @@ HB_FUNC( __CLSSYNCWAIT ) } /* - * __ClassH( ) -> + * __classH( ) -> * * Returns class handle of */ @@ -5035,7 +5033,7 @@ void hb_mthAddTime( HB_ULONG ulClockTicks ) } #endif -/* ( nClass, cMsg ) --> aMethodInfo { nTimes, nTime } */ +/* __getMsgPrf( nClass, cMsg ) --> aMethodInfo { nTimes, nTime } */ HB_FUNC( __GETMSGPRF ) /* profiler: returns a method called and consumed times */ { HB_STACK_TLS_PRELOAD @@ -5067,7 +5065,7 @@ HB_FUNC( __GETMSGPRF ) /* profiler: returns a method called and consumed times * hb_storvnl( 0, -1, 2 ); } -/* __ClsGetProperties( nClassHandle, [ lAllExported ] ) --> aPropertiesNames +/* __clsGetProperties( , [ ] ) --> * Notice that this function works quite similar to __CLASSSEL() * except that just returns the name of the datas and methods * that have been declared as PROPERTY (PERSISTENT) or also EXPORTED @@ -5168,6 +5166,7 @@ HB_FUNC( __CLSMSGTYPE ) * for MT programs which will allocate dynamically at runtime * more then 16386 classes. In practice rather impossible though * who knows ;-) + * __clsPreAllocate( [] ) -> */ HB_FUNC( __CLSPREALLOCATE ) { @@ -5191,6 +5190,8 @@ HB_FUNC( __CLSPREALLOCATE ) hb_retnl( s_uiClsSize ); } +/* __clsLockDef( ) -> + */ HB_FUNC( __CLSLOCKDEF ) { HB_STACK_TLS_PRELOAD @@ -5210,6 +5211,8 @@ HB_FUNC( __CLSLOCKDEF ) hb_retl( fLocked ); } +/* __clsUnlockDef( @, ) + */ HB_FUNC( __CLSUNLOCKDEF ) { PHB_ITEM pClsDst = hb_param( 1, HB_IT_BYREF ), @@ -5227,13 +5230,31 @@ HB_FUNC( __CLSUNLOCKDEF ) hb_threadMutexUnlock( s_pClassMtx ); } +/* Dirty functions which converts array to object of given class + * __objSetClass( , [, ] ) -> + */ +HB_FUNC( __OBJSETCLASS ) +{ + PHB_ITEM pObject = hb_param( 1, HB_IT_ARRAY ); + + if( pObject && pObject->item.asArray.value->uiClass == 0 ) + { + const char * szClass = hb_parc( 2 ); + + if( szClass ) + hb_objSetClass( pObject, szClass, hb_parc( 3 ) ); + } + + hb_itemReturn( pObject ); +} + /* Real dirty function, though very usefull under certain circunstances: * It allows to change the class handle of an object into another class handle, * so the object behaves like a different Class of object. * Based on objects.lib SetClsHandle() + * __objSetClassHandle( , ) --> */ - -HB_FUNC( HB_SETCLSHANDLE ) /* ( oObject, nClassHandle ) --> nPrevClassHandle */ +HB_FUNC( __OBJSETCLASSHANDLE ) { HB_STACK_TLS_PRELOAD PHB_ITEM pObject = hb_param( 1, HB_IT_OBJECT ); @@ -5251,23 +5272,9 @@ HB_FUNC( HB_SETCLSHANDLE ) /* ( oObject, nClassHandle ) --> nPrevClassHandle */ hb_retnl( uiPrevClassHandle ); } -/* Dirty functions which converts array to object of given class - * __OBJSETCLASS( , [, ] ) -> - */ -HB_FUNC( __OBJSETCLASS ) -{ - PHB_ITEM pObject = hb_param( 1, HB_IT_ARRAY ); - - if( pObject && pObject->item.asArray.value->uiClass == 0 ) - { - const char * szClass = hb_parc( 2 ); - - if( szClass ) - hb_objSetClass( pObject, szClass, hb_parc( 3 ) ); - } - - hb_itemReturn( pObject ); -} +#if defined( HB_LEGACY_LEVEL5 ) +HB_FUNC_TRANSLATE( HB_SETCLSHANDLE, __OBJSETCLASSHANDLE ) +#endif /* Harbour equivalent for Clipper internal __mdCreate() */ HB_USHORT hb_clsCreate( HB_USHORT usSize, const char * szClassName )