wined3d: Don't record dirty areas in a readonly lock.
[wine] / dlls / rpcrt4 / rpc_epmap.c
1 /*
2  * RPC endpoint mapper
3  *
4  * Copyright 2002 Greg Turner
5  * Copyright 2001 Ove Kåven, TransGaming Technologies
6  * Copyright 2008 Robert Shearman (for CodeWeavers)
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22
23 #include <stdarg.h>
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winerror.h"
28
29 #include "rpc.h"
30
31 #include "wine/debug.h"
32 #include "wine/exception.h"
33
34 #include "rpc_binding.h"
35 #include "epm.h"
36 #include "epm_towers.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(ole);
39
40 /* The "real" RPC portmapper endpoints that I know of are:
41  *
42  *  ncadg_ip_udp: 135
43  *  ncacn_ip_tcp: 135
44  *  ncacn_np: \\pipe\epmapper
45  *  ncalrpc: epmapper
46  *  ncacn_http: 593
47  *
48  * If the user's machine ran a DCE RPC daemon, it would
49  * probably be possible to connect to it, but there are many
50  * reasons not to, like:
51  *  - the user probably does *not* run one, and probably
52  *    shouldn't be forced to run one just for local COM
53  *  - very few Unix systems use DCE RPC... if they run a RPC
54  *    daemon at all, it's usually Sun RPC
55  *  - DCE RPC registrations are persistent and saved on disk,
56  *    while MS-RPC registrations are documented as non-persistent
57  *    and stored only in RAM, and auto-destroyed when the process
58  *    dies (something DCE RPC can't do)
59  *
60  * Of course, if the user *did* want to run a DCE RPC daemon anyway,
61  * there would be interoperability advantages, like the possibility
62  * of running a fully functional DCOM server using Wine...
63  */
64
65 static const struct epm_endpoints
66 {
67     const char *protseq;
68     const char *endpoint;
69 } epm_endpoints[] =
70 {
71     { "ncacn_np", "\\pipe\\epmapper" },
72     { "ncacn_ip_tcp", "135" },
73     { "ncacn_ip_udp", "135" },
74     { "ncalrpc", "epmapper" },
75     { "ncacn_http", "593" },
76 };
77
78 static BOOL start_rpcss(void)
79 {
80     PROCESS_INFORMATION pi;
81     STARTUPINFOW si;
82     WCHAR cmd[MAX_PATH];
83     static const WCHAR rpcss[] = {'\\','r','p','c','s','s','.','e','x','e',0};
84     BOOL rslt;
85
86     TRACE("\n");
87
88     ZeroMemory(&si, sizeof(STARTUPINFOA));
89     si.cb = sizeof(STARTUPINFOA);
90     GetSystemDirectoryW( cmd, MAX_PATH - sizeof(rpcss)/sizeof(WCHAR) );
91     lstrcatW( cmd, rpcss );
92
93     rslt = CreateProcessW( cmd, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi );
94
95     if (rslt)
96     {
97         CloseHandle(pi.hProcess);
98         CloseHandle(pi.hThread);
99         Sleep(100);
100     }
101
102     return rslt;
103 }
104
105 static inline BOOL is_epm_destination_local(RPC_BINDING_HANDLE handle)
106 {
107     RpcBinding *bind = handle;
108     const char *protseq = bind->Protseq;
109     const char *network_addr = bind->NetworkAddr;
110
111     return (!strcmp(protseq, "ncalrpc") ||
112            (!strcmp(protseq, "ncacn_np") &&
113                 (!network_addr || !strcmp(network_addr, "."))));
114 }
115
116 static RPC_STATUS get_epm_handle_client(RPC_BINDING_HANDLE handle, RPC_BINDING_HANDLE *epm_handle)
117 {
118     RpcBinding *bind = handle;
119     const char * pszEndpoint = NULL;
120     RPC_STATUS status;
121     RpcBinding* epm_bind;
122     unsigned int i;
123
124     if (bind->server)
125         return RPC_S_INVALID_BINDING;
126
127     for (i = 0; i < sizeof(epm_endpoints)/sizeof(epm_endpoints[0]); i++)
128         if (!strcmp(bind->Protseq, epm_endpoints[i].protseq))
129             pszEndpoint = epm_endpoints[i].endpoint;
130
131     if (!pszEndpoint)
132     {
133         FIXME("no endpoint for the endpoint-mapper found for protseq %s\n", debugstr_a(bind->Protseq));
134         return RPC_S_PROTSEQ_NOT_SUPPORTED;
135     }
136
137     status = RpcBindingCopy(handle, epm_handle);
138     if (status != RPC_S_OK) return status;
139
140     epm_bind = *epm_handle;
141     if (epm_bind->AuthInfo)
142     {
143         /* don't bother with authenticating against the EPM by default
144         * (see EnableAuthEpResolution registry value) */
145         RpcAuthInfo_Release(epm_bind->AuthInfo);
146         epm_bind->AuthInfo = NULL;
147     }
148     RPCRT4_ResolveBinding(epm_bind, pszEndpoint);
149     TRACE("RPC_S_OK\n");
150     return RPC_S_OK;
151 }
152
153 static RPC_STATUS get_epm_handle_server(RPC_BINDING_HANDLE *epm_handle)
154 {
155     unsigned char string_binding[] = "ncacn_np:.[\\\\pipe\\\\epmapper]";
156
157     return RpcBindingFromStringBindingA(string_binding, epm_handle);
158 }
159
160 static LONG WINAPI rpc_filter(EXCEPTION_POINTERS *__eptr)
161 {
162     switch (GetExceptionCode())
163     {
164         case EXCEPTION_ACCESS_VIOLATION:
165         case EXCEPTION_ILLEGAL_INSTRUCTION:
166             return EXCEPTION_CONTINUE_SEARCH;
167         default:
168             return EXCEPTION_EXECUTE_HANDLER;
169     }
170 }
171
172 /***********************************************************************
173  *             RpcEpRegisterA (RPCRT4.@)
174  */
175 RPC_STATUS WINAPI RpcEpRegisterA( RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR *BindingVector,
176                                   UUID_VECTOR *UuidVector, RPC_CSTR Annotation )
177 {
178   PRPC_SERVER_INTERFACE If = IfSpec;
179   ULONG i;
180   RPC_STATUS status = RPC_S_OK;
181   error_status_t status2;
182   ept_entry_t *entries;
183   handle_t handle;
184
185   TRACE("(%p,%p,%p,%s)\n", IfSpec, BindingVector, UuidVector, debugstr_a((char*)Annotation));
186   TRACE(" ifid=%s\n", debugstr_guid(&If->InterfaceId.SyntaxGUID));
187   for (i=0; i<BindingVector->Count; i++) {
188     RpcBinding* bind = BindingVector->BindingH[i];
189     TRACE(" protseq[%d]=%s\n", i, debugstr_a(bind->Protseq));
190     TRACE(" endpoint[%d]=%s\n", i, debugstr_a(bind->Endpoint));
191   }
192   if (UuidVector) {
193     for (i=0; i<UuidVector->Count; i++)
194       TRACE(" obj[%d]=%s\n", i, debugstr_guid(UuidVector->Uuid[i]));
195   }
196
197   if (!BindingVector->Count) return RPC_S_OK;
198
199   entries = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*entries) * BindingVector->Count * (UuidVector ? UuidVector->Count : 1));
200   if (!entries)
201       return RPC_S_OUT_OF_MEMORY;
202
203   status = get_epm_handle_server(&handle);
204   if (status != RPC_S_OK)
205   {
206     HeapFree(GetProcessHeap(), 0, entries);
207     return status;
208   }
209
210   for (i = 0; i < BindingVector->Count; i++)
211   {
212       unsigned j;
213       RpcBinding* bind = BindingVector->BindingH[i];
214       for (j = 0; j < (UuidVector ? UuidVector->Count : 1); j++)
215       {
216           status = TowerConstruct(&If->InterfaceId, &If->TransferSyntax,
217                                   bind->Protseq, bind->Endpoint,
218                                   bind->NetworkAddr,
219                                   &entries[i*(UuidVector ? UuidVector->Count : 1) + j].tower);
220           if (status != RPC_S_OK) break;
221
222           if (UuidVector)
223               memcpy(&entries[i * UuidVector->Count].object, &UuidVector->Uuid[j], sizeof(GUID));
224           else
225               memset(&entries[i].object, 0, sizeof(entries[i].object));
226           if (Annotation)
227               memcpy(entries[i].annotation, Annotation,
228                      min(strlen((char *)Annotation) + 1, ept_max_annotation_size));
229       }
230   }
231
232   if (status == RPC_S_OK)
233   {
234       while (TRUE)
235       {
236           __TRY
237           {
238               ept_insert(handle, BindingVector->Count * (UuidVector ? UuidVector->Count : 1),
239                          entries, TRUE, &status2);
240           }
241           __EXCEPT(rpc_filter)
242           {
243               status2 = GetExceptionCode();
244           }
245           __ENDTRY
246           if (status2 == RPC_S_SERVER_UNAVAILABLE &&
247               is_epm_destination_local(handle))
248           {
249               if (start_rpcss())
250                   continue;
251           }
252           if (status2 != RPC_S_OK)
253               ERR("ept_insert failed with error %d\n", status2);
254           status = status2; /* FIXME: convert status? */
255           break;
256       }
257   }
258   RpcBindingFree(&handle);
259
260   for (i = 0; i < BindingVector->Count; i++)
261   {
262       unsigned j;
263       for (j = 0; j < (UuidVector ? UuidVector->Count : 1); j++)
264           I_RpcFree(entries[i*(UuidVector ? UuidVector->Count : 1) + j].tower);
265   }
266
267   HeapFree(GetProcessHeap(), 0, entries);
268
269   return status;
270 }
271
272 /***********************************************************************
273  *             RpcEpRegisterW (RPCRT4.@)
274  */
275 RPC_STATUS WINAPI RpcEpRegisterW( RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR *BindingVector,
276                                   UUID_VECTOR *UuidVector, RPC_WSTR Annotation )
277 {
278   LPSTR annA = RPCRT4_strdupWtoA(Annotation);
279   RPC_STATUS status;
280
281   status = RpcEpRegisterA(IfSpec, BindingVector, UuidVector, (RPC_CSTR)annA);
282
283   HeapFree(GetProcessHeap(), 0, annA);
284   return status;
285 }
286
287 /***********************************************************************
288  *             RpcEpUnregister (RPCRT4.@)
289  */
290 RPC_STATUS WINAPI RpcEpUnregister( RPC_IF_HANDLE IfSpec, RPC_BINDING_VECTOR *BindingVector,
291                                    UUID_VECTOR *UuidVector )
292 {
293   PRPC_SERVER_INTERFACE If = IfSpec;
294   ULONG i;
295   RPC_STATUS status = RPC_S_OK;
296   error_status_t status2;
297   ept_entry_t *entries;
298   handle_t handle;
299
300   TRACE("(%p,%p,%p)\n", IfSpec, BindingVector, UuidVector);
301   TRACE(" ifid=%s\n", debugstr_guid(&If->InterfaceId.SyntaxGUID));
302   for (i=0; i<BindingVector->Count; i++) {
303     RpcBinding* bind = BindingVector->BindingH[i];
304     TRACE(" protseq[%d]=%s\n", i, debugstr_a(bind->Protseq));
305     TRACE(" endpoint[%d]=%s\n", i, debugstr_a(bind->Endpoint));
306   }
307   if (UuidVector) {
308     for (i=0; i<UuidVector->Count; i++)
309       TRACE(" obj[%d]=%s\n", i, debugstr_guid(UuidVector->Uuid[i]));
310   }
311
312   entries = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*entries) * BindingVector->Count * (UuidVector ? UuidVector->Count : 1));
313   if (!entries)
314       return RPC_S_OUT_OF_MEMORY;
315
316   status = get_epm_handle_server(&handle);
317   if (status != RPC_S_OK)
318   {
319     HeapFree(GetProcessHeap(), 0, entries);
320     return status;
321   }
322
323   for (i = 0; i < BindingVector->Count; i++)
324   {
325       unsigned j;
326       RpcBinding* bind = BindingVector->BindingH[i];
327       for (j = 0; j < (UuidVector ? UuidVector->Count : 1); j++)
328       {
329           status = TowerConstruct(&If->InterfaceId, &If->TransferSyntax,
330                                   bind->Protseq, bind->Endpoint,
331                                   bind->NetworkAddr,
332                                   &entries[i*(UuidVector ? UuidVector->Count : 1) + j].tower);
333           if (status != RPC_S_OK) break;
334
335           if (UuidVector)
336               memcpy(&entries[i * UuidVector->Count + j].object, &UuidVector->Uuid[j], sizeof(GUID));
337           else
338               memset(&entries[i].object, 0, sizeof(entries[i].object));
339       }
340   }
341
342   if (status == RPC_S_OK)
343   {
344       __TRY
345       {
346           ept_insert(handle, BindingVector->Count * (UuidVector ? UuidVector->Count : 1),
347                      entries, TRUE, &status2);
348       }
349       __EXCEPT(rpc_filter)
350       {
351           status2 = GetExceptionCode();
352       }
353       __ENDTRY
354       if (status2 == RPC_S_SERVER_UNAVAILABLE)
355           status2 = EPT_S_NOT_REGISTERED;
356       if (status2 != RPC_S_OK)
357           ERR("ept_insert failed with error %d\n", status2);
358       status = status2; /* FIXME: convert status? */
359   }
360   RpcBindingFree(&handle);
361
362   for (i = 0; i < BindingVector->Count; i++)
363   {
364       unsigned j;
365       for (j = 0; j < (UuidVector ? UuidVector->Count : 1); j++)
366           I_RpcFree(entries[i*(UuidVector ? UuidVector->Count : 1) + j].tower);
367   }
368
369   HeapFree(GetProcessHeap(), 0, entries);
370
371   return status;
372 }
373
374 /***********************************************************************
375  *             RpcEpResolveBinding (RPCRT4.@)
376  */
377 RPC_STATUS WINAPI RpcEpResolveBinding( RPC_BINDING_HANDLE Binding, RPC_IF_HANDLE IfSpec )
378 {
379   PRPC_CLIENT_INTERFACE If = IfSpec;
380   RpcBinding* bind = Binding;
381   RPC_STATUS status;
382   error_status_t status2;
383   handle_t handle;
384   ept_lookup_handle_t entry_handle = NULL;
385   twr_t *tower;
386   twr_t *towers[4] = { NULL };
387   unsigned32 num_towers, i;
388   GUID uuid = GUID_NULL;
389   char *resolved_endpoint = NULL;
390
391   TRACE("(%p,%p)\n", Binding, IfSpec);
392   TRACE(" protseq=%s\n", debugstr_a(bind->Protseq));
393   TRACE(" obj=%s\n", debugstr_guid(&bind->ObjectUuid));
394   TRACE(" networkaddr=%s\n", debugstr_a(bind->NetworkAddr));
395   TRACE(" ifid=%s\n", debugstr_guid(&If->InterfaceId.SyntaxGUID));
396
397   /* just return for fully bound handles */
398   if (bind->Endpoint && (bind->Endpoint[0] != '\0'))
399     return RPC_S_OK;
400
401   status = get_epm_handle_client(Binding, &handle);
402   if (status != RPC_S_OK) return status;
403   
404   status = TowerConstruct(&If->InterfaceId, &If->TransferSyntax, bind->Protseq,
405                           ((RpcBinding *)handle)->Endpoint,
406                           bind->NetworkAddr, &tower);
407   if (status != RPC_S_OK)
408   {
409       WARN("couldn't get tower\n");
410       RpcBindingFree(&handle);
411       return status;
412   }
413
414   while (TRUE)
415   {
416     __TRY
417     {
418       ept_map(handle, &uuid, tower, &entry_handle, sizeof(towers)/sizeof(towers[0]), &num_towers, towers, &status2);
419       /* FIXME: translate status2? */
420     }
421     __EXCEPT(rpc_filter)
422     {
423       status2 = GetExceptionCode();
424     }
425     __ENDTRY
426     if (status2 == RPC_S_SERVER_UNAVAILABLE &&
427         is_epm_destination_local(handle))
428     {
429       if (start_rpcss())
430         continue;
431     }
432     break;
433   };
434
435   RpcBindingFree(&handle);
436   I_RpcFree(tower);
437
438   if (status2 != RPC_S_OK)
439   {
440     ERR("ept_map failed for ifid %s, protseq %s, networkaddr %s\n", debugstr_guid(&If->TransferSyntax.SyntaxGUID), bind->Protseq, bind->NetworkAddr);
441     return status2;
442   }
443
444   for (i = 0; i < num_towers; i++)
445   {
446     /* only parse the tower if we haven't already found a suitable
447     * endpoint, otherwise just free the tower */
448     if (!resolved_endpoint)
449     {
450       status = TowerExplode(towers[i], NULL, NULL, NULL, &resolved_endpoint, NULL);
451       TRACE("status = %d\n", status);
452     }
453     I_RpcFree(towers[i]);
454   }
455
456   if (resolved_endpoint)
457   {
458     RPCRT4_ResolveBinding(Binding, resolved_endpoint);
459     I_RpcFree(resolved_endpoint);
460     return RPC_S_OK;
461   }
462
463   WARN("couldn't find an endpoint\n");
464   return EPT_S_NOT_REGISTERED;
465 }
466
467 /*****************************************************************************
468  * TowerExplode (RPCRT4.@)
469  */
470 RPC_STATUS WINAPI TowerExplode(
471     const twr_t *tower, PRPC_SYNTAX_IDENTIFIER object, PRPC_SYNTAX_IDENTIFIER syntax,
472     char **protseq, char **endpoint, char **address)
473 {
474     size_t tower_size;
475     RPC_STATUS status;
476     const unsigned char *p;
477     u_int16 floor_count;
478     const twr_uuid_floor_t *object_floor;
479     const twr_uuid_floor_t *syntax_floor;
480
481     TRACE("(%p, %p, %p, %p, %p, %p)\n", tower, object, syntax, protseq,
482           endpoint, address);
483
484     if (protseq)
485         *protseq = NULL;
486     if (endpoint)
487         *endpoint = NULL;
488     if (address)
489         *address = NULL;
490
491     tower_size = tower->tower_length;
492
493     if (tower_size < sizeof(u_int16))
494         return EPT_S_NOT_REGISTERED;
495
496     p = &tower->tower_octet_string[0];
497
498     floor_count = *(const u_int16 *)p;
499     p += sizeof(u_int16);
500     tower_size -= sizeof(u_int16);
501     TRACE("floor_count: %d\n", floor_count);
502     /* FIXME: should we do something with the floor count? at the moment we don't */
503
504     if (tower_size < sizeof(*object_floor) + sizeof(*syntax_floor))
505         return EPT_S_NOT_REGISTERED;
506
507     object_floor = (const twr_uuid_floor_t *)p;
508     p += sizeof(*object_floor);
509     tower_size -= sizeof(*object_floor);
510     syntax_floor = (const twr_uuid_floor_t *)p;
511     p += sizeof(*syntax_floor);
512     tower_size -= sizeof(*syntax_floor);
513
514     if ((object_floor->count_lhs != sizeof(object_floor->protid) +
515         sizeof(object_floor->uuid) + sizeof(object_floor->major_version)) ||
516         (object_floor->protid != EPM_PROTOCOL_UUID) ||
517         (object_floor->count_rhs != sizeof(object_floor->minor_version)))
518         return EPT_S_NOT_REGISTERED;
519
520     if ((syntax_floor->count_lhs != sizeof(syntax_floor->protid) +
521         sizeof(syntax_floor->uuid) + sizeof(syntax_floor->major_version)) ||
522         (syntax_floor->protid != EPM_PROTOCOL_UUID) ||
523         (syntax_floor->count_rhs != sizeof(syntax_floor->minor_version)))
524         return EPT_S_NOT_REGISTERED;
525
526     status = RpcTransport_ParseTopOfTower(p, tower_size, protseq, address, endpoint);
527     if ((status == RPC_S_OK) && syntax && object)
528     {
529         syntax->SyntaxGUID = syntax_floor->uuid;
530         syntax->SyntaxVersion.MajorVersion = syntax_floor->major_version;
531         syntax->SyntaxVersion.MinorVersion = syntax_floor->minor_version;
532         object->SyntaxGUID = object_floor->uuid;
533         object->SyntaxVersion.MajorVersion = object_floor->major_version;
534         object->SyntaxVersion.MinorVersion = object_floor->minor_version;
535     }
536     return status;
537 }
538
539 /***********************************************************************
540  *             TowerConstruct (RPCRT4.@)
541  */
542 RPC_STATUS WINAPI TowerConstruct(
543     const RPC_SYNTAX_IDENTIFIER *object, const RPC_SYNTAX_IDENTIFIER *syntax,
544     const char *protseq, const char *endpoint, const char *address,
545     twr_t **tower)
546 {
547     size_t tower_size;
548     RPC_STATUS status;
549     unsigned char *p;
550     twr_uuid_floor_t *object_floor;
551     twr_uuid_floor_t *syntax_floor;
552
553     TRACE("(%p, %p, %s, %s, %s, %p)\n", object, syntax, debugstr_a(protseq),
554           debugstr_a(endpoint), debugstr_a(address), tower);
555
556     *tower = NULL;
557
558     status = RpcTransport_GetTopOfTower(NULL, &tower_size, protseq, address, endpoint);
559
560     if (status != RPC_S_OK)
561         return status;
562
563     tower_size += sizeof(u_int16) + sizeof(*object_floor) + sizeof(*syntax_floor);
564     *tower = I_RpcAllocate(FIELD_OFFSET(twr_t, tower_octet_string[tower_size]));
565     if (!*tower)
566         return RPC_S_OUT_OF_RESOURCES;
567
568     (*tower)->tower_length = tower_size;
569     p = &(*tower)->tower_octet_string[0];
570     *(u_int16 *)p = 5; /* number of floors */
571     p += sizeof(u_int16);
572     object_floor = (twr_uuid_floor_t *)p;
573     p += sizeof(*object_floor);
574     syntax_floor = (twr_uuid_floor_t *)p;
575     p += sizeof(*syntax_floor);
576
577     object_floor->count_lhs = sizeof(object_floor->protid) + sizeof(object_floor->uuid) +
578                               sizeof(object_floor->major_version);
579     object_floor->protid = EPM_PROTOCOL_UUID;
580     object_floor->count_rhs = sizeof(object_floor->minor_version);
581     object_floor->uuid = object->SyntaxGUID;
582     object_floor->major_version = object->SyntaxVersion.MajorVersion;
583     object_floor->minor_version = object->SyntaxVersion.MinorVersion;
584
585     syntax_floor->count_lhs = sizeof(syntax_floor->protid) + sizeof(syntax_floor->uuid) +
586                               sizeof(syntax_floor->major_version);
587     syntax_floor->protid = EPM_PROTOCOL_UUID;
588     syntax_floor->count_rhs = sizeof(syntax_floor->minor_version);
589     syntax_floor->uuid = syntax->SyntaxGUID;
590     syntax_floor->major_version = syntax->SyntaxVersion.MajorVersion;
591     syntax_floor->minor_version = syntax->SyntaxVersion.MinorVersion;
592
593     status = RpcTransport_GetTopOfTower(p, &tower_size, protseq, address, endpoint);
594     if (status != RPC_S_OK)
595     {
596         I_RpcFree(*tower);
597         *tower = NULL;
598         return status;
599     }
600     return RPC_S_OK;
601 }
602
603 void __RPC_FAR * __RPC_USER MIDL_user_allocate(SIZE_T len)
604 {
605     return HeapAlloc(GetProcessHeap(), 0, len);
606 }
607
608 void __RPC_USER MIDL_user_free(void __RPC_FAR * ptr)
609 {
610     HeapFree(GetProcessHeap(), 0, ptr);
611 }