1/* 2 * ---------------------------------------------------------------------------- 3 * nmakehlp.c -- 4 * 5 * This is used to fix limitations within nmake and the environment. 6 * 7 * Copyright (c) 2002 by David Gravereaux. 8 * Copyright (c) 2006 by Pat Thoyts 9 * 10 * See the file "license.terms" for information on usage and redistribution of 11 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 12 * 13 * ---------------------------------------------------------------------------- 14 * RCS: @(#) $Id: nmakehlp.c,v 1.2 2009/01/22 14:36:58 patthoyts Exp $ 15 * ---------------------------------------------------------------------------- 16 */ 17 18#define _CRT_SECURE_NO_DEPRECATE 19#include <windows.h> 20#define NO_SHLWAPI_GDI 21#define NO_SHLWAPI_STREAM 22#define NO_SHLWAPI_REG 23#include <shlwapi.h> 24#pragma comment (lib, "user32.lib") 25#pragma comment (lib, "kernel32.lib") 26#pragma comment (lib, "shlwapi.lib") 27#include <stdio.h> 28#include <math.h> 29 30/* 31 * This library is required for x64 builds with _some_ versions of MSVC 32 */ 33#if defined(_M_IA64) || defined(_M_AMD64) 34#if _MSC_VER >= 1400 && _MSC_VER < 1500 35#pragma comment(lib, "bufferoverflowU") 36#endif 37#endif 38 39/* ISO hack for dumb VC++ */ 40#ifdef _MSC_VER 41#define snprintf _snprintf 42#endif 43 44 45 46/* protos */ 47 48int CheckForCompilerFeature(const char *option); 49int CheckForLinkerFeature(const char *option); 50int IsIn(const char *string, const char *substring); 51int GrepForDefine(const char *file, const char *string); 52int SubstituteFile(const char *substs, const char *filename); 53int QualifyPath(const char *path); 54const char * GetVersionFromFile(const char *filename, const char *match); 55DWORD WINAPI ReadFromPipe(LPVOID args); 56 57/* globals */ 58 59#define CHUNK 25 60#define STATICBUFFERSIZE 1000 61typedef struct { 62 HANDLE pipe; 63 char buffer[STATICBUFFERSIZE]; 64} pipeinfo; 65 66pipeinfo Out = {INVALID_HANDLE_VALUE, '\0'}; 67pipeinfo Err = {INVALID_HANDLE_VALUE, '\0'}; 68 69/* 70 * exitcodes: 0 == no, 1 == yes, 2 == error 71 */ 72 73int 74main( 75 int argc, 76 char *argv[]) 77{ 78 char msg[300]; 79 DWORD dwWritten; 80 int chars; 81 82 /* 83 * Make sure children (cl.exe and link.exe) are kept quiet. 84 */ 85 86 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 87 88 /* 89 * Make sure the compiler and linker aren't effected by the outside world. 90 */ 91 92 SetEnvironmentVariable("CL", ""); 93 SetEnvironmentVariable("LINK", ""); 94 95 if (argc > 1 && *argv[1] == '-') { 96 switch (*(argv[1]+1)) { 97 case 'c': 98 if (argc != 3) { 99 chars = snprintf(msg, sizeof(msg) - 1, 100 "usage: %s -c <compiler option>\n" 101 "Tests for whether cl.exe supports an option\n" 102 "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); 103 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 104 &dwWritten, NULL); 105 return 2; 106 } 107 return CheckForCompilerFeature(argv[2]); 108 case 'l': 109 if (argc != 3) { 110 chars = snprintf(msg, sizeof(msg) - 1, 111 "usage: %s -l <linker option>\n" 112 "Tests for whether link.exe supports an option\n" 113 "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); 114 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 115 &dwWritten, NULL); 116 return 2; 117 } 118 return CheckForLinkerFeature(argv[2]); 119 case 'f': 120 if (argc == 2) { 121 chars = snprintf(msg, sizeof(msg) - 1, 122 "usage: %s -f <string> <substring>\n" 123 "Find a substring within another\n" 124 "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); 125 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 126 &dwWritten, NULL); 127 return 2; 128 } else if (argc == 3) { 129 /* 130 * If the string is blank, there is no match. 131 */ 132 133 return 0; 134 } else { 135 return IsIn(argv[2], argv[3]); 136 } 137 case 'g': 138 if (argc == 2) { 139 chars = snprintf(msg, sizeof(msg) - 1, 140 "usage: %s -g <file> <string>\n" 141 "grep for a #define\n" 142 "exitcodes: integer of the found string (no decimals)\n", 143 argv[0]); 144 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 145 &dwWritten, NULL); 146 return 2; 147 } 148 return GrepForDefine(argv[2], argv[3]); 149 case 's': 150 if (argc == 2) { 151 chars = snprintf(msg, sizeof(msg) - 1, 152 "usage: %s -s <substitutions file> <file>\n" 153 "Perform a set of string map type substutitions on a file\n" 154 "exitcodes: 0\n", 155 argv[0]); 156 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 157 &dwWritten, NULL); 158 return 2; 159 } 160 return SubstituteFile(argv[2], argv[3]); 161 case 'V': 162 if (argc != 4) { 163 chars = snprintf(msg, sizeof(msg) - 1, 164 "usage: %s -V filename matchstring\n" 165 "Extract a version from a file:\n" 166 "eg: pkgIndex.tcl \"package ifneeded http\"", 167 argv[0]); 168 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 169 &dwWritten, NULL); 170 return 0; 171 } 172 printf("%s\n", GetVersionFromFile(argv[2], argv[3])); 173 return 0; 174 case 'Q': 175 if (argc != 3) { 176 chars = snprintf(msg, sizeof(msg) - 1, 177 "usage: %s -q path\n" 178 "Emit the fully qualified path\n" 179 "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); 180 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 181 &dwWritten, NULL); 182 return 2; 183 } 184 return QualifyPath(argv[2]); 185 } 186 } 187 chars = snprintf(msg, sizeof(msg) - 1, 188 "usage: %s -c|-l|-f|-g|-V|-s|-Q ...\n" 189 "This is a little helper app to equalize shell differences between WinNT and\n" 190 "Win9x and get nmake.exe to accomplish its job.\n", 191 argv[0]); 192 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL); 193 return 2; 194} 195 196int 197CheckForCompilerFeature( 198 const char *option) 199{ 200 STARTUPINFO si; 201 PROCESS_INFORMATION pi; 202 SECURITY_ATTRIBUTES sa; 203 DWORD threadID; 204 char msg[300]; 205 BOOL ok; 206 HANDLE hProcess, h, pipeThreads[2]; 207 char cmdline[100]; 208 209 hProcess = GetCurrentProcess(); 210 211 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 212 ZeroMemory(&si, sizeof(STARTUPINFO)); 213 si.cb = sizeof(STARTUPINFO); 214 si.dwFlags = STARTF_USESTDHANDLES; 215 si.hStdInput = INVALID_HANDLE_VALUE; 216 217 ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); 218 sa.nLength = sizeof(SECURITY_ATTRIBUTES); 219 sa.lpSecurityDescriptor = NULL; 220 sa.bInheritHandle = FALSE; 221 222 /* 223 * Create a non-inheritible pipe. 224 */ 225 226 CreatePipe(&Out.pipe, &h, &sa, 0); 227 228 /* 229 * Dupe the write side, make it inheritible, and close the original. 230 */ 231 232 DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE, 233 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 234 235 /* 236 * Same as above, but for the error side. 237 */ 238 239 CreatePipe(&Err.pipe, &h, &sa, 0); 240 DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE, 241 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 242 243 /* 244 * Base command line. 245 */ 246 247 lstrcpy(cmdline, "cl.exe -nologo -c -TC -Zs -X -Fp.\\_junk.pch "); 248 249 /* 250 * Append our option for testing 251 */ 252 253 lstrcat(cmdline, option); 254 255 /* 256 * Filename to compile, which exists, but is nothing and empty. 257 */ 258 259 lstrcat(cmdline, " .\\nul"); 260 261 ok = CreateProcess( 262 NULL, /* Module name. */ 263 cmdline, /* Command line. */ 264 NULL, /* Process handle not inheritable. */ 265 NULL, /* Thread handle not inheritable. */ 266 TRUE, /* yes, inherit handles. */ 267 DETACHED_PROCESS, /* No console for you. */ 268 NULL, /* Use parent's environment block. */ 269 NULL, /* Use parent's starting directory. */ 270 &si, /* Pointer to STARTUPINFO structure. */ 271 &pi); /* Pointer to PROCESS_INFORMATION structure. */ 272 273 if (!ok) { 274 DWORD err = GetLastError(); 275 int chars = snprintf(msg, sizeof(msg) - 1, 276 "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); 277 278 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| 279 FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars], 280 (300-chars), 0); 281 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg,lstrlen(msg), &err,NULL); 282 return 2; 283 } 284 285 /* 286 * Close our references to the write handles that have now been inherited. 287 */ 288 289 CloseHandle(si.hStdOutput); 290 CloseHandle(si.hStdError); 291 292 WaitForInputIdle(pi.hProcess, 5000); 293 CloseHandle(pi.hThread); 294 295 /* 296 * Start the pipe reader threads. 297 */ 298 299 pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID); 300 pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID); 301 302 /* 303 * Block waiting for the process to end. 304 */ 305 306 WaitForSingleObject(pi.hProcess, INFINITE); 307 CloseHandle(pi.hProcess); 308 309 /* 310 * Wait for our pipe to get done reading, should it be a little slow. 311 */ 312 313 WaitForMultipleObjects(2, pipeThreads, TRUE, 500); 314 CloseHandle(pipeThreads[0]); 315 CloseHandle(pipeThreads[1]); 316 317 /* 318 * Look for the commandline warning code in both streams. 319 * - in MSVC 6 & 7 we get D4002, in MSVC 8 we get D9002. 320 */ 321 322 return !(strstr(Out.buffer, "D4002") != NULL 323 || strstr(Err.buffer, "D4002") != NULL 324 || strstr(Out.buffer, "D9002") != NULL 325 || strstr(Err.buffer, "D9002") != NULL 326 || strstr(Out.buffer, "D2021") != NULL 327 || strstr(Err.buffer, "D2021") != NULL); 328} 329 330int 331CheckForLinkerFeature( 332 const char *option) 333{ 334 STARTUPINFO si; 335 PROCESS_INFORMATION pi; 336 SECURITY_ATTRIBUTES sa; 337 DWORD threadID; 338 char msg[300]; 339 BOOL ok; 340 HANDLE hProcess, h, pipeThreads[2]; 341 char cmdline[100]; 342 343 hProcess = GetCurrentProcess(); 344 345 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 346 ZeroMemory(&si, sizeof(STARTUPINFO)); 347 si.cb = sizeof(STARTUPINFO); 348 si.dwFlags = STARTF_USESTDHANDLES; 349 si.hStdInput = INVALID_HANDLE_VALUE; 350 351 ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); 352 sa.nLength = sizeof(SECURITY_ATTRIBUTES); 353 sa.lpSecurityDescriptor = NULL; 354 sa.bInheritHandle = TRUE; 355 356 /* 357 * Create a non-inheritible pipe. 358 */ 359 360 CreatePipe(&Out.pipe, &h, &sa, 0); 361 362 /* 363 * Dupe the write side, make it inheritible, and close the original. 364 */ 365 366 DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE, 367 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 368 369 /* 370 * Same as above, but for the error side. 371 */ 372 373 CreatePipe(&Err.pipe, &h, &sa, 0); 374 DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE, 375 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 376 377 /* 378 * Base command line. 379 */ 380 381 lstrcpy(cmdline, "link.exe -nologo "); 382 383 /* 384 * Append our option for testing. 385 */ 386 387 lstrcat(cmdline, option); 388 389 ok = CreateProcess( 390 NULL, /* Module name. */ 391 cmdline, /* Command line. */ 392 NULL, /* Process handle not inheritable. */ 393 NULL, /* Thread handle not inheritable. */ 394 TRUE, /* yes, inherit handles. */ 395 DETACHED_PROCESS, /* No console for you. */ 396 NULL, /* Use parent's environment block. */ 397 NULL, /* Use parent's starting directory. */ 398 &si, /* Pointer to STARTUPINFO structure. */ 399 &pi); /* Pointer to PROCESS_INFORMATION structure. */ 400 401 if (!ok) { 402 DWORD err = GetLastError(); 403 int chars = snprintf(msg, sizeof(msg) - 1, 404 "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); 405 406 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| 407 FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars], 408 (300-chars), 0); 409 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg,lstrlen(msg), &err,NULL); 410 return 2; 411 } 412 413 /* 414 * Close our references to the write handles that have now been inherited. 415 */ 416 417 CloseHandle(si.hStdOutput); 418 CloseHandle(si.hStdError); 419 420 WaitForInputIdle(pi.hProcess, 5000); 421 CloseHandle(pi.hThread); 422 423 /* 424 * Start the pipe reader threads. 425 */ 426 427 pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID); 428 pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID); 429 430 /* 431 * Block waiting for the process to end. 432 */ 433 434 WaitForSingleObject(pi.hProcess, INFINITE); 435 CloseHandle(pi.hProcess); 436 437 /* 438 * Wait for our pipe to get done reading, should it be a little slow. 439 */ 440 441 WaitForMultipleObjects(2, pipeThreads, TRUE, 500); 442 CloseHandle(pipeThreads[0]); 443 CloseHandle(pipeThreads[1]); 444 445 /* 446 * Look for the commandline warning code in the stderr stream. 447 */ 448 449 return !(strstr(Out.buffer, "LNK1117") != NULL || 450 strstr(Err.buffer, "LNK1117") != NULL || 451 strstr(Out.buffer, "LNK4044") != NULL || 452 strstr(Err.buffer, "LNK4044") != NULL); 453} 454 455DWORD WINAPI 456ReadFromPipe( 457 LPVOID args) 458{ 459 pipeinfo *pi = (pipeinfo *) args; 460 char *lastBuf = pi->buffer; 461 DWORD dwRead; 462 BOOL ok; 463 464 again: 465 if (lastBuf - pi->buffer + CHUNK > STATICBUFFERSIZE) { 466 CloseHandle(pi->pipe); 467 return (DWORD)-1; 468 } 469 ok = ReadFile(pi->pipe, lastBuf, CHUNK, &dwRead, 0L); 470 if (!ok || dwRead == 0) { 471 CloseHandle(pi->pipe); 472 return 0; 473 } 474 lastBuf += dwRead; 475 goto again; 476 477 return 0; /* makes the compiler happy */ 478} 479 480int 481IsIn( 482 const char *string, 483 const char *substring) 484{ 485 return (strstr(string, substring) != NULL); 486} 487 488/* 489 * Find a specified #define by name. 490 * 491 * If the line is '#define TCL_VERSION "8.5"', it returns 85 as the result. 492 */ 493 494int 495GrepForDefine( 496 const char *file, 497 const char *string) 498{ 499 char s1[51], s2[51], s3[51]; 500 FILE *f = fopen(file, "rt"); 501 502 if (f == NULL) { 503 return 0; 504 } 505 506 do { 507 int r = fscanf(f, "%50s", s1); 508 509 if (r == 1 && !strcmp(s1, "#define")) { 510 /* 511 * Get next two words. 512 */ 513 514 r = fscanf(f, "%50s %50s", s2, s3); 515 if (r != 2) { 516 continue; 517 } 518 519 /* 520 * Is the first word what we're looking for? 521 */ 522 523 if (!strcmp(s2, string)) { 524 double d1; 525 526 fclose(f); 527 528 /* 529 * Add 1 past first double quote char. "8.5" 530 */ 531 532 d1 = atof(s3 + 1); /* 8.5 */ 533 while (floor(d1) != d1) { 534 d1 *= 10.0; 535 } 536 return ((int) d1); /* 85 */ 537 } 538 } 539 } while (!feof(f)); 540 541 fclose(f); 542 return 0; 543} 544 545/* 546 * GetVersionFromFile -- 547 * Looks for a match string in a file and then returns the version 548 * following the match where a version is anything acceptable to 549 * package provide or package ifneeded. 550 */ 551 552const char * 553GetVersionFromFile( 554 const char *filename, 555 const char *match) 556{ 557 size_t cbBuffer = 100; 558 static char szBuffer[100]; 559 char *szResult = NULL; 560 FILE *fp = fopen(filename, "rt"); 561 562 if (fp != NULL) { 563 /* 564 * Read data until we see our match string. 565 */ 566 567 while (fgets(szBuffer, cbBuffer, fp) != NULL) { 568 LPSTR p, q; 569 570 p = strstr(szBuffer, match); 571 if (p != NULL) { 572 /* 573 * Skip to first digit. 574 */ 575 576 while (*p && !isdigit(*p)) { 577 ++p; 578 } 579 580 /* 581 * Find ending whitespace. 582 */ 583 584 q = p; 585 while (*q && (isalnum(*q) || *q == '.')) { 586 ++q; 587 } 588 589 memcpy(szBuffer, p, q - p); 590 szBuffer[q-p] = 0; 591 szResult = szBuffer; 592 break; 593 } 594 } 595 fclose(fp); 596 } 597 return szResult; 598} 599 600/* 601 * List helpers for the SubstituteFile function 602 */ 603 604typedef struct list_item_t { 605 struct list_item_t *nextPtr; 606 char * key; 607 char * value; 608} list_item_t; 609 610/* insert a list item into the list (list may be null) */ 611static list_item_t * 612list_insert(list_item_t **listPtrPtr, const char *key, const char *value) 613{ 614 list_item_t *itemPtr = malloc(sizeof(list_item_t)); 615 if (itemPtr) { 616 itemPtr->key = strdup(key); 617 itemPtr->value = strdup(value); 618 itemPtr->nextPtr = NULL; 619 620 while(*listPtrPtr) { 621 listPtrPtr = &(*listPtrPtr)->nextPtr; 622 } 623 *listPtrPtr = itemPtr; 624 } 625 return itemPtr; 626} 627 628static void 629list_free(list_item_t **listPtrPtr) 630{ 631 list_item_t *tmpPtr, *listPtr = *listPtrPtr; 632 while (listPtr) { 633 tmpPtr = listPtr; 634 listPtr = listPtr->nextPtr; 635 free(tmpPtr->key); 636 free(tmpPtr->value); 637 free(tmpPtr); 638 } 639} 640 641/* 642 * SubstituteFile -- 643 * As windows doesn't provide anything useful like sed and it's unreliable 644 * to use the tclsh you are building against (consider x-platform builds - 645 * eg compiling AMD64 target from IX86) we provide a simple substitution 646 * option here to handle autoconf style substitutions. 647 * The substitution file is whitespace and line delimited. The file should 648 * consist of lines matching the regular expression: 649 * \s*\S+\s+\S*$ 650 * 651 * Usage is something like: 652 * nmakehlp -S << $** > $@ 653 * @PACKAGE_NAME@ $(PACKAGE_NAME) 654 * @PACKAGE_VERSION@ $(PACKAGE_VERSION) 655 * << 656 */ 657 658int 659SubstituteFile( 660 const char *substitutions, 661 const char *filename) 662{ 663 size_t cbBuffer = 1024; 664 static char szBuffer[1024], szCopy[1024]; 665 char *szResult = NULL; 666 list_item_t *substPtr = NULL; 667 FILE *fp, *sp; 668 669 fp = fopen(filename, "rt"); 670 if (fp != NULL) { 671 672 /* 673 * Build a list of substutitions from the first filename 674 */ 675 676 sp = fopen(substitutions, "rt"); 677 if (sp != NULL) { 678 while (fgets(szBuffer, cbBuffer, sp) != NULL) { 679 char *ks, *ke, *vs, *ve; 680 ks = szBuffer; 681 while (ks && *ks && isspace(*ks)) ++ks; 682 ke = ks; 683 while (ke && *ke && !isspace(*ke)) ++ke; 684 vs = ke; 685 while (vs && *vs && isspace(*vs)) ++vs; 686 ve = vs; 687 while (ve && *ve && !(*ve == '\r' || *ve == '\n')) ++ve; 688 *ke = 0, *ve = 0; 689 list_insert(&substPtr, ks, vs); 690 } 691 fclose(sp); 692 } 693 694 /* debug: dump the list */ 695#ifdef _DEBUG 696 { 697 int n = 0; 698 list_item_t *p = NULL; 699 for (p = substPtr; p != NULL; p = p->nextPtr, ++n) { 700 fprintf(stderr, "% 3d '%s' => '%s'\n", n, p->key, p->value); 701 } 702 } 703#endif 704 705 /* 706 * Run the substitutions over each line of the input 707 */ 708 709 while (fgets(szBuffer, cbBuffer, fp) != NULL) { 710 list_item_t *p = NULL; 711 for (p = substPtr; p != NULL; p = p->nextPtr) { 712 char *m = strstr(szBuffer, p->key); 713 if (m) { 714 char *cp, *op, *sp; 715 cp = szCopy; 716 op = szBuffer; 717 while (op != m) *cp++ = *op++; 718 sp = p->value; 719 while (sp && *sp) *cp++ = *sp++; 720 op += strlen(p->key); 721 while (*op) *cp++ = *op++; 722 *cp = 0; 723 memcpy(szBuffer, szCopy, sizeof(szCopy)); 724 } 725 } 726 printf(szBuffer); 727 } 728 729 list_free(&substPtr); 730 } 731 fclose(fp); 732 return 0; 733} 734 735/* 736 * QualifyPath -- 737 * 738 * This composes the current working directory with a provided path 739 * and returns the fully qualified and normalized path. 740 * Mostly needed to setup paths for testing. 741 */ 742 743int 744QualifyPath( 745 const char *szPath) 746{ 747 char szCwd[MAX_PATH + 1]; 748 char szTmp[MAX_PATH + 1]; 749 char *p; 750 GetCurrentDirectory(MAX_PATH, szCwd); 751 while ((p = strchr(szPath, '/')) && *p) 752 *p = '\\'; 753 PathCombine(szTmp, szCwd, szPath); 754 PathCanonicalize(szCwd, szTmp); 755 printf("%s\n", szCwd); 756 return 0; 757} 758 759/* 760 * Local variables: 761 * mode: c 762 * c-basic-offset: 4 763 * fill-column: 78 764 * indent-tabs-mode: t 765 * tab-width: 8 766 * End: 767 */ 768