Implemented GetProcessAffinityMask() and SetProcessAffinityMask().
[reactos.git] / reactos / lib / kernel32 / process / proc.c
1 /* $Id: proc.c,v 1.52 2003/03/06 15:05:29 ekohl Exp $
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS system libraries
5 * FILE: lib/kernel32/proc/proc.c
6 * PURPOSE: Process functions
7 * PROGRAMMER: Ariadne ( ariadne@xs4all.nl)
8 * UPDATE HISTORY:
9 * Created 01/11/98
10 */
11
12 /* INCLUDES ****************************************************************/
13
14 #include <k32.h>
15
16
17 #define NDEBUG
18 #include <kernel32/kernel32.h>
19
20
21 /* GLOBALS *******************************************************************/
22
23 WaitForInputIdleType lpfnGlobalRegisterWaitForInputIdle;
24
25 LPSTARTUPINFO lpLocalStartupInfo = NULL;
26
27 VOID STDCALL
28 RegisterWaitForInputIdle(WaitForInputIdleType lpfnRegisterWaitForInputIdle);
29
30 WINBOOL STDCALL
31 GetProcessId (HANDLE hProcess, LPDWORD lpProcessId);
32
33
34 /* FUNCTIONS ****************************************************************/
35
36 BOOL STDCALL
37 GetProcessAffinityMask (HANDLE hProcess,
38 LPDWORD lpProcessAffinityMask,
39 LPDWORD lpSystemAffinityMask)
40 {
41 PROCESS_BASIC_INFORMATION ProcessInfo;
42 ULONG BytesWritten;
43 NTSTATUS Status;
44
45 Status = NtQueryInformationProcess (hProcess,
46 ProcessBasicInformation,
47 (PVOID)&ProcessInfo,
48 sizeof(PROCESS_BASIC_INFORMATION),
49 &BytesWritten);
50 if (!NT_SUCCESS(Status))
51 {
52 SetLastError (Status);
53 return FALSE;
54 }
55
56 *lpProcessAffinityMask = (DWORD)ProcessInfo.AffinityMask;
57
58 /* FIXME */
59 *lpSystemAffinityMask = 0x00000001;
60
61 return TRUE;
62 }
63
64
65 BOOL STDCALL
66 SetProcessAffinityMask (HANDLE hProcess,
67 DWORD dwProcessAffinityMask)
68 {
69 NTSTATUS Status;
70
71 Status = NtSetInformationProcess (hProcess,
72 ProcessAffinityMask,
73 (PVOID)&dwProcessAffinityMask,
74 sizeof(DWORD));
75 if (!NT_SUCCESS(Status))
76 {
77 SetLastError (Status);
78 return FALSE;
79 }
80
81 return TRUE;
82 }
83
84
85 WINBOOL STDCALL
86 GetProcessShutdownParameters (LPDWORD lpdwLevel,
87 LPDWORD lpdwFlags)
88 {
89 CSRSS_API_REQUEST CsrRequest;
90 CSRSS_API_REPLY CsrReply;
91 NTSTATUS Status;
92
93 CsrRequest.Type = CSRSS_GET_SHUTDOWN_PARAMETERS;
94 Status = CsrClientCallServer(&CsrRequest,
95 &CsrReply,
96 sizeof(CSRSS_API_REQUEST),
97 sizeof(CSRSS_API_REPLY));
98 if (!NT_SUCCESS(Status) || !NT_SUCCESS(CsrReply.Status))
99 {
100 SetLastError(Status);
101 return(FALSE);
102 }
103
104 *lpdwLevel = CsrReply.Data.GetShutdownParametersReply.Level;
105 *lpdwFlags = CsrReply.Data.GetShutdownParametersReply.Flags;
106
107 return(TRUE);
108 }
109
110
111 WINBOOL STDCALL
112 SetProcessShutdownParameters (DWORD dwLevel,
113 DWORD dwFlags)
114 {
115 CSRSS_API_REQUEST CsrRequest;
116 CSRSS_API_REPLY CsrReply;
117 NTSTATUS Status;
118
119 CsrRequest.Data.SetShutdownParametersRequest.Level = dwLevel;
120 CsrRequest.Data.SetShutdownParametersRequest.Flags = dwFlags;
121
122 CsrRequest.Type = CSRSS_SET_SHUTDOWN_PARAMETERS;
123 Status = CsrClientCallServer(&CsrRequest,
124 &CsrReply,
125 sizeof(CSRSS_API_REQUEST),
126 sizeof(CSRSS_API_REPLY));
127 if (!NT_SUCCESS(Status) || !NT_SUCCESS(CsrReply.Status))
128 {
129 SetLastError(Status);
130 return(FALSE);
131 }
132
133 return(TRUE);
134 }
135
136
137 WINBOOL STDCALL
138 GetProcessWorkingSetSize (HANDLE hProcess,
139 LPDWORD lpMinimumWorkingSetSize,
140 LPDWORD lpMaximumWorkingSetSize)
141 {
142 QUOTA_LIMITS QuotaLimits;
143 NTSTATUS Status;
144
145 Status = NtQueryInformationProcess(hProcess,
146 ProcessQuotaLimits,
147 &QuotaLimits,
148 sizeof(QUOTA_LIMITS),
149 NULL);
150 if (!NT_SUCCESS(Status))
151 {
152 SetLastErrorByStatus(Status);
153 return(FALSE);
154 }
155
156 *lpMinimumWorkingSetSize = (DWORD)QuotaLimits.MinimumWorkingSetSize;
157 *lpMaximumWorkingSetSize = (DWORD)QuotaLimits.MaximumWorkingSetSize;
158
159 return(TRUE);
160 }
161
162
163 WINBOOL STDCALL
164 SetProcessWorkingSetSize(HANDLE hProcess,
165 DWORD dwMinimumWorkingSetSize,
166 DWORD dwMaximumWorkingSetSize)
167 {
168 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
169 return(FALSE);
170 }
171
172
173 WINBOOL STDCALL
174 GetProcessTimes(HANDLE hProcess,
175 LPFILETIME lpCreationTime,
176 LPFILETIME lpExitTime,
177 LPFILETIME lpKernelTime,
178 LPFILETIME lpUserTime)
179 {
180 KERNEL_USER_TIMES Kut;
181 NTSTATUS Status;
182
183 Status = NtQueryInformationProcess(hProcess,
184 ProcessTimes,
185 &Kut,
186 sizeof(Kut),
187 NULL);
188 if (!NT_SUCCESS(Status))
189 {
190 SetLastErrorByStatus(Status);
191 return(FALSE);
192 }
193
194 lpCreationTime->dwLowDateTime = Kut.CreateTime.u.LowPart;
195 lpCreationTime->dwHighDateTime = Kut.CreateTime.u.HighPart;
196
197 lpExitTime->dwLowDateTime = Kut.ExitTime.u.LowPart;
198 lpExitTime->dwHighDateTime = Kut.ExitTime.u.HighPart;
199
200 lpKernelTime->dwLowDateTime = Kut.KernelTime.u.LowPart;
201 lpKernelTime->dwHighDateTime = Kut.KernelTime.u.HighPart;
202
203 lpUserTime->dwLowDateTime = Kut.UserTime.u.LowPart;
204 lpUserTime->dwHighDateTime = Kut.UserTime.u.HighPart;
205
206 return(TRUE);
207 }
208
209
210 HANDLE STDCALL
211 GetCurrentProcess(VOID)
212 {
213 return((HANDLE)NtCurrentProcess());
214 }
215
216
217 HANDLE STDCALL
218 GetCurrentThread(VOID)
219 {
220 return((HANDLE)NtCurrentThread());
221 }
222
223
224 DWORD STDCALL
225 GetCurrentProcessId(VOID)
226 {
227 return((DWORD)GetTeb()->Cid.UniqueProcess);
228 }
229
230
231 WINBOOL STDCALL
232 GetExitCodeProcess(HANDLE hProcess,
233 LPDWORD lpExitCode)
234 {
235 PROCESS_BASIC_INFORMATION ProcessBasic;
236 ULONG BytesWritten;
237 NTSTATUS Status;
238
239 Status = NtQueryInformationProcess(hProcess,
240 ProcessBasicInformation,
241 &ProcessBasic,
242 sizeof(PROCESS_BASIC_INFORMATION),
243 &BytesWritten);
244 if (!NT_SUCCESS(Status))
245 {
246 SetLastErrorByStatus(Status);
247 return(FALSE);
248 }
249
250 memcpy(lpExitCode, &ProcessBasic.ExitStatus, sizeof(DWORD));
251
252 return(TRUE);
253 }
254
255
256 WINBOOL STDCALL
257 GetProcessId(HANDLE hProcess,
258 LPDWORD lpProcessId)
259 {
260 PROCESS_BASIC_INFORMATION ProcessBasic;
261 ULONG BytesWritten;
262 NTSTATUS Status;
263
264 Status = NtQueryInformationProcess(hProcess,
265 ProcessBasicInformation,
266 &ProcessBasic,
267 sizeof(PROCESS_BASIC_INFORMATION),
268 &BytesWritten);
269 if (!NT_SUCCESS(Status))
270 {
271 SetLastErrorByStatus(Status);
272 return(FALSE);
273 }
274
275 memcpy(lpProcessId, &ProcessBasic.UniqueProcessId, sizeof(DWORD));
276
277 return(TRUE);
278 }
279
280
281 HANDLE STDCALL
282 OpenProcess(DWORD dwDesiredAccess,
283 WINBOOL bInheritHandle,
284 DWORD dwProcessId)
285 {
286 NTSTATUS errCode;
287 HANDLE ProcessHandle;
288 OBJECT_ATTRIBUTES ObjectAttributes;
289 CLIENT_ID ClientId ;
290
291 ClientId.UniqueProcess = (HANDLE)dwProcessId;
292 ClientId.UniqueThread = INVALID_HANDLE_VALUE;
293
294 ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
295 ObjectAttributes.RootDirectory = (HANDLE)NULL;
296 ObjectAttributes.SecurityDescriptor = NULL;
297 ObjectAttributes.SecurityQualityOfService = NULL;
298 ObjectAttributes.ObjectName = NULL;
299
300 if (bInheritHandle == TRUE)
301 ObjectAttributes.Attributes = OBJ_INHERIT;
302 else
303 ObjectAttributes.Attributes = 0;
304
305 errCode = NtOpenProcess(&ProcessHandle,
306 dwDesiredAccess,
307 &ObjectAttributes,
308 &ClientId);
309 if (!NT_SUCCESS(errCode))
310 {
311 SetLastErrorByStatus (errCode);
312 return NULL;
313 }
314 return ProcessHandle;
315 }
316
317
318 UINT STDCALL
319 WinExec(LPCSTR lpCmdLine,
320 UINT uCmdShow)
321 {
322 STARTUPINFOA StartupInfo;
323 PROCESS_INFORMATION ProcessInformation;
324 HINSTANCE hInst;
325 DWORD dosErr;
326
327 StartupInfo.cb = sizeof(STARTUPINFOA);
328 StartupInfo.wShowWindow = uCmdShow;
329 StartupInfo.dwFlags = 0;
330
331 hInst = (HINSTANCE)CreateProcessA(NULL,
332 (PVOID)lpCmdLine,
333 NULL,
334 NULL,
335 FALSE,
336 0,
337 NULL,
338 NULL,
339 &StartupInfo,
340 &ProcessInformation);
341 if ( hInst == NULL )
342 {
343 dosErr = GetLastError();
344 return dosErr;
345 }
346 if (NULL != lpfnGlobalRegisterWaitForInputIdle)
347 {
348 lpfnGlobalRegisterWaitForInputIdle (
349 ProcessInformation.hProcess,
350 10000
351 );
352 }
353 NtClose (ProcessInformation.hProcess);
354 NtClose (ProcessInformation.hThread);
355 return 0;
356 }
357
358
359 VOID STDCALL
360 RegisterWaitForInputIdle (
361 WaitForInputIdleType lpfnRegisterWaitForInputIdle
362 )
363 {
364 lpfnGlobalRegisterWaitForInputIdle = lpfnRegisterWaitForInputIdle;
365 return;
366 }
367
368
369 DWORD STDCALL
370 WaitForInputIdle (
371 HANDLE hProcess,
372 DWORD dwMilliseconds
373 )
374 {
375 return 0;
376 }
377
378
379 VOID STDCALL
380 Sleep(DWORD dwMilliseconds)
381 {
382 SleepEx(dwMilliseconds, FALSE);
383 return;
384 }
385
386
387 DWORD STDCALL
388 SleepEx(DWORD dwMilliseconds,
389 BOOL bAlertable)
390 {
391 TIME Interval;
392 NTSTATUS errCode;
393
394 if (dwMilliseconds != INFINITE)
395 {
396 /*
397 * System time units are 100 nanoseconds (a nanosecond is a billionth of
398 * a second).
399 */
400 Interval.QuadPart = dwMilliseconds;
401 Interval.QuadPart = -(Interval.QuadPart * 10000);
402 }
403 else
404 {
405 /* Approximately 292000 years hence */
406 Interval.QuadPart = -0x7FFFFFFFFFFFFFFF;
407 }
408
409 errCode = NtDelayExecution (bAlertable, &Interval);
410 if (!NT_SUCCESS(errCode))
411 {
412 SetLastErrorByStatus (errCode);
413 return -1;
414 }
415 return 0;
416 }
417
418
419 VOID STDCALL
420 GetStartupInfoW(LPSTARTUPINFOW lpStartupInfo)
421 {
422 PRTL_USER_PROCESS_PARAMETERS Params;
423
424 if (lpStartupInfo == NULL)
425 {
426 SetLastError(ERROR_INVALID_PARAMETER);
427 return;
428 }
429
430 Params = NtCurrentPeb()->ProcessParameters;
431
432 lpStartupInfo->cb = sizeof(STARTUPINFOW);
433 lpStartupInfo->lpDesktop = Params->DesktopInfo.Buffer;
434 lpStartupInfo->lpTitle = Params->WindowTitle.Buffer;
435 lpStartupInfo->dwX = Params->dwX;
436 lpStartupInfo->dwY = Params->dwY;
437 lpStartupInfo->dwXSize = Params->dwXSize;
438 lpStartupInfo->dwYSize = Params->dwYSize;
439 lpStartupInfo->dwXCountChars = Params->dwXCountChars;
440 lpStartupInfo->dwYCountChars = Params->dwYCountChars;
441 lpStartupInfo->dwFillAttribute = Params->dwFillAttribute;
442 lpStartupInfo->dwFlags = Params->dwFlags;
443 lpStartupInfo->wShowWindow = Params->wShowWindow;
444 lpStartupInfo->lpReserved = Params->ShellInfo.Buffer;
445 lpStartupInfo->cbReserved2 = Params->RuntimeInfo.Length;
446 lpStartupInfo->lpReserved2 = (LPBYTE)Params->RuntimeInfo.Buffer;
447
448 lpStartupInfo->hStdInput = Params->hStdInput;
449 lpStartupInfo->hStdOutput = Params->hStdOutput;
450 lpStartupInfo->hStdError = Params->hStdError;
451 }
452
453
454 VOID STDCALL
455 GetStartupInfoA(LPSTARTUPINFOA lpStartupInfo)
456 {
457 PRTL_USER_PROCESS_PARAMETERS Params;
458 ANSI_STRING AnsiString;
459
460 if (lpStartupInfo == NULL)
461 {
462 SetLastError(ERROR_INVALID_PARAMETER);
463 return;
464 }
465
466 Params = NtCurrentPeb ()->ProcessParameters;
467
468 RtlAcquirePebLock ();
469
470 if (lpLocalStartupInfo == NULL)
471 {
472 /* create new local startup info (ansi) */
473 lpLocalStartupInfo = RtlAllocateHeap (RtlGetProcessHeap (),
474 0,
475 sizeof(STARTUPINFOA));
476
477 lpLocalStartupInfo->cb = sizeof(STARTUPINFOA);
478
479 /* copy window title string */
480 RtlUnicodeStringToAnsiString (&AnsiString,
481 &Params->WindowTitle,
482 TRUE);
483 lpLocalStartupInfo->lpTitle = AnsiString.Buffer;
484
485 /* copy desktop info string */
486 RtlUnicodeStringToAnsiString (&AnsiString,
487 &Params->DesktopInfo,
488 TRUE);
489 lpLocalStartupInfo->lpDesktop = AnsiString.Buffer;
490
491 /* copy shell info string */
492 RtlUnicodeStringToAnsiString (&AnsiString,
493 &Params->ShellInfo,
494 TRUE);
495 lpLocalStartupInfo->lpReserved = AnsiString.Buffer;
496
497 lpLocalStartupInfo->dwX = Params->dwX;
498 lpLocalStartupInfo->dwY = Params->dwY;
499 lpLocalStartupInfo->dwXSize = Params->dwXSize;
500 lpLocalStartupInfo->dwYSize = Params->dwYSize;
501 lpLocalStartupInfo->dwXCountChars = Params->dwXCountChars;
502 lpLocalStartupInfo->dwYCountChars = Params->dwYCountChars;
503 lpLocalStartupInfo->dwFillAttribute = Params->dwFillAttribute;
504 lpLocalStartupInfo->dwFlags = Params->dwFlags;
505 lpLocalStartupInfo->wShowWindow = Params->wShowWindow;
506 lpLocalStartupInfo->cbReserved2 = Params->RuntimeInfo.Length;
507 lpLocalStartupInfo->lpReserved2 = (LPBYTE)Params->RuntimeInfo.Buffer;
508
509 lpLocalStartupInfo->hStdInput = Params->hStdInput;
510 lpLocalStartupInfo->hStdOutput = Params->hStdOutput;
511 lpLocalStartupInfo->hStdError = Params->hStdError;
512 }
513
514 RtlReleasePebLock ();
515
516 /* copy local startup info data to external startup info */
517 memcpy (lpStartupInfo,
518 lpLocalStartupInfo,
519 sizeof(STARTUPINFOA));
520 }
521
522
523 BOOL STDCALL
524 FlushInstructionCache (HANDLE hProcess,
525 LPCVOID lpBaseAddress,
526 DWORD dwSize)
527 {
528 NTSTATUS Status;
529
530 Status = NtFlushInstructionCache(hProcess,
531 (PVOID)lpBaseAddress,
532 dwSize);
533 if (!NT_SUCCESS(Status))
534 {
535 SetLastErrorByStatus(Status);
536 return FALSE;
537 }
538 return TRUE;
539 }
540
541
542 VOID STDCALL
543 ExitProcess(UINT uExitCode)
544 {
545 CSRSS_API_REQUEST CsrRequest;
546 CSRSS_API_REPLY CsrReply;
547 NTSTATUS Status;
548
549 /* unload all dll's */
550 LdrShutdownProcess ();
551
552 /* notify csrss of process termination */
553 CsrRequest.Type = CSRSS_TERMINATE_PROCESS;
554 Status = CsrClientCallServer(&CsrRequest,
555 &CsrReply,
556 sizeof(CSRSS_API_REQUEST),
557 sizeof(CSRSS_API_REPLY));
558 if (!NT_SUCCESS(Status) || !NT_SUCCESS(CsrReply.Status))
559 {
560 DbgPrint("Failed to tell csrss about terminating process. "
561 "Expect trouble.\n");
562 }
563
564
565 NtTerminateProcess (NtCurrentProcess (),
566 uExitCode);
567 }
568
569
570 WINBOOL STDCALL
571 TerminateProcess (HANDLE hProcess,
572 UINT uExitCode)
573 {
574 NTSTATUS Status;
575
576 Status = NtTerminateProcess (hProcess, uExitCode);
577 if (NT_SUCCESS(Status))
578 {
579 return TRUE;
580 }
581 SetLastErrorByStatus (Status);
582 return FALSE;
583 }
584
585
586 VOID STDCALL
587 FatalAppExitA (UINT uAction,
588 LPCSTR lpMessageText)
589 {
590 UNICODE_STRING MessageTextU;
591 ANSI_STRING MessageText;
592
593 RtlInitAnsiString (&MessageText, (LPSTR) lpMessageText);
594
595 RtlAnsiStringToUnicodeString (&MessageTextU,
596 &MessageText,
597 TRUE);
598
599 FatalAppExitW (uAction, MessageTextU.Buffer);
600
601 RtlFreeUnicodeString (&MessageTextU);
602 }
603
604
605 VOID STDCALL
606 FatalAppExitW(UINT uAction,
607 LPCWSTR lpMessageText)
608 {
609 return;
610 }
611
612
613 VOID STDCALL
614 FatalExit (int ExitCode)
615 {
616 ExitProcess(ExitCode);
617 }
618
619
620 DWORD STDCALL
621 GetPriorityClass (HANDLE hProcess)
622 {
623 HANDLE hProcessTmp;
624 DWORD CsrPriorityClass = 0; // This tells CSRSS we want to GET it!
625 NTSTATUS Status;
626
627 Status =
628 NtDuplicateObject (GetCurrentProcess(),
629 hProcess,
630 GetCurrentProcess(),
631 &hProcessTmp,
632 (PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION),
633 FALSE,
634 0);
635 if (!NT_SUCCESS(Status))
636 {
637 SetLastErrorByStatus (Status);
638 return (0); /* ERROR */
639 }
640 /* Ask CSRSS to set it */
641 CsrSetPriorityClass (hProcessTmp, &CsrPriorityClass);
642 NtClose (hProcessTmp);
643 /* Translate CSR->W32 priorities */
644 switch (CsrPriorityClass)
645 {
646 case CSR_PRIORITY_CLASS_NORMAL:
647 return (NORMAL_PRIORITY_CLASS); /* 32 */
648 case CSR_PRIORITY_CLASS_IDLE:
649 return (IDLE_PRIORITY_CLASS); /* 64 */
650 case CSR_PRIORITY_CLASS_HIGH:
651 return (HIGH_PRIORITY_CLASS); /* 128 */
652 case CSR_PRIORITY_CLASS_REALTIME:
653 return (REALTIME_PRIORITY_CLASS); /* 256 */
654 }
655 SetLastError (ERROR_ACCESS_DENIED);
656 return (0); /* ERROR */
657 }
658
659
660
661 WINBOOL STDCALL
662 SetPriorityClass (HANDLE hProcess,
663 DWORD dwPriorityClass)
664 {
665 HANDLE hProcessTmp;
666 DWORD CsrPriorityClass;
667 NTSTATUS Status;
668
669 switch (dwPriorityClass)
670 {
671 case NORMAL_PRIORITY_CLASS: /* 32 */
672 CsrPriorityClass = CSR_PRIORITY_CLASS_NORMAL;
673 break;
674 case IDLE_PRIORITY_CLASS: /* 64 */
675 CsrPriorityClass = CSR_PRIORITY_CLASS_IDLE;
676 break;
677 case HIGH_PRIORITY_CLASS: /* 128 */
678 CsrPriorityClass = CSR_PRIORITY_CLASS_HIGH;
679 break;
680 case REALTIME_PRIORITY_CLASS: /* 256 */
681 CsrPriorityClass = CSR_PRIORITY_CLASS_REALTIME;
682 break;
683 default:
684 SetLastError (ERROR_INVALID_PARAMETER);
685 return (FALSE);
686 }
687 Status =
688 NtDuplicateObject (GetCurrentProcess(),
689 hProcess,
690 GetCurrentProcess(),
691 &hProcessTmp,
692 (PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION),
693 FALSE,
694 0);
695 if (!NT_SUCCESS(Status))
696 {
697 SetLastErrorByStatus (Status);
698 return (FALSE); /* ERROR */
699 }
700 /* Ask CSRSS to set it */
701 Status = CsrSetPriorityClass (hProcessTmp, &CsrPriorityClass);
702 NtClose (hProcessTmp);
703 if (!NT_SUCCESS(Status))
704 {
705 SetLastErrorByStatus (Status);
706 return (FALSE);
707 }
708 return (TRUE);
709 }
710
711
712 DWORD STDCALL
713 GetProcessVersion (DWORD ProcessId)
714 {
715 DWORD Version = 0;
716 PIMAGE_NT_HEADERS NtHeader = NULL;
717 PVOID BaseAddress = NULL;
718
719 /* Caller's */
720 if (0 == ProcessId || GetCurrentProcessId() == ProcessId)
721 {
722 BaseAddress = (PVOID) NtCurrentPeb()->ImageBaseAddress;
723 NtHeader = RtlImageNtHeader (BaseAddress);
724 if (NULL != NtHeader)
725 {
726 Version =
727 (NtHeader->OptionalHeader.MajorOperatingSystemVersion << 16) |
728 (NtHeader->OptionalHeader.MinorOperatingSystemVersion);
729 }
730 }
731 else /* other process */
732 {
733 /* FIXME: open the other process */
734 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
735 }
736 return (Version);
737 }
738
739 /* EOF */