You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Access子窗体中的SumatraPDF实例获取正确DDE通道?

问题描述

我开发了一个MS-Access 2010应用,使用Sumatra PDF作为PDF查看器,通过插件模式将其加载到子窗体中。当前实现运行正常,但希望通过DDE命令与PDF文档交互(Sumatra PDF 3.5及以上版本支持DDE)。单独运行Sumatra PDF时,DDE命令可以正常执行,但加载到子窗体后,常规的DDE初始化会失败——尽管Sumatra确实以进程形式运行。

此外场景存在复杂性:用户可能同时打开多个包含PDF查看器的子窗体实例,且应用支持多用户同时使用并打开PDF查看器。

我需要解决的核心问题是:

  • 能否确定子窗体中运行的SumatraPDF进程对应的正确DDE通道?
  • 能否通过子窗体的Hwnd调用API实现关联?
  • 若可行,提供示例代码。
现有实现代码

子窗体加载Sumatra PDF的代码

Private Sub Form_Load() 'subform load procedure
    Dim inpFile As String, cmd As String
    
    inpFile = "D:\temp\test.pdf"

    cmd = CurrentProject.Path & "\SumatraPDF-3.5.2-32.exe -plugin " & _
          Me.Hwnd & " """ & inpFile & """"
    runShell cmd
End Sub

Private Function runShell(ByVal cmd As String)
    Dim wsh As Object
    
    Set wsh = VBA.CreateObject("WScript.Shell")
    wsh.Run cmd, vbHide, False 

    Set wsh = Nothing
End Function

单独运行Sumatra时的DDE命令代码

Private Sub Command1_Click()
    Dim channelNumber As Long
    
    channelNumber = Application.DDEInitiate("SUMATRA", "control")

    Application.DDEExecute channelNumber, "[CmdCopySelection]"
    Application.DDETerminate channelNumber
End Sub
解决方案

方案1:启动Sumatra时指定唯一DDE服务名(推荐)

Sumatra PDF支持通过-dde-name参数指定自定义DDE服务名,这样每个子窗体对应的Sumatra实例可以拥有唯一的服务标识,直接通过该标识建立DDE通道,无需依赖API,实现简单可靠。

修改后的子窗体加载代码

Private Sub Form_Load() 'subform load procedure
    Dim inpFile As String, cmd As String
    Dim uniqueDdeName As String
    
    inpFile = "D:\temp\test.pdf"
    ' 用子窗体Hwnd+当前进程ID生成唯一DDE服务名,避免多用户/多实例冲突
    uniqueDdeName = "Sumatra_" & Me.Hwnd & "_" & GetCurrentProcessId()
    ' 将唯一服务名存入子窗体Tag属性,后续调用时使用
    Me.Tag = uniqueDdeName

    cmd = CurrentProject.Path & "\SumatraPDF-3.5.2-32.exe -plugin " & _
          Me.Hwnd & " -dde-name """ & uniqueDdeName & """ " & _
          """" & inpFile & """"
    runShell cmd
End Sub

Private Function runShell(ByVal cmd As String)
    Dim wsh As Object
    
    Set wsh = VBA.CreateObject("WScript.Shell")
    wsh.Run cmd, vbHide, False 

    Set wsh = Nothing
End Function

' 需要声明获取当前进程ID的API
Private Declare PtrSafe Function GetCurrentProcessId Lib "kernel32" () As Long

针对子窗体发送DDE命令的代码

' 假设此代码在主窗体或子窗体自身的按钮点击事件中
Private Sub CmdCopySelection_Click()
    Dim channelNumber As Long
    Dim uniqueDdeName As String
    
    ' 从子窗体Tag中取出唯一DDE服务名(如果是子窗体自身的事件,直接用Me.Tag)
    uniqueDdeName = Me.Tag ' 若在主窗体,可改为 Forms("子窗体容器").Form.Tag
    
    On Error Resume Next
    channelNumber = Application.DDEInitiate(uniqueDdeName, "control")
    If Err.Number = 0 Then
        Application.DDEExecute channelNumber, "[CmdCopySelection]"
        Application.DDETerminate channelNumber
    Else
        MsgBox "无法建立DDE连接:" & Err.Description
    End If
    On Error GoTo 0
End Sub

方案2:通过API关联子窗体Hwnd与Sumatra的DDE通道

如果无法修改启动命令,可通过Windows API枚举子窗体的子窗口,找到Sumatra的窗口句柄,再通过DDE API直接连接该窗口,而非依赖服务名。此方式相对复杂,需要处理更多API调用。

示例代码(需声明多个API)

' 声明所需API
Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" ( _
    ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, _
    ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr

Private Declare PtrSafe Function DdeInitialize Lib "user32" Alias "DdeInitializeA" ( _
    ByRef idInst As Long, ByVal pfnCallback As LongPtr, _
    ByVal afCmd As Long, ByVal ulRes As Long) As Long

Private Declare PtrSafe Function DdeConnect Lib "user32" ( _
    ByVal idInst As Long, ByVal hszService As LongPtr, _
    ByVal hszTopic As LongPtr, ByVal pCC As LongPtr) As LongPtr

Private Declare PtrSafe Function DdeCreateStringHandle Lib "user32" Alias "DdeCreateStringHandleA" ( _
    ByVal idInst As Long, ByVal psz As String, ByVal iCodePage As Long) As LongPtr

Private Declare PtrSafe Function DdeExecute Lib "user32" ( _
    ByVal hConv As LongPtr, ByVal hszCmd As LongPtr) As Long

Private Declare PtrSafe Function DdeFreeStringHandle Lib "user32" ( _
    ByVal idInst As Long, ByVal hsz As LongPtr) As Long

Private Declare PtrSafe Function DdeDisconnect Lib "user32" ( _
    ByVal hConv As LongPtr) As Long

Private Declare PtrSafe Function DdeUninitialize Lib "user32" ( _
    ByVal idInst As Long) As Long

Private Const CP_WINANSI = 1
Private Const DDE_FIRST = &H8000
Private Const DDEACK_FIRST = (DDE_FIRST + 100)
Private Const DDEACK_OK = (DDEACK_FIRST + 0)

Private Sub CmdCopySelection_ByHwnd()
    Dim sumatraHwnd As LongPtr
    Dim ddeInst As Long
    Dim hszService As LongPtr, hszTopic As LongPtr, hszCmd As LongPtr
    Dim hConv As LongPtr
    Dim result As Long
    
    ' 找到子窗体中的Sumatra窗口(Sumatra的窗口类名为"SumatraPDF")
    sumatraHwnd = FindWindowEx(Me.Hwnd, 0, "SumatraPDF", vbNullString)
    If sumatraHwnd = 0 Then
        MsgBox "未找到嵌入的Sumatra窗口"
        Exit Sub
    End If
    
    ' 初始化DDE实例
    If DdeInitialize(ddeInst, 0, 0, 0) <> 0 Then
        MsgBox "DDE初始化失败"
        Exit Sub
    End If
    
    ' 创建服务名、主题名、命令的字符串句柄
    hszService = DdeCreateStringHandle(ddeInst, "SUMATRA", CP_WINANSI)
    hszTopic = DdeCreateStringHandle(ddeInst, "control", CP_WINANSI)
    hszCmd = DdeCreateStringHandle(ddeInst, "[CmdCopySelection]", CP_WINANSI)
    
    ' 连接到指定窗口的DDE会话
    hConv = DdeConnect(ddeInst, hszService, hszTopic, 0)
    If hConv <> 0 Then
        ' 执行DDE命令
        result = DdeExecute(hConv, hszCmd)
        If result = DDEACK_OK Then
            MsgBox "命令执行成功"
        Else
            MsgBox "命令执行失败"
        End If
        ' 断开连接
        DdeDisconnect hConv
    Else
        MsgBox "无法连接到DDE会话"
    End If
    
    ' 释放资源
    DdeFreeStringHandle ddeInst, hszCmd
    DdeFreeStringHandle ddeInst, hszTopic
    DdeFreeStringHandle ddeInst, hszService
    DdeUninitialize ddeInst
End Sub

注意事项

  • 方案1更简洁可靠,推荐优先使用,因为直接通过唯一服务名区分实例,避免枚举窗口的不确定性。
  • 多用户场景下,由于每个用户的Access进程独立,GetCurrentProcessId()可以确保服务名不会跨进程冲突。
  • 若使用64位Access,需确保所有API声明使用PtrSafe,并对应LongPtr类型。

内容的提问来源于stack exchange,提问作者emphyrio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:24:52