65.9K
CodeProject 正在变化。 阅读更多。
Home

PocketPC 的手动卸载程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (13投票s)

2003年6月8日

公共领域

5分钟阅读

viewsIcon

100234

downloadIcon

218

如果你不想使用 CAB,可以这样做。

引言

大多数 CE 应用程序使用 Microsoft 的 CABWIZ 工具来创建其安装程序和卸载程序。我认为这是一个丑陋的工具,用户界面笨拙。本文讨论了如何改写手动安装程序/卸载程序。

实际上,简短的回答是“你不能”。CABWIZ 仍然是必需的。但是,你可以做到的是,只使用 CABWIZ 作为骨架,其唯一的任务是执行你自己的手动卸载程序。因此,本文将讨论这个骨架。

我有一条非常重要的建议:始终检查错误,并在屏幕上显示精确的错误消息。使用 GetLastError()FormatMessage() 来获取错误文本。当你在屏幕上显示它时,请准确说明错误来自何处。不要将多个错误归结为通用的“发生某种失败”消息。因为——我保证——你的安装程序会出错,最终用户会写信给你抱怨,除非你有完整的诊断信息,否则你永远不知道出了什么问题。(为了简单起见,本文省略了错误检查)。

背景

CABWIZ 是 Microsoft 的 eMbedded Visual Tools 3.0(免费)附带的工具。通常,你编写一个 .INF 文件并使用 CABWIZ 进行编译。这将生成一个 .CAB 文件。该 .CAB 文件可以在 PocketPC 上执行,然后它会安装应用程序并在 Settings > RemovePrograms 中添加一个条目。

手动编写安装程序很容易——你只需将可执行文件复制到 \Program 文件夹,并在 \Windows\Programs 中为其添加快捷方式。也许还要在开始菜单中添加快捷方式,也许还要注册一些注册表项。

问题是如何注册你的程序,以便它显示在 Start > Settings > System > RemovePrograms 下。好吧,系统会从注册表项中填充 RemovePrograms 对话框。

HKEY_LOCAL_MACHINE\SOFTWARE\Apps\<Provider> <AppName>

在这些键中,对话框会查找一个名为 InstlDWORD 值;如果设置为 1,则表示应用程序已安装,并应在对话框中列出。如果为 0,则表示应用程序曾经安装过但现在已不再安装,因此不会被列出。

当用户选择卸载已安装的程序时,它会查找注册表项中的 CmdFileIsvFile 值。第一个指向一个 .DAT 文件,通常是 \Windows\AppMgr\<Provider> <AppName>.DAT,该文件由安装程序放置在那里。它以专有的 .DAT 格式编写,例如 CABWIZ 生成的格式。第二个指向一个具有四个特定导出函数的 .DLL 文件,我们将对其进行讨论。

系统内置的卸载程序会执行 DLL 中的导出函数 Uninstall_Init,然后解析并执行 .DAT 文件,最后执行 Uninstall_Exit。因此,对于我们的手动卸载程序来说,我们的手动安装程序必须使用 CABWIZ 生成一个 .DAT 文件,并将其放置在该目录中,同时也将我们的 .DLL 文件也放置在那里。

但是有一个问题。系统在执行初始 CAB 文件(即首次安装软件时)时,还会创建几个其他相关的注册表项。这些额外的注册表项是所有已安装应用程序共享的。而且它们没有被文档化。因此,手动安装是不安全的,因为我们可能无法正确设置这些注册表项。相反,我们必须使用 .CAB 文件进行安装

一切并未失去。我们讨厌基于 CAB 的安装程序,即使我们被迫使用它们,我们至少可以以最小的方式使用它们:这样 CAB 安装程序的唯一任务就是设置这些注册表项,仅此而已。然后,我们可以将安装程序的真正核心逻辑放在我们自己的应用程序中,将卸载程序的真正核心逻辑放在我们自定义编写的 .DLL 文件中。

顺便说一句,当用户单击“Remove”按钮时,系统会暂停一段时间,显示 CE-logo 沙漏(从零点五秒到二十秒,通常接近一秒),然后才执行 setup.dll。这完全没有必要。真是的!

简要说明:我们的安装程序将仅为最小目的调用 CAB,因此我们希望完全静默地调用它——没有任何对话框。以下是实现方法。

// How to install a CAB completely silently

