Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

649 lines
21 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 1994.
  5. //
  6. // File: inv16.cxx
  7. //
  8. // Contents: 32->16 Call thunking
  9. //
  10. // History: 25-Feb-94 DrewB Created
  11. //
  12. //----------------------------------------------------------------------------
  13. #include "headers.cxx"
  14. #pragma hdrstop
  15. #include "..\..\ole232\inc\le2int.h" // Include le2int.h before inplace.h
  16. #include "..\..\ole232\inplace\inplace.h" // We need CFrameFilter for
  17. // WinWord 6 Hack
  18. //+---------------------------------------------------------------------------
  19. //
  20. // Function: InvokeOn16, public
  21. //
  22. // Synopsis: Sets up the THUNKINFO and starts thunking for a 32->16 call
  23. //
  24. // Arguments: [iidx] - Custom interface or known interface index
  25. // [dwMethod] - Method index
  26. // [pvStack32] - 32-bit stack
  27. //
  28. // Returns: Appropriate status code
  29. //
  30. // History: 25-Feb-94 DrewB Created
  31. //
  32. //----------------------------------------------------------------------------
  33. #if DBG == 1
  34. extern "C"
  35. {
  36. ULONG InvokeOn16_count = 0;
  37. ULONG InvokeOn16_break = 0;
  38. int _iInvokeOn16BreakIidx = -1;
  39. int _iInvokeOn16BreakMethod = -1;
  40. };
  41. #endif
  42. // InvokeOn16 uses a lot of local variables so allocate its locals
  43. // rather than declaring them on the stack. This saves roughly
  44. // 150 bytes of stack per call
  45. struct INVOKE16RECORD
  46. {
  47. THOP CONST *pThop;
  48. THOP CONST * CONST *ppThop;
  49. UINT uiThop;
  50. VTBLFN UNALIGNED CONST *pvfnVtbl;
  51. VPVOID vpvThis16;
  52. VPVOID vpvVtbl16;
  53. VPVOID UNALIGNED CONST *pvpvThis16;
  54. DWORD dwStack16[MAX_PARAMS];
  55. THUNKINFO ti;
  56. THUNK3216OBJ *ptoThis32;
  57. ThreadData *ptd;
  58. };
  59. DWORD InvokeOn16(IIDIDX iidx, DWORD dwMethod, LPVOID pvStack32)
  60. {
  61. // NOTE: Do not declare local variables in this routine
  62. // except for debug builds
  63. INVOKE16RECORD *pir;
  64. DWORD dwResult;
  65. #if DBG == 1
  66. ULONG ulInvokeOn16_count = ++InvokeOn16_count;
  67. if (InvokeOn16_count == InvokeOn16_break)
  68. {
  69. DebugBreak();
  70. }
  71. thkDebugOut((DEB_ITRACE, "%sInvokeOn16(0x%x, 0x%x, %p)\n",
  72. NestingLevelString(), iidx, dwMethod, pvStack32));
  73. #endif
  74. pir = (INVOKE16RECORD *)STACKALLOC32(sizeof(INVOKE16RECORD));
  75. if (pir == NULL)
  76. {
  77. // This error isn't guaranteed to mean anything for
  78. // this call. Not much else we can do, though
  79. return (DWORD)E_OUTOFMEMORY;
  80. }
  81. // pvStack32 is a pointer to an array of arguments from the
  82. // 32-bit call. It's always laid out with the first
  83. // argument low and increasing from there
  84. pir->ti.s32.pbStart = (BYTE *)pvStack32;
  85. pir->ti.s32.pbCurrent = pir->ti.s32.pbStart;
  86. pir->ti.s16.pbStart = (BYTE *)pir->dwStack16;
  87. pir->ti.s16.pbCurrent = pir->ti.s16.pbStart;
  88. pir->ti.scResult = S_OK;
  89. pir->ti.fResultThunked = FALSE;
  90. pir->ptd = TlsThkGetData();
  91. if (pir->ptd == NULL)
  92. {
  93. thkDebugOut((DEB_WARN, "WARNING: InvokeOn16: Call refused\n"));
  94. STACKFREE32(pir, sizeof(INVOKE16RECORD));
  95. return (DWORD)E_FAIL;
  96. }
  97. pir->ti.pThkMgr = pir->ptd->pCThkMgr;
  98. thkAssert(pir->ti.pThkMgr != NULL);
  99. thkAssert(iidx < THI_COUNT);
  100. // For each interface there is an array of thop strings, one for
  101. // each method. The IUnknown methods don't have thop strings so
  102. // bias the thop string pointer to account for that
  103. thkAssert(dwMethod >= SMI_COUNT);
  104. pir->ppThop = athopiInterfaceThopis[iidx].ppThops-SMI_COUNT;
  105. pir->uiThop = athopiInterfaceThopis[iidx].uiSize;
  106. // Methods are cdecl so we need to move upwards in memory to
  107. // get to the next parameter
  108. pir->ti.s16.iDir = 1;
  109. // We need to look up the appropriate method pointer by
  110. // looking in the 16-bit object's vtable
  111. GET_STACK32(&pir->ti, pir->ptoThis32, THUNK3216OBJ *);
  112. thkDebugOut((DEB_INVOKES,
  113. "InvokeOn16: ptoThis32 = %08lX\n", pir->ptoThis32 ));
  114. if ( pir->ptoThis32->grfFlags & PROXYFLAG_CLEANEDUP )
  115. {
  116. thkDebugOut((DEB_WARN,
  117. "InvokeOn16: Attempt to call %s::%s"
  118. "on cleaned-up proxy %08lX for 16-bit object %08lX\n",
  119. inInterfaceNames[iidx].pszInterface,
  120. inInterfaceNames[iidx].ppszMethodNames[dwMethod],
  121. pir->ptoThis32, pir->ptoThis32->vpvThis16));
  122. STACKFREE32(pir, sizeof(INVOKE16RECORD));
  123. return (DWORD)E_FAIL;
  124. }
  125. // check PROXYFLAG_CLEANEDUP before calling DebugValidateProxy3216.
  126. // Otherwise we might get asserts on checked OLE.
  127. DebugValidateProxy3216(pir->ptoThis32);
  128. pir->ti.dwCallerProxy = (DWORD)pir->ptoThis32;
  129. pir->vpvThis16 = pir->ptoThis32->vpvThis16;
  130. pir->pvpvThis16 = (VPVOID UNALIGNED *)
  131. GetReadPtr16(&pir->ti, pir->vpvThis16, sizeof(VPVOID));
  132. if (pir->pvpvThis16 == NULL)
  133. {
  134. dwResult = pir->ti.scResult;
  135. STACKFREE32(pir, sizeof(INVOKE16RECORD));
  136. return dwResult;
  137. }
  138. pir->vpvVtbl16 = *pir->pvpvThis16;
  139. pir->pvfnVtbl = (VTBLFN UNALIGNED *)
  140. GetReadPtr16(&pir->ti, pir->vpvVtbl16, sizeof(VPVOID)*pir->uiThop);
  141. WOWRELVDMPTR(pir->vpvThis16);
  142. if (pir->pvfnVtbl == NULL)
  143. {
  144. dwResult = pir->ti.scResult;
  145. STACKFREE32(pir, sizeof(INVOKE16RECORD));
  146. return dwResult;
  147. }
  148. // Push the 16-bit this pointer on the stack first
  149. TO_STACK16(&pir->ti, pir->vpvThis16, VPVOID);
  150. thkAssert(dwMethod < pir->uiThop);
  151. pir->pThop = pir->ppThop[dwMethod];
  152. thkAssert(pir->pThop != NULL);
  153. pir->ti.pThop = pir->pThop;
  154. pir->ti.pvfn = pir->pvfnVtbl[dwMethod];
  155. pir->ti.iidx = iidx;
  156. pir->ti.dwMethod = dwMethod;
  157. pir->ti.this32 = (IUnknown *)pir->ptoThis32;
  158. WOWRELVDMPTR(pir->vpvVtbl16);
  159. thkDebugOut((DEB_INVOKES, "%s#(%04X):InvokeOn16 on %p:%p, %s::%s\n",
  160. NestingLevelString(), ulInvokeOn16_count,
  161. pir->vpvThis16, pir->ti.pvfn,
  162. inInterfaceNames[iidx].pszInterface,
  163. inInterfaceNames[iidx].ppszMethodNames[dwMethod]));
  164. DebugIncrementNestingLevel();
  165. pir->ti.pThkMgr->SetThkState(THKSTATE_INVOKETHKIN16);
  166. #if DBG == 1
  167. SStackRecord sr;
  168. RecordStackState16(&sr);
  169. #endif
  170. #if DBG == 1
  171. if ((_iInvokeOn16BreakIidx > 0 && _iInvokeOn16BreakIidx == (int)iidx) &&
  172. (_iInvokeOn16BreakMethod < 0 ||
  173. _iInvokeOn16BreakMethod == (int)dwMethod))
  174. {
  175. DebugBreak();
  176. }
  177. #endif
  178. dwResult = EXECUTE_THOP3216(&pir->ti);
  179. #if DBG == 1
  180. if ( !pir->ti.fResultThunked && FAILED(dwResult) )
  181. {
  182. thkDebugOut((DEB_FAILURES,
  183. "InvokeOn16 probable failure %s::%s sc = %08lX\n",
  184. inInterfaceNames[iidx].pszInterface,
  185. inInterfaceNames[iidx].ppszMethodNames[dwMethod],
  186. dwResult));
  187. if(thkInfoLevel & DEB_DBGFAIL)
  188. thkAssert(!"Wish to Debug");
  189. }
  190. CheckStackState16(&sr);
  191. #endif
  192. pir->ti.pThkMgr->SetThkState(THKSTATE_NOCALL);
  193. DebugDecrementNestingLevel();
  194. thkDebugOut((DEB_INVOKES,
  195. "%s#(%04X):InvokeOn16 on %p:%p, %s::%s returns 0x%08lX\n",
  196. NestingLevelString(), ulInvokeOn16_count,
  197. pir->vpvThis16, pir->ti.pvfn,
  198. inInterfaceNames[iidx].pszInterface,
  199. inInterfaceNames[iidx].ppszMethodNames[dwMethod],
  200. dwResult));
  201. STACKFREE32(pir, sizeof(INVOKE16RECORD));
  202. return dwResult;
  203. }
  204. //+---------------------------------------------------------------------------
  205. //
  206. // Function: Call3216, private
  207. //
  208. // Synopsis: Sets up stack and transitions to 16-bit
  209. //
  210. // Arguments: [pvfn] - Function to call
  211. // [pbStack] - Stack in 32-bits
  212. // [cbStack] - Size of stack
  213. //
  214. // Returns: Appropriate status code
  215. //
  216. // History: 04-Mar-94 DrewB Created
  217. //
  218. //----------------------------------------------------------------------------
  219. #if DBG == 1
  220. extern "C" ULONG Call3216_count = 0;
  221. extern "C" ULONG Call3216_break = 0;
  222. #endif
  223. DWORD Call3216(VPVOID pvfn, BYTE *pbStack, UINT cbStack)
  224. {
  225. #if DBG == 1
  226. ULONG ulCall3216_count = ++Call3216_count;
  227. if (Call3216_count == Call3216_break)
  228. {
  229. DebugBreak();
  230. }
  231. #endif
  232. VPVOID vpvStack16;
  233. DWORD dwResult;
  234. void *pvStack16;
  235. dwResult = (DWORD)S_OK;
  236. if (cbStack <= WCB16_MAX_CBARGS)
  237. {
  238. thkDebugOut((DEB_ITRACE, "%sCallbackTo16Ex(%p, %lu, %p) #(%x)\n",
  239. NestingLevelString(), pvfn, cbStack, pbStack,
  240. ulCall3216_count));
  241. // pbStack must have at least WCB16_MAX_CBARGS bytes of valid memory
  242. // since 16V always copies that many bytes
  243. // In our case pbStack is from InvokeOn16 which should be large enough
  244. thkAssert(MAX_PARAMS*sizeof(DWORD) >= WCB16_MAX_CBARGS);
  245. if (!CallbackTo16Ex(pvfn, WCB16_CDECL, cbStack, pbStack,
  246. &dwResult))
  247. {
  248. dwResult = (DWORD)E_UNEXPECTED;
  249. }
  250. }
  251. else
  252. {
  253. CALLDATA UNALIGNED *pcd;
  254. UINT cbAlloc;
  255. cbAlloc = cbStack+sizeof(CALLDATA);
  256. vpvStack16 = STACKALLOC16(cbAlloc);
  257. if (vpvStack16 == 0)
  258. {
  259. dwResult = (DWORD)E_OUTOFMEMORY;
  260. }
  261. else
  262. {
  263. pvStack16 = (void *)WOWFIXVDMPTR(vpvStack16, cbAlloc);
  264. pcd = (CALLDATA UNALIGNED *)((BYTE *)pvStack16+cbStack);
  265. pcd->vpfn = (DWORD)pvfn;
  266. pcd->vpvStack16 = vpvStack16;
  267. pcd->cbStack = cbStack;
  268. memcpy(pvStack16, pbStack, cbStack);
  269. WOWRELVDMPTR(vpvStack16);
  270. thkDebugOut((DEB_ITRACE, "%sCallbackTo16(%p, (%p, %p, %lu)) #(%x)\n",
  271. NestingLevelString(), gdata16Data.fnCallStub16, pvfn,
  272. vpvStack16, cbStack, ulCall3216_count));
  273. dwResult = CallbackTo16(gdata16Data.fnCallStub16,
  274. vpvStack16+cbStack);
  275. STACKFREE16(vpvStack16, cbAlloc);
  276. }
  277. }
  278. return dwResult;
  279. }
  280. //+---------------------------------------------------------------------------
  281. //
  282. // Function: ThunkCall3216, public
  283. //
  284. // Synopsis: Sets up the 16-bit stack and makes a 32->16 call
  285. //
  286. // Arguments: [pti] - Thunk state info
  287. //
  288. // Returns: Appropriate status code
  289. //
  290. // History: 25-Feb-94 DrewB Created
  291. // 08-Aug-94 AlexT Add IOleClientSite::OnShowWindow code
  292. //
  293. //----------------------------------------------------------------------------
  294. #if DBG == 1
  295. extern "C" ULONG ThunkCall3216_count = 0;
  296. extern "C" ULONG ThunkCall3216_break = 0;
  297. #endif
  298. DWORD ThunkCall3216(THUNKINFO *pti)
  299. {
  300. DWORD dwReturn;
  301. UINT cbStack;
  302. DWORD dwCallerTID;
  303. HRESULT hrCaller;
  304. BOOL fFail = FALSE;
  305. #if DBG == 1
  306. ULONG ulThunkCall3216_count = ++ThunkCall3216_count;
  307. thkAssert( (ThunkCall3216_count != ThunkCall3216_break) &&
  308. "Break Count Hit");
  309. #endif
  310. thkAssert(*pti->pThop == THOP_END);
  311. pti->pThop++;
  312. thkAssert(*pti->pThop == THOP_ROUTINEINDEX);
  313. pti->pThop++;
  314. thkDebugOut((DEB_ITRACE, "%sIn ThunkCall3216 #(%x) %p, index %d\n",
  315. NestingLevelString(), ulThunkCall3216_count,
  316. pti->pvfn, *pti->pThop));
  317. DebugIncrementNestingLevel();
  318. cbStack = (ULONG) (pti->s16.pbCurrent-pti->s16.pbStart);
  319. // The this pointer should always be on the stack
  320. thkAssert(cbStack >= sizeof(VPVOID));
  321. //
  322. // Hacks for specific interface member functions.
  323. // The placement of these hacks here is by no means an optimal solution.
  324. // It just happens to be convienient for now since everything goes through
  325. // here. This section is for pre-processing.
  326. //
  327. if ( IIDIDX_IS_INDEX(pti->iidx) )
  328. {
  329. switch( IIDIDX_INDEX(pti->iidx) )
  330. {
  331. case THI_IOleClientSite:
  332. #define METHOD_ONSHOWWINDOW 7
  333. if ( pti->dwMethod == METHOD_ONSHOWWINDOW )
  334. {
  335. //
  336. // Here we merge the input queues for the sole reason so that
  337. // we can link the object's window activations into the calling
  338. // thread's window z-order.
  339. //
  340. hrCaller = CoGetCallerTID( &dwCallerTID );
  341. if ( hrCaller == S_FALSE )
  342. {
  343. AttachThreadInput( dwCallerTID, GetCurrentThreadId(),
  344. TRUE );
  345. }
  346. }
  347. break;
  348. case THI_IOleObject:
  349. #define METHOD_DOVERB 11
  350. if ( pti->dwMethod == METHOD_DOVERB )
  351. {
  352. //
  353. // Here we merge the input queues for the sole reason so
  354. // that we can link the object's window activations into
  355. // the calling thread's window z-order.
  356. //
  357. hrCaller = CoGetCallerTID( &dwCallerTID );
  358. if ( hrCaller == S_FALSE )
  359. {
  360. AttachThreadInput( dwCallerTID, GetCurrentThreadId(),
  361. TRUE );
  362. }
  363. }
  364. break;
  365. case THI_IRpcStubBuffer:
  366. #define METHOD_DEBUGSERVER_QUERYINTERFACE 8
  367. #define METHOD_DEBUGSERVER_RELEASE 9
  368. if(pti->dwMethod == METHOD_DEBUGSERVER_QUERYINTERFACE)
  369. {
  370. // This is badly designed method in the sense that
  371. // we do not know how to thunk the OUT interface
  372. // parameter returned by this method. The interface
  373. // may have been addrefed and may be not. We also do
  374. // not know the IID of the interface. At best, we can
  375. // thunk it as an IUnknown and not call release on the
  376. // actual interface when building it. We can then release
  377. // the proxy built above when DebugServerRelease is called
  378. // later. But, it will lead to an AV if the invoker of this
  379. // method invokes a non-IUnknown method on the thunked
  380. // interface. In view of the above, I am failing the call
  381. // to this method. This is a more desirable behavior and
  382. // Wx86 thunking also would not get affected by it.
  383. // GopalK Aug 18, 97.
  384. thkDebugOut((DEB_FAILURES, "Call on IRpcStubBuffer::DebugServerQueryInterface\n"));
  385. dwReturn = E_NOINTERFACE;
  386. fFail = TRUE;
  387. }
  388. else if(pti->dwMethod == METHOD_DEBUGSERVER_RELEASE)
  389. {
  390. // See comment for DebugServerQueryInterface
  391. thkAssert(!"Call on IRpcStubBuffer::DebugServerRelease");
  392. thkDebugOut((DEB_ERROR, "Call on IRpcStubBuffer::DebugServerRelease\n"));
  393. dwReturn = S_OK;
  394. fFail = TRUE;
  395. }
  396. default:
  397. break;
  398. }
  399. }
  400. if(!fFail)
  401. {
  402. pti->pThkMgr->SetThkState(THKSTATE_NOCALL);
  403. dwReturn = Call3216((VPVOID)pti->pvfn, pti->s16.pbStart, cbStack);
  404. pti->pThkMgr->SetThkState(THKSTATE_INVOKETHKOUT16);
  405. //
  406. // Hacks for specific interface member functions.
  407. // Again, the placement of these is by no means an optimal solution.
  408. // They can be moved as long as they have the same effect for just these
  409. // interfaces. This section is for post-processing.
  410. //
  411. if ( IIDIDX_IS_INDEX(pti->iidx) )
  412. {
  413. switch( IIDIDX_INDEX(pti->iidx) )
  414. {
  415. case THI_IOleClientSite:
  416. if ( pti->dwMethod == METHOD_ONSHOWWINDOW )
  417. {
  418. //
  419. // Unmerge the input queues afterward.
  420. //
  421. if ( hrCaller == S_FALSE )
  422. {
  423. AttachThreadInput( dwCallerTID, GetCurrentThreadId(),
  424. FALSE );
  425. }
  426. }
  427. break;
  428. case THI_IOleObject:
  429. if ( pti->dwMethod == METHOD_DOVERB )
  430. {
  431. //
  432. // Unmerge the input queues afterward.
  433. //
  434. if ( hrCaller == S_FALSE )
  435. {
  436. AttachThreadInput( dwCallerTID, GetCurrentThreadId(),
  437. FALSE );
  438. }
  439. }
  440. #define METHOD_GETCLIENTSITE 4
  441. if ( pti->dwMethod == METHOD_GETCLIENTSITE )
  442. {
  443. //
  444. // Excel 5.0a needs to perform some special processing
  445. // on the way out of a IOleObject::GetClientSite call.
  446. // See CTHKMGR.CXX and APINOT.CXX for more details.
  447. //
  448. if ( TlsThkGetAppCompatFlags() & OACF_CLIENTSITE_REF )
  449. {
  450. //
  451. // Tell the thkmgr that we are thunking a bad
  452. // IOleObject::GetClientSite reference on the way out.
  453. //
  454. thkDebugOut((DEB_WARN,"TC3216: OACF_CLIENTSITE_REF used: "
  455. "Setting to clientsite thunk state\n"));
  456. pti->pThkMgr->SetThkState(
  457. THKSTATE_INVOKETHKOUT16_CLIENTSITE);
  458. }
  459. }
  460. break;
  461. case THI_IOleInPlaceFrame:
  462. #define METHOD_REMOVEMENUS 11
  463. //
  464. // Winword 6.0a didn't call OleSetMenuDescriptor(NULL)
  465. // during its IOleInPlaceFrame::RemoveMenus. This leaves
  466. // OLE's frame filter in place. The frame filter holds references
  467. // to some objects so everybody's refcounting gets thrown off
  468. // Here, when we see a RemoveMenus call completing we force
  469. // the OleSetMenuDescriptor(NULL) call to occur. This shuts
  470. // down the frame filter and corrects the reference counts.
  471. //
  472. // There is one other hack necessary: Word unsubclasses the
  473. // window itself directly rather than going through
  474. // OleSetMenuDescriptor. Therefore the frame filter code
  475. // is patched to only unhook if it detects that it is the
  476. // current hook
  477. //
  478. // See APINOT.CXX for more hack code.
  479. //
  480. if (pti->dwMethod == METHOD_REMOVEMENUS)
  481. {
  482. if ( TlsThkGetAppCompatFlags() & OACF_RESETMENU )
  483. {
  484. HRESULT hr;
  485. HWND hwnd;
  486. LPOLEINPLACEFRAME lpoipf;
  487. pti->pThkMgr->SetThkState(THKSTATE_NOCALL);
  488. lpoipf = (LPOLEINPLACEFRAME)pti->this32;
  489. hr = lpoipf->GetWindow( &hwnd );
  490. pti->pThkMgr->SetThkState(THKSTATE_INVOKETHKOUT16);
  491. if ( FAILED(hr) )
  492. {
  493. break;
  494. }
  495. thkDebugOut((DEB_WARN,
  496. "TC3216: OACF_RESETMENU used: "
  497. "Setting menu descriptor "
  498. "to NULL on %p\n", hwnd));
  499. OleSetMenuDescriptor(NULL, hwnd, NULL, NULL, NULL);
  500. }
  501. }
  502. break;
  503. default:
  504. break;
  505. }
  506. }
  507. if ( !pti->fResultThunked )
  508. {
  509. dwReturn = TransformHRESULT_1632( dwReturn );
  510. #if DBG == 1
  511. if (FAILED(dwReturn) )
  512. {
  513. thkDebugOut((DEB_FAILURES,
  514. "Call3216 pvfn = %08lX Probably failed hr = %08lX\n",
  515. pti->pvfn, dwReturn));
  516. if(thkInfoLevel & DEB_DBGFAIL)
  517. thkAssert(!"Wish to Debug");
  518. }
  519. #endif
  520. }
  521. }
  522. DebugDecrementNestingLevel();
  523. thkDebugOut((DEB_ITRACE, "%sOut ThunkCall3216 #(%x) returns 0x%08lX\n",
  524. NestingLevelString(), ulThunkCall3216_count, dwReturn));
  525. return dwReturn;
  526. }
  527. //+---------------------------------------------------------------------------
  528. //
  529. // Function: SetOwnerPublicHMEM16, public
  530. //
  531. // Synopsis: Changes the 16-bit memory handle into a public selector, owned
  532. // by nobody. This prevents any app from taking it away when it
  533. // is cleaned up.
  534. //
  535. // Arguments: [hmem] - 16-bit memory handle
  536. //
  537. // Returns: Appropriate status code
  538. //
  539. // History: 13-Jul-94 BobDay Created it
  540. //
  541. //----------------------------------------------------------------------------
  542. void SetOwnerPublicHMEM16( DWORD hmem )
  543. {
  544. CallbackTo16(gdata16Data.fnSetOwnerPublic16, hmem );
  545. }