VB6 - 如何从应用 运行 作为服务打开文件

VB6 - How do I open a file from an app running as a service

我已经为这个问题苦苦挣扎了将近一个星期。我需要一个 vb6 应用程序,它是 运行ning 作为打开文件的服务。我不需要对文件做任何事情,我只需要打开它。我尝试使用 ShellExecute 和 ShellExecuteEx 以及使用 CreateProcess 尝试从命令行启动文件。当 none 这些实现工作时,我尝试启动另一个应用程序(使用 CreateProcess),其唯一任务是打开文件然后自行关闭。

这些解决方案在应用程序 运行 正常时都有效,但在 运行 作为服务时无效。非常重要的是,应用程序能够在 运行 作为服务打开文件时,无论是直接还是间接,它只需要能够触发它。

我知道 Windows 自 Windows Vista 以来已经锁定了服务与桌面交互的能力,但我确信必须有一种方法可以从中触发文件打开命令一项服务。我开发的应用程序能够从命令行使用 CreateProcess 运行 pg_dump.exe(postgres 数据库的备份可执行文件)来备份数据库文件,同时 运行 宁作为一项服务。这就是为什么我虽然从服务启动一个 exe 以间接打开文件可能有效。但是,由于某种原因,应用程序 运行s pg_dump.exe 可以,但不会 运行 我创建的可执行文件。我想知道我创建的 exe 是否期望在桌面上有某种存在,这就是服务不想启动它的原因。我在辅助 exe 中更改了主窗体的属性,使该窗体不可见,也不会显示在任务栏上,但有些东西告诉我这还不够。

这是我的 CreateProcess 代码(大部分内容不是我写的,请原谅我的无知):

Private Declare Function WaitForSingleObject Lib "KERNEL32" (ByVal _
   hHandle As Long, ByVal dwMilliseconds As Long) As Long

Private Declare Function CloseHandle Lib "KERNEL32" _
   (ByVal hObject As Long) As Long

Private Declare Function GetExitCodeProcess Lib "KERNEL32" _
   (ByVal hProcess As Long, lpExitCode As Long) As Long

'create a new win process.
Private Declare Function CreateProcessA Lib "KERNEL32" (ByVal _
   lpApplicationName As String, ByVal lpCommandLine As String, ByVal _
   lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
   ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
   ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As String, _
   lpStartupInfo As STARTUPINFO, lpProcessInformation As _
   PROCESS_INFORMATION) As Long

'used by CreateProcess
Private Type STARTUPINFO
   cb As Long
   lpReserved As String
   lpDesktop As String
   lpTitle As String
   dwX As Long
   dwY As Long
   dwXSize As Long
   dwYSize As Long
   dwXCountChars As Long
   dwYCountChars As Long
   dwFillAttribute As Long
   dwFlags As Long
   wShowWindow As Integer
   cbReserved2 As Integer
   lpReserved2 As Long
   hStdInput As Long
   hStdOutput As Long
   hStdError As Long
End Type

Private Type PROCESS_INFORMATION
   hProcess As Long
   hThread As Long
   dwProcessID As Long
   dwThreadID As Long
End Type

Const NORMAL_PRIORITY_CLASS = &H20&
Const INFINITE = -1&

Public Function ExecSynchronousCmd(cmdline As String) As Long

    ' - Used to force a shelled command to run synchronously (code will
    '   suspend where this function is called until shelled process
    '   returns a return value)
    ' - There is no time out - it will wait forever!!
    ' - Function returns exit value for shelled process

    Dim proc As PROCESS_INFORMATION
    Dim start As STARTUPINFO
    Dim ret As Long

    'Initialize the STARTUPINFO structure:
    start.cb = Len(start)

    'Start the shelled application:
    ret = CreateProcessA(vbNullString, cmdline$, 0&, 0&, 1&, _
        NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)

    'Wait for the shelled application to finish:
    ret = WaitForSingleObject(proc.hProcess, INFINITE)
    Call GetExitCodeProcess(proc.hProcess, ret&)
    Call CloseHandle(proc.hThread)
    Call CloseHandle(proc.hProcess)
    ExecSynchronousCmd = ret