#include <windows.h>

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPTSTR,int)
{
  // get the current application directory
  wchar_t fn[MAX_PATH]; GetModuleFileName(NULL,fn,MAX_PATH);
  wchar_t *c=fn, *lastslash=c;
  while (*c!=0) {if (*c=='\\') lastslash=c+1; c++;}
  wcscpy(lastslash,L"ce_setup.cab");


  // First, if cabwiz thinks that the app was already
  // installed, then it will complain. Hence we lie to it.
  // MUST USE THE CORRECT REGKEY HERE! it is
  //   Provider <space> AppName
  // as defined in ce_setup.inf and used by cabwiz.
  HKEY hkey; LONG lres;
  lres=RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                  L"SOFTWARE\\Apps\\Lu ce_setup",0,0,&hkey);
  if (lres==ERROR_SUCCESS)
  { DWORD val=0;
    RegSetValueEx(hkey,L"Instl",0,REG_DWORD,(LPBYTE)&val,
                  sizeof(val));
    RegCloseKey(hkey);
  }

  // The act of running cabwiz will also delete the cab.
  // I think that's horrible. So I make it readonly
  DWORD attr = GetFileAttributes(fn);
  if (attr==0xFFFFFFFF)
  { return MessageBox(NULL,fn,L"Not found",MB_OK);
  }
  SetFileAttributes(fn,attr|FILE_ATTRIBUTE_READONLY);

  // Now run the cab using wceload, telling it to be quiet:
  const wchar_t *cmd = L"\\windows\\wceload.exe";
  wchar_t arg[MAX_PATH+40];
  wsprintf(arg,L"/noaskdest /noui \"%s\"",fn);
  //
  PROCESS_INFORMATION pi;
  BOOL res=CreateProcess(cmd,arg,0,0,0,0,0,0,0,&pi);
  if (!res) MessageBox(NULL,L"Couldn't",L"ce_setup",MB_OK);
  else
  { WaitForSingleObject(pi.hProcess,50000);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
  }

  // Really, we should wait until the cabwiz has finished
  // before restoring the file attributes. But I put a
  // timeout of 50s in there just in case something went
  // wrong, since it's bad in a PocketPC to leave
  // blocked processes.
  SetFileAttributes(fn,attr);

  return 0;
}

Setup.dll

如前所述,当用户单击“Remove”时,系统将调用一个自定义 DLL。这就是我们将卸载程序的逻辑放入的地方。这是该 DLL 的形式。两个导出安装函数如下:

#include <windows.h>


enum codeINSTALL_INIT
{ codeINSTALL_INIT_CONTINUE=0,
  codeINSTALL_INIT_CANCEL
};
enum codeINSTALL_EXIT
{ codeINSTALL_EXIT_DONE=0,
  codeINSTALL_EXIT_UNINSTALL
};

extern "C" __declspec(dllexport) codeINSTALL_INIT
Install_Init(HWND hpar, BOOL fFirstCall,
             BOOL fPreviouslyInstalled,
             LPCTSTR pszSuggestedInstallDir)
{ // can either return "continue", or abort before we've
  // even started the install
  return codeINSTALL_INIT_CONTINUE;
}

extern "C" __declspec(dllexport) codeINSTALL_EXIT
Install_Exit(HWND hpar, LPCTSTR pszChosenInstallDir,
             WORD cFailedDirs, WORD cFailedFiles,
             WORD cFailedRegKeys, WORD cFailedRegVals,
             WORD cFailedShortcuts)
{ // can either return "okay", or "uninstall what we've just
  // installed". But we're going to return "okay" since it
  // succeeded, and then delete some dummy files. The
  // dummy files were just a workaround around a bug in
  // cabwiz, not a sign of failure.
  wchar_t buf[MAX_PATH]; wcscpy(buf,pszChosenInstallDir);
  wcscat(buf,L"\\ce_setup_dummyN.txt");
  int len=wcslen(buf)-5;
  buf[len]='1'; DeleteFile(buf);
  buf[len]='2'; DeleteFile(buf);
  buf[len]='3'; DeleteFile(buf);
  buf[len]='4'; DeleteFile(buf);
  
  // And continue with other installation work if we want:
  MessageBox(hpar,L"Installing...",L"My App",MB_OK);
  // ...

  return codeINSTALL_EXIT_DONE;
}

临时文件的棘手问题将在下面讨论。

下面给出了两个导出的卸载函数。请注意,我们使用全局变量 install_dir 来记录安装目录。因为系统在调用 Uninstall_Init() 时会给我们这个信息,但在调用 Uninstall_Exit() 时(我们想要它的时候)就不会了。

