OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/process_singleton.h" | 5 #include "chrome/browser/process_singleton.h" |
6 | 6 |
7 #include <shellapi.h> | 7 #include <shellapi.h> |
8 #include <shobjidl.h> | 8 #include <shobjidl.h> |
9 | 9 |
10 #include "base/base_paths.h" | 10 #include "base/base_paths.h" |
(...skipping 29 matching lines...) Expand all Loading... | |
40 #include "ui/base/l10n/l10n_util.h" | 40 #include "ui/base/l10n/l10n_util.h" |
41 #include "ui/base/win/hwnd_util.h" | 41 #include "ui/base/win/hwnd_util.h" |
42 | 42 |
43 namespace { | 43 namespace { |
44 | 44 |
45 const char kLockfile[] = "lockfile"; | 45 const char kLockfile[] = "lockfile"; |
46 | 46 |
47 const char kSearchUrl[] = | 47 const char kSearchUrl[] = |
48 "http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8"; | 48 "http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8"; |
49 | 49 |
50 const int kImmersiveChromeInitTimeout = 500; | 50 const int kMetroChromeActivationTimeoutMs = 3000; |
51 | |
52 // A helper class that acquires the given |mutex| while the AutoLockMutex is in | |
53 // scope. | |
54 class AutoLockMutex { | |
55 public: | |
56 explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) { | |
57 DWORD result = WaitForSingleObject(mutex_, INFINITE); | |
58 DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; | |
59 } | |
60 | |
61 ~AutoLockMutex() { | |
62 BOOL released = ReleaseMutex(mutex_); | |
63 DPCHECK(released); | |
64 } | |
65 | |
66 private: | |
67 HANDLE mutex_; | |
68 DISALLOW_COPY_AND_ASSIGN(AutoLockMutex); | |
69 }; | |
70 | |
71 // A helper class that releases the given |mutex| while the AutoUnlockMutex is | |
72 // in scope and immediately re-acquires it when going out of scope. | |
73 class AutoUnlockMutex { | |
74 public: | |
75 explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) { | |
76 BOOL released = ReleaseMutex(mutex_); | |
77 DPCHECK(released); | |
78 } | |
79 | |
80 ~AutoUnlockMutex() { | |
81 DWORD result = WaitForSingleObject(mutex_, INFINITE); | |
82 DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; | |
83 } | |
84 | |
85 private: | |
86 HANDLE mutex_; | |
87 DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex); | |
88 }; | |
51 | 89 |
52 // Checks the visibility of the enumerated window and signals once a visible | 90 // Checks the visibility of the enumerated window and signals once a visible |
53 // window has been found. | 91 // window has been found. |
54 BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { | 92 BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { |
55 bool* result = reinterpret_cast<bool*>(param); | 93 bool* result = reinterpret_cast<bool*>(param); |
56 *result = IsWindowVisible(window) != 0; | 94 *result = IsWindowVisible(window) != 0; |
57 // Stops enumeration if a visible window has been found. | 95 // Stops enumeration if a visible window has been found. |
58 return !*result; | 96 return !*result; |
59 } | 97 } |
60 | 98 |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
201 if (integrity_level == base::HIGH_INTEGRITY) | 239 if (integrity_level == base::HIGH_INTEGRITY) |
202 return false; | 240 return false; |
203 | 241 |
204 FilePath default_user_data_dir; | 242 FilePath default_user_data_dir; |
205 if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir)) | 243 if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir)) |
206 return false; | 244 return false; |
207 | 245 |
208 if (default_user_data_dir != user_data_dir) | 246 if (default_user_data_dir != user_data_dir) |
209 return false; | 247 return false; |
210 | 248 |
211 // TODO(gab): This is a temporary solution to avoid activating Metro Chrome | |
212 // when chrome.exe is invoked with one of the short-lived commands below. The | |
213 // long-term and correct solution is to only check/activate Chrome later; | |
214 // after handling of these short-lived commands has occured | |
215 // (http://crbug.com/155585). | |
216 // This is a 1:1 mapping of the switches that force an early exit of Chrome in | |
217 // ChromeBrowserMainParts::PreMainMessageLoopRunImpl(). | |
218 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUninstall) || | |
219 CommandLine::ForCurrentProcess()->HasSwitch(switches::kHideIcons) || | |
220 CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowIcons) || | |
221 CommandLine::ForCurrentProcess()->HasSwitch( | |
222 switches::kMakeDefaultBrowser) || | |
223 CommandLine::ForCurrentProcess()->HasSwitch(switches::kPackExtension)) { | |
224 return false; | |
225 } | |
226 | |
227 base::win::RegKey reg_key; | 249 base::win::RegKey reg_key; |
228 DWORD reg_value = 0; | 250 DWORD reg_value = 0; |
229 if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath, | 251 if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath, |
230 KEY_READ) == ERROR_SUCCESS && | 252 KEY_READ) == ERROR_SUCCESS && |
231 reg_key.ReadValueDW(chrome::kLaunchModeValue, | 253 reg_key.ReadValueDW(chrome::kLaunchModeValue, |
232 ®_value) == ERROR_SUCCESS) { | 254 ®_value) == ERROR_SUCCESS) { |
233 return reg_value == 1; | 255 return reg_value == 1; |
234 } | 256 } |
235 return base::win::IsMachineATablet(); | 257 return base::win::IsMachineATablet(); |
236 } | 258 } |
(...skipping 24 matching lines...) Expand all Loading... | |
261 ::SetForegroundWindow(hwnd); | 283 ::SetForegroundWindow(hwnd); |
262 break; | 284 break; |
263 } | 285 } |
264 ::Sleep(10); | 286 ::Sleep(10); |
265 } | 287 } |
266 return true; | 288 return true; |
267 } | 289 } |
268 return false; | 290 return false; |
269 } | 291 } |
270 | 292 |
271 // Look for a Chrome instance that uses the same profile directory. | |
272 // If there isn't one, create a message window with its title set to | |
273 // the profile directory path. | |
274 ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir) | 293 ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir) |
275 : window_(NULL), locked_(false), foreground_window_(NULL), | 294 : window_(NULL), locked_(false), foreground_window_(NULL), |
276 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE) { | 295 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE), |
277 // For Windows 8 and above check if we need to relaunch into Windows 8 | 296 user_data_dir_(user_data_dir) { |
278 // immersive mode. | |
279 if (ShouldLaunchInWindows8ImmersiveMode(user_data_dir)) { | |
280 bool immersive_chrome_launched = ActivateMetroChrome(); | |
281 if (!immersive_chrome_launched) { | |
282 LOG(WARNING) << "Failed to launch immersive chrome"; | |
283 } else { | |
284 // Sleep to allow the immersive chrome process to create its initial | |
285 // message window. | |
286 SleepEx(kImmersiveChromeInitTimeout, FALSE); | |
287 } | |
288 } | |
289 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, | |
290 chrome::kMessageWindowClass, | |
291 user_data_dir.value().c_str()); | |
292 if (!remote_window_ && !EscapeVirtualization(user_data_dir)) { | |
293 // Make sure we will be the one and only process creating the window. | |
294 // We use a named Mutex since we are protecting against multi-process | |
295 // access. As documented, it's clearer to NOT request ownership on creation | |
296 // since it isn't guaranteed we will get it. It is better to create it | |
297 // without ownership and explicitly get the ownership afterward. | |
298 std::wstring mutex_name(L"Local\\ChromeProcessSingletonStartup!"); | |
299 base::win::ScopedHandle only_me( | |
300 CreateMutex(NULL, FALSE, mutex_name.c_str())); | |
301 DCHECK(only_me.Get() != NULL) << "GetLastError = " << GetLastError(); | |
302 | |
303 // This is how we acquire the mutex (as opposed to the initial ownership). | |
304 DWORD result = WaitForSingleObject(only_me, INFINITE); | |
305 DCHECK(result == WAIT_OBJECT_0) << "Result = " << result << | |
306 "GetLastError = " << GetLastError(); | |
307 | |
308 // We now own the mutex so we are the only process that can create the | |
309 // window at this time, but we must still check if someone created it | |
310 // between the time where we looked for it above and the time the mutex | |
311 // was given to us. | |
312 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, | |
313 chrome::kMessageWindowClass, | |
314 user_data_dir.value().c_str()); | |
315 if (!remote_window_) { | |
316 // We have to make sure there is no Chrome instance running on another | |
317 // machine that uses the same profile. | |
318 FilePath lock_file_path = user_data_dir.AppendASCII(kLockfile); | |
319 lock_file_ = CreateFile(lock_file_path.value().c_str(), | |
320 GENERIC_WRITE, | |
321 FILE_SHARE_READ, | |
322 NULL, | |
323 CREATE_ALWAYS, | |
324 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, | |
325 NULL); | |
326 DWORD error = GetLastError(); | |
327 LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && | |
328 error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; | |
329 LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) | |
330 << "Lock file can not be created! Error code: " << error; | |
331 | |
332 if (lock_file_ != INVALID_HANDLE_VALUE) { | |
333 HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc); | |
334 | |
335 WNDCLASSEX wc = {0}; | |
336 wc.cbSize = sizeof(wc); | |
337 wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>; | |
338 wc.hInstance = hinst; | |
339 wc.lpszClassName = chrome::kMessageWindowClass; | |
340 ATOM clazz = ::RegisterClassEx(&wc); | |
341 DCHECK(clazz); | |
342 | |
343 // Set the window's title to the path of our user data directory so | |
344 // other Chrome instances can decide if they should forward to us. | |
345 window_ = ::CreateWindow(MAKEINTATOM(clazz), | |
346 user_data_dir.value().c_str(), | |
347 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this); | |
348 CHECK(window_); | |
349 } | |
350 } | |
351 BOOL success = ReleaseMutex(only_me); | |
352 DCHECK(success) << "GetLastError = " << GetLastError(); | |
353 } | |
354 } | 297 } |
355 | 298 |
356 ProcessSingleton::~ProcessSingleton() { | 299 ProcessSingleton::~ProcessSingleton() { |
357 // We need to unregister the window as late as possible so that we can detect | 300 // We need to unregister the window as late as possible so that we can detect |
358 // another instance of chrome running. Otherwise we may end up writing out | 301 // another instance of chrome running. Otherwise we may end up writing out |
359 // data while a new chrome is starting up. | 302 // data while a new chrome is starting up. |
360 if (window_) { | 303 if (window_) { |
361 ::DestroyWindow(window_); | 304 ::DestroyWindow(window_); |
362 ::UnregisterClass(chrome::kMessageWindowClass, | 305 ::UnregisterClass(chrome::kMessageWindowClass, |
363 base::GetModuleFromAddress(&ThunkWndProc)); | 306 base::GetModuleFromAddress(&ThunkWndProc)); |
364 } | 307 } |
365 if (lock_file_ != INVALID_HANDLE_VALUE) | 308 if (lock_file_ != INVALID_HANDLE_VALUE) |
366 CloseHandle(lock_file_); | 309 CloseHandle(lock_file_); |
367 } | 310 } |
368 | 311 |
312 // Code roughly based on Mozilla. | |
369 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { | 313 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { |
370 if (is_virtualized_) | 314 if (is_virtualized_) |
371 return PROCESS_NOTIFIED; // We already spawned the process in this case. | 315 return PROCESS_NOTIFIED; // We already spawned the process in this case. |
372 if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) { | 316 if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) { |
373 return LOCK_ERROR; | 317 return LOCK_ERROR; |
374 } else if (!remote_window_) { | 318 } else if (!remote_window_) { |
375 g_browser_process->PlatformSpecificCommandLineProcessing( | 319 g_browser_process->PlatformSpecificCommandLineProcessing( |
376 *CommandLine::ForCurrentProcess()); | 320 *CommandLine::ForCurrentProcess()); |
377 return PROCESS_NONE; | 321 return PROCESS_NONE; |
378 } | 322 } |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
474 } | 418 } |
475 | 419 |
476 // Time to take action. Kill the browser process. | 420 // Time to take action. Kill the browser process. |
477 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true); | 421 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true); |
478 remote_window_ = NULL; | 422 remote_window_ = NULL; |
479 return PROCESS_NONE; | 423 return PROCESS_NONE; |
480 } | 424 } |
481 | 425 |
482 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate( | 426 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate( |
483 const NotificationCallback& notification_callback) { | 427 const NotificationCallback& notification_callback) { |
484 NotifyResult result = NotifyOtherProcess(); | 428 ProcessSingleton::NotifyResult result = PROCESS_NONE; |
485 if (result != PROCESS_NONE) | 429 if (!Create(notification_callback)) { |
486 return result; | 430 result = NotifyOtherProcess(); |
487 return Create(notification_callback) ? PROCESS_NONE : PROFILE_IN_USE; | 431 if (result == PROCESS_NONE) |
432 result = PROFILE_IN_USE; | |
433 } | |
434 return result; | |
488 } | 435 } |
489 | 436 |
490 // On Windows, there is no need to call Create() since the message | 437 // Look for a Chrome instance that uses the same profile directory. If there |
491 // window is created in the constructor but to avoid having more | 438 // isn't one, create a message window with its title set to the profile |
492 // platform specific code in browser_main.cc we tolerate calls to | 439 // directory path. |
493 // Create(). | |
494 bool ProcessSingleton::Create( | 440 bool ProcessSingleton::Create( |
495 const NotificationCallback& notification_callback) { | 441 const NotificationCallback& notification_callback) { |
496 DCHECK(!remote_window_); | |
497 DCHECK(notification_callback_.is_null()); | 442 DCHECK(notification_callback_.is_null()); |
498 | 443 |
444 static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!"; | |
445 static const wchar_t kMetroActivationEventName[] = | |
446 L"Local\\ChromeProcessSingletonStartupMetroActivation!"; | |
447 | |
448 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, | |
449 chrome::kMessageWindowClass, | |
450 user_data_dir_.value().c_str()); | |
451 if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) { | |
452 // Make sure we will be the one and only process creating the window. | |
453 // We use a named Mutex since we are protecting against multi-process | |
454 // access. As documented, it's clearer to NOT request ownership on creation | |
455 // since it isn't guaranteed we will get it. It is better to create it | |
456 // without ownership and explicitly get the ownership afterward. | |
457 base::win::ScopedHandle only_me(CreateMutex(NULL, FALSE, kMutexName)); | |
ananta
2012/12/04 19:11:54
I think the mutex name needs to be formed off the
gab
2012/12/04 19:44:18
As discussed offline,
1) This has always been the
grt (UTC plus 2)
2012/12/04 19:55:23
We switched to using a global mutex about two year
| |
458 DPCHECK(only_me.IsValid()); | |
459 | |
460 AutoLockMutex auto_lock_only_me(only_me); | |
461 | |
462 // We now own the mutex so we are the only process that can create the | |
463 // window at this time, but we must still check if someone created it | |
464 // between the time where we looked for it above and the time the mutex | |
465 // was given to us. | |
466 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, | |
467 chrome::kMessageWindowClass, | |
468 user_data_dir_.value().c_str()); | |
469 | |
470 | |
471 // In Win8+, a new Chrome process launched in Desktop mode may need to be | |
472 // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for | |
473 // heuristics). To accomplish this, the current Chrome activates Metro | |
474 // Chrome, releases the startup mutex, and waits for metro Chrome to take | |
475 // the singleton. From that point onward, the command line for this Chrome | |
476 // process will be sent to Metro Chrome by the usual channels. | |
477 if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 && | |
478 !base::win::IsMetroProcess()) { | |
479 // |metro_activation_event| is created right before activating a Metro | |
480 // Chrome (note that there can only be one Metro Chrome process; by OS | |
481 // design); all following Desktop processes will then wait for this event | |
482 // to be signaled by Metro Chrome which will do so as soon as it grabs | |
483 // this singleton (should any of the waiting processes timeout waiting for | |
484 // the signal they will try to grab the singleton for themselves which | |
485 // will result in a forced Desktop Chrome launch in the worst case). | |
486 base::win::ScopedHandle metro_activation_event( | |
487 OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName)); | |
488 if (!metro_activation_event.IsValid() && | |
489 ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) { | |
490 // No Metro activation is under way, but the desire is to launch in | |
491 // Metro mode: activate and rendez-vous with the activated process. | |
492 metro_activation_event.Set( | |
493 CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName)); | |
494 if (!ActivateMetroChrome()) { | |
495 // Failed to launch immersive Chrome, default to launching on Desktop. | |
496 LOG(ERROR) << "Failed to launch immersive chrome"; | |
497 metro_activation_event.Close(); | |
498 } | |
499 } | |
500 | |
501 if (metro_activation_event.IsValid()) { | |
502 // Release |only_me| (to let Metro Chrome grab this singleton) and wait | |
503 // until the event is signaled (i.e. Metro Chrome was successfully | |
504 // activated). Ignore timeout waiting for |metro_activation_event|. | |
505 { | |
506 AutoUnlockMutex auto_unlock_only_me(only_me); | |
507 | |
508 DWORD result = WaitForSingleObject(metro_activation_event, | |
509 kMetroChromeActivationTimeoutMs); | |
510 DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT) | |
511 << "Result = " << result; | |
512 } | |
513 | |
514 // Check if this singleton was successfully grabbed by another process | |
515 // (hopefully Metro Chrome). Failing to do so, this process will grab | |
516 // the singleton and launch in Desktop mode. | |
517 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, | |
518 chrome::kMessageWindowClass, | |
519 user_data_dir_.value().c_str()); | |
520 } | |
521 } | |
522 | |
523 if (!remote_window_) { | |
524 // We have to make sure there is no Chrome instance running on another | |
525 // machine that uses the same profile. | |
526 FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile); | |
527 lock_file_ = CreateFile(lock_file_path.value().c_str(), | |
528 GENERIC_WRITE, | |
529 FILE_SHARE_READ, | |
530 NULL, | |
531 CREATE_ALWAYS, | |
532 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, | |
533 NULL); | |
534 DWORD error = GetLastError(); | |
535 LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && | |
536 error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; | |
537 LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) | |
538 << "Lock file can not be created! Error code: " << error; | |
539 | |
540 if (lock_file_ != INVALID_HANDLE_VALUE) { | |
541 HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc); | |
542 | |
543 WNDCLASSEX wc = {0}; | |
544 wc.cbSize = sizeof(wc); | |
545 wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>; | |
546 wc.hInstance = hinst; | |
547 wc.lpszClassName = chrome::kMessageWindowClass; | |
548 ATOM clazz = ::RegisterClassEx(&wc); | |
549 DCHECK(clazz); | |
550 | |
551 // Set the window's title to the path of our user data directory so | |
552 // other Chrome instances can decide if they should forward to us. | |
553 window_ = ::CreateWindow(MAKEINTATOM(clazz), | |
554 user_data_dir_.value().c_str(), | |
555 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this); | |
556 CHECK(window_); | |
557 } | |
558 | |
559 if (base::win::GetVersion() >= base::win::VERSION_WIN8) { | |
560 // Make sure no one is still waiting on Metro activation whether it | |
561 // succeeded (i.e., this is the Metro process) or failed. | |
562 base::win::ScopedHandle metro_activation_event( | |
563 OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName)); | |
564 if (metro_activation_event.IsValid()) | |
565 SetEvent(metro_activation_event); | |
566 } | |
567 } | |
568 } | |
569 | |
499 if (window_ != NULL) | 570 if (window_ != NULL) |
500 notification_callback_ = notification_callback; | 571 notification_callback_ = notification_callback; |
501 | 572 |
502 return window_ != NULL; | 573 return window_ != NULL; |
503 } | 574 } |
504 | 575 |
505 void ProcessSingleton::Cleanup() { | 576 void ProcessSingleton::Cleanup() { |
506 } | 577 } |
507 | 578 |
508 LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) { | 579 LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) { |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
541 switch (message) { | 612 switch (message) { |
542 case WM_COPYDATA: | 613 case WM_COPYDATA: |
543 return OnCopyData(reinterpret_cast<HWND>(wparam), | 614 return OnCopyData(reinterpret_cast<HWND>(wparam), |
544 reinterpret_cast<COPYDATASTRUCT*>(lparam)); | 615 reinterpret_cast<COPYDATASTRUCT*>(lparam)); |
545 default: | 616 default: |
546 break; | 617 break; |
547 } | 618 } |
548 | 619 |
549 return ::DefWindowProc(hwnd, message, wparam, lparam); | 620 return ::DefWindowProc(hwnd, message, wparam, lparam); |
550 } | 621 } |
OLD | NEW |