1998-11-25 18:57:26 +01:00
|
|
|
/*
|
|
|
|
* PE->NE resource conversion functions
|
|
|
|
*
|
|
|
|
* Copyright 1998 Ulrich Weigand
|
|
|
|
*/
|
|
|
|
|
1999-02-28 13:27:56 +01:00
|
|
|
#include <string.h>
|
1999-02-17 14:51:06 +01:00
|
|
|
#include "wine/winuser16.h"
|
1998-11-25 18:57:26 +01:00
|
|
|
#include "module.h"
|
1999-06-12 17:45:58 +02:00
|
|
|
#include "debugtools.h"
|
1998-11-25 18:57:26 +01:00
|
|
|
#include "debugtools.h"
|
|
|
|
|
1999-04-19 16:56:29 +02:00
|
|
|
DEFAULT_DEBUG_CHANNEL(resource)
|
|
|
|
|
1998-11-25 18:57:26 +01:00
|
|
|
/**********************************************************************
|
|
|
|
* ConvertDialog32To16 (KERNEL.615)
|
|
|
|
*/
|
|
|
|
VOID WINAPI ConvertDialog32To16( LPVOID dialog32, DWORD size, LPVOID dialog16 )
|
|
|
|
{
|
|
|
|
LPVOID p = dialog32;
|
|
|
|
WORD nbItems, data, dialogEx;
|
|
|
|
DWORD style;
|
|
|
|
|
|
|
|
style = *((DWORD *)dialog16)++ = *((DWORD *)p)++;
|
|
|
|
dialogEx = (style == 0xffff0001); /* DIALOGEX resource */
|
|
|
|
if (dialogEx)
|
|
|
|
{
|
|
|
|
*((DWORD *)dialog16)++ = *((DWORD *)p)++; /* helpID */
|
|
|
|
*((DWORD *)dialog16)++ = *((DWORD *)p)++; /* exStyle */
|
|
|
|
style = *((DWORD *)dialog16)++ = *((DWORD *)p)++; /* style */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
((DWORD *)p)++; /* exStyle ignored in 16-bit standard dialog */
|
|
|
|
|
|
|
|
nbItems = *((BYTE *)dialog16)++ = (BYTE)*((WORD *)p)++;
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* x */
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* y */
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* cx */
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* cy */
|
|
|
|
|
|
|
|
/* Transfer menu name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; *((BYTE *)dialog16)++ = 0; break;
|
|
|
|
case 0xffff: ((WORD *)p)++; *((BYTE *)dialog16)++ = 0xff;
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; break;
|
|
|
|
default: lstrcpyWtoA( (LPSTR)dialog16, (LPWSTR)p );
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)dialog16) += lstrlenA( (LPSTR)dialog16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Transfer class name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; *((BYTE *)dialog16)++ = 0; break;
|
|
|
|
case 0xffff: ((WORD *)p)++; *((BYTE *)dialog16)++ = 0xff;
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; break;
|
|
|
|
default: lstrcpyWtoA( (LPSTR)dialog16, (LPWSTR)p );
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)dialog16) += lstrlenA( (LPSTR)dialog16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Transfer window caption */
|
|
|
|
lstrcpyWtoA( (LPSTR)dialog16, (LPWSTR)p );
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)dialog16) += lstrlenA( (LPSTR)dialog16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
|
|
|
|
/* Transfer font info */
|
|
|
|
if (style & DS_SETFONT)
|
|
|
|
{
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* pointSize */
|
|
|
|
if (dialogEx)
|
|
|
|
{
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* weight */
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* italic */
|
|
|
|
}
|
|
|
|
lstrcpyWtoA( (LPSTR)dialog16, (LPWSTR)p ); /* faceName */
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)dialog16) += lstrlenA( (LPSTR)dialog16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Transfer dialog items */
|
|
|
|
while (nbItems)
|
|
|
|
{
|
|
|
|
/* align on DWORD boundary (32-bit only) */
|
|
|
|
p = (LPVOID)((((int)p) + 3) & ~3);
|
|
|
|
|
|
|
|
if (dialogEx)
|
|
|
|
{
|
|
|
|
*((DWORD *)dialog16)++ = *((DWORD *)p)++; /* helpID */
|
|
|
|
*((DWORD *)dialog16)++ = *((DWORD *)p)++; /* exStyle */
|
|
|
|
*((DWORD *)dialog16)++ = *((DWORD *)p)++; /* style */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
style = *((DWORD *)p)++; /* save style */
|
|
|
|
((DWORD *)p)++; /* ignore exStyle */
|
|
|
|
}
|
|
|
|
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* x */
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* y */
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* cx */
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* cy */
|
|
|
|
|
|
|
|
if (dialogEx)
|
|
|
|
*((DWORD *)dialog16)++ = *((DWORD *)p)++; /* ID */
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; /* ID */
|
|
|
|
*((DWORD *)dialog16)++ = style; /* style from above */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Transfer class name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; *((BYTE *)dialog16)++ = 0; break;
|
|
|
|
case 0xffff: ((WORD *)p)++;
|
|
|
|
*((BYTE *)dialog16)++ = (BYTE)*((WORD *)p)++; break;
|
|
|
|
default: lstrcpyWtoA( (LPSTR)dialog16, (LPWSTR)p );
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)dialog16) += lstrlenA( (LPSTR)dialog16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Transfer window name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; *((BYTE *)dialog16)++ = 0; break;
|
|
|
|
case 0xffff: ((WORD *)p)++; *((BYTE *)dialog16)++ = 0xff;
|
|
|
|
*((WORD *)dialog16)++ = *((WORD *)p)++; break;
|
|
|
|
default: lstrcpyWtoA( (LPSTR)dialog16, (LPWSTR)p );
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)dialog16) += lstrlenA( (LPSTR)dialog16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Transfer data */
|
|
|
|
data = *((WORD *)p)++;
|
|
|
|
if (dialogEx)
|
|
|
|
*((WORD *)dialog16)++ = data;
|
|
|
|
else
|
|
|
|
*((BYTE *)dialog16)++ = (BYTE)data;
|
|
|
|
|
|
|
|
if (data)
|
|
|
|
{
|
|
|
|
memcpy( dialog16, p, data );
|
|
|
|
(LPSTR)dialog16 += data;
|
|
|
|
(LPSTR)p += data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Next item */
|
|
|
|
nbItems--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
|
|
* GetDialog32Size (KERNEL.618)
|
|
|
|
*/
|
1999-02-26 12:11:13 +01:00
|
|
|
WORD WINAPI GetDialog32Size16( LPVOID dialog32 )
|
1998-11-25 18:57:26 +01:00
|
|
|
{
|
|
|
|
LPVOID p = dialog32;
|
|
|
|
WORD nbItems, data, dialogEx;
|
|
|
|
DWORD style;
|
|
|
|
|
|
|
|
style = *((DWORD *)p)++;
|
|
|
|
dialogEx = (style == 0xffff0001); /* DIALOGEX resource */
|
|
|
|
if (dialogEx)
|
|
|
|
{
|
|
|
|
((DWORD *)p)++; /* helpID */
|
|
|
|
((DWORD *)p)++; /* exStyle */
|
|
|
|
style = *((DWORD *)p)++; /* style */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
((DWORD *)p)++; /* exStyle */
|
|
|
|
|
|
|
|
nbItems = *((WORD *)p)++;
|
|
|
|
((WORD *)p)++; /* x */
|
|
|
|
((WORD *)p)++; /* y */
|
|
|
|
((WORD *)p)++; /* cx */
|
|
|
|
((WORD *)p)++; /* cy */
|
|
|
|
|
|
|
|
/* Skip menu name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; break;
|
|
|
|
case 0xffff: ((WORD *)p) += 2; break;
|
1999-02-26 12:11:13 +01:00
|
|
|
default: ((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1; break;
|
1998-11-25 18:57:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip class name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; break;
|
|
|
|
case 0xffff: ((WORD *)p) += 2; break;
|
1999-02-26 12:11:13 +01:00
|
|
|
default: ((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1; break;
|
1998-11-25 18:57:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip window caption */
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
|
|
|
|
/* Skip font info */
|
|
|
|
if (style & DS_SETFONT)
|
|
|
|
{
|
|
|
|
((WORD *)p)++; /* pointSize */
|
|
|
|
if (dialogEx)
|
|
|
|
{
|
|
|
|
((WORD *)p)++; /* weight */
|
|
|
|
((WORD *)p)++; /* italic */
|
|
|
|
}
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1; /* faceName */
|
1998-11-25 18:57:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip dialog items */
|
|
|
|
while (nbItems)
|
|
|
|
{
|
|
|
|
/* align on DWORD boundary */
|
|
|
|
p = (LPVOID)((((int)p) + 3) & ~3);
|
|
|
|
|
|
|
|
if (dialogEx)
|
|
|
|
{
|
|
|
|
((DWORD *)p)++; /* helpID */
|
|
|
|
((DWORD *)p)++; /* exStyle */
|
|
|
|
((DWORD *)p)++; /* style */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
((DWORD *)p)++; /* style */
|
|
|
|
((DWORD *)p)++; /* exStyle */
|
|
|
|
}
|
|
|
|
|
|
|
|
((WORD *)p)++; /* x */
|
|
|
|
((WORD *)p)++; /* y */
|
|
|
|
((WORD *)p)++; /* cx */
|
|
|
|
((WORD *)p)++; /* cy */
|
|
|
|
|
|
|
|
if (dialogEx)
|
|
|
|
((DWORD *)p)++; /* ID */
|
|
|
|
else
|
|
|
|
((WORD *)p)++; /* ID */
|
|
|
|
|
|
|
|
/* Skip class name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; break;
|
|
|
|
case 0xffff: ((WORD *)p) += 2; break;
|
1999-02-26 12:11:13 +01:00
|
|
|
default: ((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1; break;
|
1998-11-25 18:57:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip window name */
|
|
|
|
switch (*((WORD *)p))
|
|
|
|
{
|
|
|
|
case 0x0000: ((WORD *)p)++; break;
|
|
|
|
case 0xffff: ((WORD *)p) += 2; break;
|
1999-02-26 12:11:13 +01:00
|
|
|
default: ((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1; break;
|
1998-11-25 18:57:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip data */
|
|
|
|
data = *((WORD *)p)++;
|
|
|
|
(LPSTR)p += data;
|
|
|
|
|
|
|
|
/* Next item */
|
|
|
|
nbItems--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (WORD)((LPSTR)p - (LPSTR)dialog32);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
|
|
* ConvertMenu32To16 (KERNEL.616)
|
|
|
|
*/
|
|
|
|
VOID WINAPI ConvertMenu32To16( LPVOID menu32, DWORD size, LPVOID menu16 )
|
|
|
|
{
|
|
|
|
LPVOID p = menu32;
|
|
|
|
WORD version, headersize, flags, level = 1;
|
|
|
|
|
|
|
|
version = *((WORD *)menu16)++ = *((WORD *)p)++;
|
|
|
|
headersize = *((WORD *)menu16)++ = *((WORD *)p)++;
|
|
|
|
if ( headersize )
|
|
|
|
{
|
|
|
|
memcpy( menu16, p, headersize );
|
|
|
|
((LPSTR)menu16) += headersize;
|
|
|
|
((LPSTR)p) += headersize;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ( level )
|
|
|
|
if ( version == 0 ) /* standard */
|
|
|
|
{
|
|
|
|
flags = *((WORD *)menu16)++ = *((WORD *)p)++;
|
|
|
|
if ( !(flags & MF_POPUP) )
|
|
|
|
*((WORD *)menu16)++ = *((WORD *)p)++; /* ID */
|
|
|
|
else
|
|
|
|
level++;
|
|
|
|
|
|
|
|
lstrcpyWtoA( (LPSTR)menu16, (LPWSTR)p );
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)menu16) += lstrlenA( (LPSTR)menu16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
|
|
|
|
if ( flags & MF_END )
|
|
|
|
level--;
|
|
|
|
}
|
|
|
|
else /* extended */
|
|
|
|
{
|
|
|
|
*((DWORD *)menu16)++ = *((DWORD *)p)++; /* fType */
|
|
|
|
*((DWORD *)menu16)++ = *((DWORD *)p)++; /* fState */
|
|
|
|
*((WORD *)menu16)++ = (WORD)*((DWORD *)p)++; /* ID */
|
|
|
|
flags = *((BYTE *)menu16)++ = (BYTE)*((WORD *)p)++;
|
|
|
|
|
|
|
|
lstrcpyWtoA( (LPSTR)menu16, (LPWSTR)p );
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPSTR)menu16) += lstrlenA( (LPSTR)menu16 ) + 1;
|
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
|
|
|
|
/* align on DWORD boundary (32-bit only) */
|
|
|
|
p = (LPVOID)((((int)p) + 3) & ~3);
|
|
|
|
|
|
|
|
/* If popup, transfer helpid */
|
|
|
|
if ( flags & 1)
|
|
|
|
{
|
|
|
|
*((DWORD *)menu16)++ = *((DWORD *)p)++;
|
|
|
|
level++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( flags & MF_END )
|
|
|
|
level--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
|
|
* GetMenu32Size (KERNEL.617)
|
|
|
|
*/
|
1999-02-26 12:11:13 +01:00
|
|
|
WORD WINAPI GetMenu32Size16( LPVOID menu32 )
|
1998-11-25 18:57:26 +01:00
|
|
|
{
|
|
|
|
LPVOID p = menu32;
|
|
|
|
WORD version, headersize, flags, level = 1;
|
|
|
|
|
|
|
|
version = *((WORD *)p)++;
|
|
|
|
headersize = *((WORD *)p)++;
|
|
|
|
((LPSTR)p) += headersize;
|
|
|
|
|
|
|
|
while ( level )
|
|
|
|
if ( version == 0 ) /* standard */
|
|
|
|
{
|
|
|
|
flags = *((WORD *)p)++;
|
|
|
|
if ( !(flags & MF_POPUP) )
|
|
|
|
((WORD *)p)++; /* ID */
|
|
|
|
else
|
|
|
|
level++;
|
|
|
|
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
|
|
|
|
if ( flags & MF_END )
|
|
|
|
level--;
|
|
|
|
}
|
|
|
|
else /* extended */
|
|
|
|
{
|
|
|
|
((DWORD *)p)++; /* fType */
|
|
|
|
((DWORD *)p)++; /* fState */
|
|
|
|
((DWORD *)p)++; /* ID */
|
|
|
|
flags = *((WORD *)p)++;
|
|
|
|
|
1999-02-26 12:11:13 +01:00
|
|
|
((LPWSTR)p) += lstrlenW( (LPWSTR)p ) + 1;
|
1998-11-25 18:57:26 +01:00
|
|
|
|
|
|
|
/* align on DWORD boundary (32-bit only) */
|
|
|
|
p = (LPVOID)((((int)p) + 3) & ~3);
|
|
|
|
|
|
|
|
/* If popup, skip helpid */
|
|
|
|
if ( flags & 1)
|
|
|
|
{
|
|
|
|
((DWORD *)p)++;
|
|
|
|
level++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( flags & MF_END )
|
|
|
|
level--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (WORD)((LPSTR)p - (LPSTR)menu32);
|
|
|
|
}
|
|
|
|
|
1999-01-17 17:32:32 +01:00
|
|
|
/**********************************************************************
|
|
|
|
* ConvertAccelerator32To16
|
|
|
|
*/
|
|
|
|
VOID ConvertAccelerator32To16( LPVOID acc32, DWORD size, LPVOID acc16 )
|
|
|
|
{
|
|
|
|
int type;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
/* Copy type */
|
|
|
|
type = *((BYTE *)acc16)++ = *((BYTE *)acc32)++;
|
|
|
|
/* Skip padding */
|
|
|
|
((BYTE *)acc32)++;
|
|
|
|
/* Copy event and IDval */
|
|
|
|
*((WORD *)acc16)++ = *((WORD *)acc32)++;
|
|
|
|
*((WORD *)acc16)++ = *((WORD *)acc32)++;
|
|
|
|
/* Skip padding */
|
|
|
|
((WORD *)acc32)++;
|
|
|
|
|
|
|
|
} while ( !( type & 0x80 ) );
|
|
|
|
}
|
|
|
|
|
1998-11-25 18:57:26 +01:00
|
|
|
/**********************************************************************
|
|
|
|
* NE_LoadPEResource
|
|
|
|
*/
|
|
|
|
HGLOBAL16 NE_LoadPEResource( NE_MODULE *pModule, WORD type, LPVOID bits, DWORD size )
|
|
|
|
{
|
|
|
|
HGLOBAL16 handle;
|
|
|
|
|
1999-06-12 17:45:58 +02:00
|
|
|
TRACE("module=%04x type=%04x\n", pModule->self, type );
|
1998-11-25 18:57:26 +01:00
|
|
|
if (!pModule || !bits || !size) return 0;
|
|
|
|
|
|
|
|
handle = GlobalAlloc16( 0, size );
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case RT_MENU16:
|
|
|
|
ConvertMenu32To16( bits, size, GlobalLock16( handle ) );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RT_DIALOG16:
|
|
|
|
ConvertDialog32To16( bits, size, GlobalLock16( handle ) );
|
|
|
|
break;
|
|
|
|
|
1999-01-17 17:32:32 +01:00
|
|
|
case RT_ACCELERATOR16:
|
|
|
|
ConvertAccelerator32To16( bits, size, GlobalLock16( handle ) );
|
|
|
|
break;
|
|
|
|
|
1998-11-25 18:57:26 +01:00
|
|
|
case RT_STRING16:
|
1999-06-12 17:45:58 +02:00
|
|
|
FIXME("not yet implemented!\n" );
|
1998-11-25 18:57:26 +01:00
|
|
|
/* fall through */
|
|
|
|
|
|
|
|
default:
|
|
|
|
memcpy( GlobalLock16( handle ), bits, size );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|