wchar_t install_dir[MAX_PATH];
//
enum codeUNINSTALL_INIT
{ codeUNINSTALL_INIT_CONTINUE=0,
  codeUNINSTALL_INIT_CANCEL
};
enum codeUNINSTALL_EXIT
{ codeUNINSTALL_EXIT_DONE=0
};

extern "C" __declspec(dllexport) codeUNINSTALL_INIT
Uninstall_Init(HWND hpar, LPCTSTR pszInstallDir)
{ // we can either return "continue", or "abort before we
  // even start the uninstall". We will abort if our
  // application is already open. Note: but first, take
  // this opportunity to record the install_dir.
  // That's because we'll need to refer to it in
  // Uninstall_Exit, but the system doesn't to tell us it
  // again. Hence the need to remember. It's safe to 
  // remember in a global variable, since Uninstall_Init
  // and _Exit are invoked in the same instance of the DLL.
  wcscpy(install_dir,pszInstallDir);
  //
  HWND halready = FindWindow(L"MyMainClass",L"My App");    
  if (!halready) return codeUNINSTALL_INIT_CONTINUE;
  MessageBox(hpar,L"Quit program before uninstalling it",
             L"My App",MB_OK);
  return codeUNINSTALL_INIT_CANCEL;
}

extern "C" __declspec(dllexport) codeUNINSTALL_EXIT
Uninstall_Exit(HWND hpar)
{ // Here we do the main work of our uninstalling:
  MessageBox(hpar,L"Uninstalling...",L"My App",MB_OK);
  // ...
  return codeUNINSTALL_EXIT_DONE;
}


BOOL WINAPI DllMain(HANDLE, DWORD, LPVOID) {return TRUE;}

CAB 文件

如前所述,我们的安装程序将(静默地)执行一个 CAB 文件来设置注册表项。这个 CAB 文件将包含我们在上一节构建的 setup.dll。要构建 CAB 文件,我们将使用 Microsoft 的 CABWIZ。这是控制文件 ce_setup.inf

[Version]
Signature   = "$Windows NT$"
Provider    = "Lu"
CESignature = "$Windows CE$"

[CEStrings]
AppName     = "ce_setup"
InstallDir  = %CE1%

系统会连接 ProviderAppName(中间有一个空格),这个连接结果会显示在 RemovePrograms 对话框中。%CE1% 代表 \Program Files —— 其他 %CE#% 值在在线帮助文档的“Destination Directory Macro Reference”帮助主题中列出。你也可以使用例如 InstallDir = %CE1%\Subdirectory

[DefaultInstall]
CopyFiles = FileList
AddReg    = 
CESetupDLL  = ce_setup.dll

[FileList]
ce_setup_dummy1.txt,ce_setup_dummy.txt,
ce_setup_dummy2.txt,ce_setup_dummy.txt,
ce_setup_dummy3.txt,ce_setup_dummy.txt,
ce_setup_dummy4.txt,ce_setup_dummy.txt,
; Bug! If using a dll, then less than four files
; generate an error when running the cab. ???

[SourceDisksNames]
1 = ,"source directory",,
2 = ,"bin directory",,ARMRel

[SourceDisksFiles]
ce_setup_dummy.txt=1
ce_setup.dll=2

[DestinationDirs]
FileList = 0,%InstallDir%

这里 CESetupDLL 指的是我们之前构建的 setup.dll[SourceDisksFiles] 将其与索引号关联起来,CABWIZ 会在 [SourceDisksNames] 下查找开发机上的文件位置。

[FileList] 是将要安装在 PocketPC 上的所有文件的列表。你可能期望它是空的。但是 CABWIZ 有一个 bug,如果你使用 setup.dll 但有少于四个项目,它就会崩溃。这就是为什么我们包含四个虚拟文件(每个文件大小为 1 字节!)。无论如何,我们的 Install_Exit 函数会在安装完成后立即将它们删除。真是的!

要在此 .inf 文件上调用 CABWIZ,从而构建我们的 .CAB,请使用此命令。(它全部在单行上。将其写入批处理文件!)

c:\progra~1\windows ce tools\wce300\pocket pc 
    2002\support\activesync\windows ce 
    application installation\cabwiz\cabwiz.exe" ce_setup.inf /err cabwiz.log
© . All rights reserved.