суббота, 16 октября 2010 г.

Как запустить ярлык 64-битного приложения из 32-битного через ShellExecute.

В плагине FAR Named Folders (NF) есть полезная функция - запуск приложения из меню "Пуск". Набираете в командной строке команду "cs:a" и вам показывается список приложений, названия которых соответствуют маске "*a*". В Win7 меню "Пуск" было модернизировано, в нем появился подобный фильтр, но лично мне через Far программы запускать по-прежнему гораздо быстрее. К сожалению, после перехода на 64-битную версию Win7, я обнаружил, что некоторые приложения запускаться через NF перестали.

Расследование показало, что возникла проблема с запуском 64-битных приложений из 32-битного FAR. В NF запуск приложений реализован через функцию ShellExecute - через нее запускается ярлык (LNK-файл) приложения, хранящийся в меню "Пуск". Выяснилось, что ShellExecute, вызванная из 32-битного приложения, ярлыки 64-битных приложений обрабатывает неправильно.

Эта проблема описана здесь и здесь. ShellExecute неверно раскрывает путь "%Program files%", который прописан в lnk-файле. 64-битные приложения на 64-битных системах устанавливаются в "C:\Program files", 32-битные в "C:\Program files (x86)". Функция ShellExecute, при запуске из 32-битного приложения, всегда интерпретирует "%Program files%" как "C:\Program files (x86)". 64-битные приложения, естественно, не запускаются.

Я обошел проблему следующим образом. Открываю lnk-файл, через IShellLink получаю полный путь к приложению, заменяю в нем "Program files (x86)" на "Program files" и затем запускаю измененый путь через тот же ShellExecute. Вот пример кода, взятый из исходных кодов Named Folders (слегка упрощенный и отвязанный от проекта).

#include "stdafx.h"
#include <Windows.h>
#include <WinBase.h>
#include <boost/scope_exit.hpp>
#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>
#include <ShlObj.h>  
#include <shellapi.h>
#include <comdef.h>
#include <Shlwapi.h>

#pragma comment(lib, "shlwapi.lib")

typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;

BOOL IsWow64()  {
  BOOL bIsWow64 = FALSE;

  //IsWow64Process is not available on all supported versions of Windows.
  //Use GetModuleHandle to get a handle to the DLL that contains the function
  //and GetProcAddress to get a pointer to the function if available.
  fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
  if (NULL != fnIsWow64Process) {
    if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64)) {
      //handle error
    }
  }
  return bIsWow64;
}

bool GetShortcutProgramPath(std::wstring const& PathToShortcut, std::wstring &destPath) {
  wchar_t wbuffer[MAX_PATH];
  lstrcpy((reinterpret_cast<wchar_t*>(&wbuffer[0])), PathToShortcut.c_str());
  PathUnquoteSpaces((reinterpret_cast<wchar_t*>(&wbuffer[0]))); //remove quotes from path if they exist

  ::CoInitialize(0);
  BOOST_SCOPE_EXIT ( (&wbuffer) ) { //wbuffer is used because BOOST_SCOPE_EXIT doesn't support empty list params
    CoUninitialize();
  } BOOST_SCOPE_EXIT_END;

  IPersistFile* ppf = 0;
  IShellLink* psh = 0;

  HRESULT hr = ::CoCreateInstance(::CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER
    , IID_IPersistFile, reinterpret_cast<void**>(&ppf));
  if (FAILED(hr)) return false;
  BOOST_SCOPE_EXIT( (&ppf) ) {
    ppf->Release();
  } BOOST_SCOPE_EXIT_END;

  hr = ppf->Load((reinterpret_cast<wchar_t*>(&wbuffer[0])), STGM_READ);
  if (FAILED(hr)) return false;

  hr = ppf->QueryInterface(IID_IShellLink, reinterpret_cast<void**>(&psh));
  if (FAILED(hr)) return false;
  BOOST_SCOPE_EXIT( (&psh) ) {
    psh->Release();
  } BOOST_SCOPE_EXIT_END;

  hr = psh->GetPath(&wbuffer[0], MAX_PATH, NULL, SLGP_UNCPRIORITY);
  if (FAILED(hr)) return false;

  destPath = &wbuffer[0];
  return true;
}

void execute_selected_program64_under_w32(std::wstring const& path, std::wstring const& params) {
  std::wstring dest_path;
  if (GetShortcutProgramPath(path, dest_path)) {
    wchar_t buffer[MAX_PATH];
    ::ExpandEnvironmentStringsW(L"%ProgramFiles%", reinterpret_cast<wchar_t*>(&buffer[0]), MAX_PATH);
    std::wstring pf32 = reinterpret_cast<wchar_t*>(&buffer[0]);
    ::ExpandEnvironmentStringsW(L"%ProgramW6432%", reinterpret_cast<wchar_t*>(&buffer[0]), MAX_PATH);
    std::wstring pf64 = reinterpret_cast<wchar_t*>(&buffer[0]);

    if (pf32 != pf64) {
      boost::replace_all(dest_path, pf32, pf64);
      HINSTANCE value = ShellExecuteW(0, NULL , dest_path.c_str(), params.c_str(), NULL, SW_SHOWNORMAL); 
    }
  }
}
void ExecuteProgram(std::wstring const &path, std::wstring const ¶ms) {
  HINSTANCE value = ShellExecuteW(0, NULL , path.c_str(), params.c_str(), NULL, SW_SHOWNORMAL); 
  if ((int)(intptr_t)value < 32) { 
    if (IsWow64()) { //workaround for #6
      execute_selected_program64_under_w32(path, params);
    }
  }
}


int _tmain(int argc, _TCHAR* argv[])
{
  ExecuteProgram(L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Paint.NET.lnk", L"");
  return 0;
}

1 комментарий:

  1. Здорово. А если есть и "c:\Program Files\SomeProgram.exe", и "c:\Program Files (x86)\SomeProgram.exe", а надо запустить именно тот, что указан в ярлыке? (А там может быть указан x86).

    ОтветитьУдалить