End Function

这是 运行ning pg_dump.exe 的实现,它在运行从服务中执行 exe 并创建数据库备份文件时成功:

i = ExecSynchronousCmd(Chr$(34) & "C:\Program Files (x86)\PostgreSQL.3\bin\pg_dump.exe" & Chr$(34) & _
                " -Ft " & _
                " -f " & Chr$(34) & tempName & Chr$(34) & _
                " -U " & s1 & _
                " -h " & s3 & _
                " -p " & s4 & _
                " " & sDB(0, x))

这是一个类似的实现,它尝试 运行 将尝试打开相关文件的辅助 exe:

i = ExecSynchronousCmd(Chr$(34) & "C:\Program Files (x86)\GranDocsNP\GDNPOpener.exe" & Chr$(34))

当应用 运行 作为服务时,上述代码不起作用。为什么 pg_dump.exe 在 运行ning 成功了,而我自己的 GDNPOpener.exe 却没有?

如上所述,我还尝试使用 ShellExecute 和 ShellExecuteEx 直接从服务打开文件,但没有用。 (我在辅助 exe (GDNPOpener.exe) 中使用 ShellExecuteEx 打开文件)

如果有人知道如何修复我的 exe 以便我的服务 运行 它,我将不胜感激帮助!如果有人知道从服务打开文件的任何替代方法,也将不胜感激,谢谢!

您不能简单地在进程上下文中调用 Shellexecute() 运行作为服务。即使它成功了(从未真正测试过),启动的 app/document 仍然不会显示在登录用户的桌面上,因为这是两个不同的会话,彼此隔离(除非你在 Windows XP 或 Windows Server 2003;对于那些服务和控制台会话应用程序 运行 在会话 0 中,但服务仍需要标记为交互式才能与桌面交互)。

有关此概念的更多详细信息,请参阅 http://blogs.technet.com/b/askperf/archive/2007/04/27/application-compatibility-session-0-isolation.aspx 或搜索 "Session 0 Isolation"。

要解决这个问题,您主要有两个选择:(**如果我遗漏了一个,请随时纠正我!)

  1. 使用client/server路线;

    例如您可以编写一个小实用程序,它将 运行 在用户 space 中(例如,在会话启动时启动)并通过进程间通信(IPC;命名管道、映射内存)的某种选择与您的服务进行通信, ETC。)。然后,您的服务可以要求实用程序在登录用户的上下文中打开文件(例如,调用 ShellExecute() 的实用程序)。

    要知道从哪里开始,请查找为 VB6 编写的进程间通信示例。

    重要:由于服务器(您的服务)和客户端(user-space实用程序)将跨会话并在不同的用户令牌下进行通信,请注意在创建和访问通信通道(命名管道等)时注意使用的安全描述符和所需的访问权限,以免您的客户端出现 "access denied" 错误。

    警告:有关权限升级风险的常见安全警告适用于此处。由于您基本上 "open a door" 进入您的服务,因此请格外小心,不要让任何冒充您的 client/utility 的恶意应用程序控制您的服务(缓冲 运行 等)或使其成为做不该做的事

  2. 使用专用 API 跨不同会话启动进程;

    取决于 (1) 您的服务 运行 是在 LocalSystem 帐户下还是在另一个管理帐户下; (2) 是否要在登录用户的上下文中启动 document/app 或者您不关心创建新会话,以及 (3) 您是否拥有用户的凭据(用户+密码)不管是否存在,一些 API 允许服务与桌面通信或在另一个用户的上下文中启动应用程序。

    查看 API CreateProcessAsUser(), CreateProcessWithLogonW(), WTSQueryUserToken() and related ones, or search for examples using them. Also, the following article could be a good read: Launching an interactive process from Windows Service in Windows Vista and later

希望这能回答您的问题!至少,它应该为您指明下一步该做什么。