【问题标题】:VB6 - How do I open a file from an app running as a serviceVB6 - 如何从作为服务运行的应用程序打开文件
【发布时间】:2015-01-10 21:29:16
【问题描述】:

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

这些解决方案在应用程序正常运行时都可以工作,但在作为服务运行时则不行。应用程序能够在作为服务运行时直接或间接打开文件非常重要,它只需要能够触发它即可。

我了解自 Windows Vista 以来 Windows 已锁定服务与桌面交互的能力,但我确信必须有一种方法可以从服务触发文件打开命令。我开发的应用程序能够使用 CreateProcess 从命令行运行 pg_dump.exe(postgres 数据库的备份可执行文件)来备份数据库文件,同时作为服务运行。这就是为什么我虽然从服务中启动一个 exe 来间接打开文件可能会起作用。但是,由于某种原因,该应用程序可以正常运行 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

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

i = ExecSynchronousCmd(Chr$(34) & "C:\Program Files (x86)\PostgreSQL\9.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运行成功,而我自己的GDNPOpener.exe却没有?

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

如果有人知道如何修复我的 exe 以便我的服务能够运行它,我将非常感谢您的帮助!如果有人知道从服务中打开文件的任何其他方法,那也将不胜感激,谢谢!

【问题讨论】:

  • 如果我理解正确,GNDPOpener.exe 是您自己的代码,服务也是如此。为什么它们是单独的应用程序?为什么不直接把GNDPOpener.exe的代码放到服务里面呢?
  • 因为服务不想直接打开文件。所以我想如果我让服务启动另一个应用程序,也许该应用程序将能够打开文件,因为它不是作为服务运行并且不会受到相同的限制。但是,该服务不仅无法打开文件,而且也无法启动辅助应用程序(这很奇怪,因为正如我在原始问题中所说,它似乎能够很好地启动 pg_dump.exe)
  • 定义“打开文件”,因为我认为你的意思与真正的意思完全不同。我的猜测是您正在尝试运行一些其他程序,该程序执行诸如读取文件并将其显示在另一个会话(可能是会话 1)中的某个用户桌面上的操作,而您不能这样做。否则,如果您真的是 open,那么您就有文件安全问题了。
  • 当一个服务不能做普通应用程序可以做的事情时,这是因为它是在服务上下文中运行的,而不是因为服务可执行文件或服务进程本身有什么特别之处。启动另一个应用程序将无济于事,因为该应用程序会在服务上下文中运行。所以你的GNDPOpener.exe 没有意义,它不能做任何服务不能做的事情。 (不过我不知道为什么它不会运行。我从未尝试在服务上下文中运行 VB6 应用程序。)
  • 至于您的根本问题,您可能需要与相关软件的供应商(Hotdocs?)进行讨论。一般来说,一个软件越复杂,它在服务环境中成功运行的可能性就越小,除非它是专门设计的。

标签: vb6 windows-services shellexecute createprocess shellexecuteex


【解决方案1】:

您不能在作为服务运行的进程的上下文中简单地调用 Shellexecute()。即使成功(从未真正测试过),启动的应用程序/文档仍然不会显示在登录用户的桌面上,因为这是两个不同的会话,彼此隔离(除非您使用的是 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. 使用客户端/服务器路由;

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

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

    重要提示:由于服务器(您的服务)和客户端(用户空间实用程序)将在不同的用户令牌下跨会话进行通信,因此请注意有关使用的安全描述符和访问权限的说明创建和访问通信通道(命名管道等)时需要,以免您的客户端出现“拒绝访问”错误。

    警告:关于权限提升风险的常见安全警告适用于此处。由于您基本上为您的服务“打开了一扇门”,因此请特别注意不要让任何冒充您的客户端/实用程序的流氓应用程序控制您的服务(缓冲区溢出等)或让它做一些不应该做的事情.

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

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

    查看 API CreateProcessAsUser()CreateProcessWithLogonW()WTSQueryUserToken() 和相关的 API,或搜索使用它们的示例。此外,下面的文章可能是一个很好的阅读:Launching an interactive process from Windows Service in Windows Vista and later

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

【讨论】:

  • 问题中没有任何迹象表明 OP 希望应用程序在已登录用户的桌面上可见,甚至在启动应用程序时必然会有任何人登录。
  • 我不同意,我认为这就是他想要做的。至少,他试图让操作系统运行与服务中的文件类型相关联的程序。只要他部署第二个可执行文件,在登录用户的上下文中自动运行以捕获通知并执行命令,简单的 IPC 就可以很好地解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-05
  • 2010-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多