Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / Interop / September 2004

Tip: Looking for answers? Try searching our database.

Windows Service to Populate Text Boxes in Pre-Existing Adobe PDF

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Geoff - 27 Sep 2004 18:41 GMT
I succeeded at writing a Windows Forms app to write values into text fields
on a PDF, save the PDF and email or fax the PDF to customers, and then the
server team asked me to turn the program into a Windows Service.

I'm having a couple of problems related to removing windows.forms from the
project.  The problem I can't resolve or work around is that the Adobe
Acrobat API may throw message boxes during processing.  I handled this in my
windows.form app by enumerating all open windows looking for a particular
window title and then sending an enter key to the window using SendKeys from
system.windows.forms.

So, in trying to convert the program, I realize that I will not be able to
use this method (can't compile).  I'm guessing that the API trying to throw a
window may cause the service to hang anyway, although I don't know.  I'll
test that next.

Other problems I have had caused by removing the windows.forms is that I
cannot use invoke to return to my original thread.  I also can't bind a
FileSystemWatcher to the main thread of the app.  These problems I can get
around.

I hope that there is a way to succeed at making this program a service.  
Does anyone have any ideas?

Thanks,

Geoff
gswitz - 28 Sep 2004 14:43 GMT
I've gotten the app to run as a service.  In the OnStart I create a loop and
use thread.sleep to get the app to run on just one thread (the adobe api
can't handle multiple threads).  This works, but causes the service to show
as starting (not started) in the service viewer area of CompManagement in the
MMC.  I'm not worried about this.

Code for OnStart...
       Dim i As Integer = 5000 'Length of time to sleep the thread while
waiting for the next thing to come along...
       Dim f As FileInfo
       Dim d As New DirectoryInfo("c:\Pdf Populator Service\InBox")

       Do Until blnStop = True
           For Each f In d.GetFiles()
               If f.Extension = ".xml" Then
                   Timer_KillAdobeWindow.Start()
                   MakeCallToProcessFiles()
                   FinishProcessing(True)
                   Timer_KillAdobeWindow.Stop()
               End If
           Next
           Thread.Sleep(i)
       Loop

As long as all the parameters for the text boxes in the PDF meet the PDF
requirements, the program will populate the PDF, save it and email it out (or
fax it).  The problem comes if bad data is passed to the program and Adobe
will not accept it (example: a date value with 2 digit year).  When this
occurs when the app is running as a windows form a MessageBox is thrown from
Adobe.  I use my WindowsCloser Class on a timer to enumerate open windows
looking for such a window and send an enter key to the window using
System.Windows.Forms.SendKeys.  On the Windows Forms version this works, but
with the Windows Service version of the app, things keep hanging up.  I try
to attach to the process to debug, but don't seem to get anything (perhaps
because the program is waiting on a return from Adobe?).  Sometimes, using
taskmanager to endprocess on the Adobe app will suddenly throw an exception
in the .Net Service App.

For the timer, I'm using
Friend WithEvents Timer_KillAdobeWindow As System.Timers.Timer

My Windows Closer Class called from the elapsed event on the async timer is
...
Option Explicit On
Option Strict Off

Imports System
Imports System.IO
Imports System.Text
Imports System.Runtime.InteropServices
Imports System.Threading

Public Delegate Function CallBack(ByVal handle As Integer, ByVal param As
IntPtr) As Boolean

Public Class LibWrap
   ' Passes a managed object instead of an LPARAM.
   ' Declares a managed prototype for the unmanaged function.
   Declare Function EnumWindows Lib "user32.dll" ( _
      ByVal cb As CallBack, ByVal param As IntPtr) As Boolean
End Class 'LibWrap

Public Class WindowCloser
   Shared cstrPartialTitle As String

   Public Shared Sub GetWindowFromTitle()
       Dim tw As TextWriter = System.Console.Out
       Dim gch As GCHandle = GCHandle.Alloc(tw)
       ' Platform invoke prevents the delegate from being garbage collected
       ' before the call ends.
       Dim cewp As CallBack
       cewp = AddressOf WindowCloser.CaptureEnumWindowsProc
       LibWrap.EnumWindows(cewp, GCHandle.op_Explicit(gch))
       gch.Free()
   End Sub 'GetWindowFromTitle
   Public Shared Function CaptureEnumWindowsProc(ByVal handle _
         As Integer, ByVal param As IntPtr) As Boolean
       Try
           Dim gch As GCHandle = GCHandle.op_Explicit(param)
           'Dim tw As TextWriter = CType(gch.Target, TextWriter)
           Dim w As New Window
           Try
               w.h = handle
               If w.GetText = PartialTitle Then
                   AppActivate(PartialTitle)
                   System.Windows.Forms.SendKeys.Send("{Enter}")
               End If
               Return True
           Catch ex As Exception
               'do nothing
           Finally
               gch = Nothing
               w = Nothing
           End Try
       Catch ex As Exception
           'do nothing
       End Try
   End Function 'CaptureEnumWindowsProc
   Public Shared Property PartialTitle() As String
       Get
           PartialTitle = cstrPartialTitle
       End Get
       Set(ByVal Value As String)
           cstrPartialTitle = Value
       End Set
   End Property
End Class
Public Class Win32API
   Public Declare Auto Sub GetWindowText Lib "User32.Dll" _
   (ByVal h As Integer, ByVal s As StringBuilder, ByVal nMaxCount As Integer)
End Class
Public Class Window
   Friend h As Integer ' Friend handle to Window.
   Public Function GetText() As String
       Dim sb As New StringBuilder(256)
       Win32API.GetWindowText(h, sb, sb.Capacity + 1)
       Return sb.ToString()
   End Function
End Class
gswitz - 28 Sep 2004 15:05 GMT
In my WindowCloser Class, in CaptureEnumWindowsProc, the line...
AppActivate(PartialTitle)
is throwing an exception when it finds the window with "Adobe Acrobat" in
the title and tries to activate that window.

Any ideas as to how I can send an enter key to this Window?  I have the
Window Handle.

I know I'm soo close to making this work!  Major thanks to anyone who can
nudge me over the top!

Thanks,

Geoff
gswitz - 28 Sep 2004 21:27 GMT
I've tried using unmanaged code...
   Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As
Long) As Long
   ' Declare the Windows API function CopyMemory
   ' Note that all variables are ByVal. pDst is passed ByVal because we want
   ' CopyMemory to go to that location and modify the data that is pointed to
   ' by the IntPtr, and not the pointer itself.
   Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDst
As IntPtr, _
                                                                ByVal pSrc
As String, _
                                                                ByVal
ByteLen As Long)

I can get the SetForegroundWindow call to work (or not throw an error), but
I cannot get the CopyMemory to work.  My original source code that I find to
try to send keys to the window was in VB 6 and used the ANY type which is not
supported in .Net.  My team leader tells me I should bag it, write the window
closer in VB 6 and shell out to it from .Net.  Is this really the best way to
get this thing to work?  I'm really disappointed.
Mattias Sj?gren - 29 Sep 2004 00:57 GMT
>I can get the SetForegroundWindow call to work (or not throw an error)

But it isn't correct. The parameter should be an IntPtr and the return
type Boolean.

>but I cannot get the CopyMemory to work.

The last parameter here should be an Integer, not Long.

Mattias

Signature

Mattias Sjögren [MVP]  mattias @ mvps.org
http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com
Please reply only to the newsgroup.

gswitz - 29 Sep 2004 13:29 GMT
These tips really haven't helped me get where I need to go, even if you are
correct.  I've found lots of conflicting examples for the parameter values
for the api calls.

Regardless, I only need to manage to send keys to the application...

In the example below, the user is able to copymemory and sendinput to
another window.  This is what I need to do.  I need to send an enter key down
and up to another application.  The fact that I cannot use types in .net
(Structures cannot be passed in the same manner the types could be passed to
the api in vb6) and that there are no examples of how to do this nor can
windows.forms.sendkeys be used in a windows service means I'm stuck.

Mattias, if you can help me, I would be greatly appreciative.  I am now
beginning to build the vb app to do the job and shell out to the vb app from
.net b/c I can't figure out how to make this happen using .net after 3 days
of effort.

Is there anyone out there who can help me?

Thanks,

Geoff

Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA"
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal
lpsz2 As String) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal
lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function SetFocus1 Lib "user32" Alias "SetFocus" (ByVal
hwnd As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA"
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam
As Long) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA"
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam
As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32.dll" _
   Alias "RtlMoveMemory" (Destination As Any, Source As Any, _
   ByVal Length As Long)
Private Type KEYBDINPUT
     wVk As Integer
     wScan As Integer
     dwFlags As Long
     time As Long
     dwExtraInfo As Long
End Type

Private Type INPUT_TYPE
     dwType As Long
     xi(0 To 23) As Byte
End Type

Private Const KEYEVENTF_KEYUP = &H2
Private Const KEYEVENTF_EXTENDEDKEY = &H1
Private Const VK_SHIFT = &H10
Private Const VK_HOME = &H24
Private Const VK_END = &H23
Private Const INPUT_KEYBOARD = 1

Private Declare Function MapVirtualKey Lib "user32" _
   Alias "MapVirtualKeyA" (ByVal wCode As Long, _
   ByVal wMapType As Long) As Long

Private Declare Function SendInput Lib "user32.dll" _
   (ByVal nInputs As Long, pInputs As INPUT_TYPE, _
    ByVal cbsize As Long) As Long

Const WM_KEYDOWN = &H100
Const WM_KEYUP = &H101
Const VK_RIGHT = &H27
Const VK_RETURN = &HD
Const VK_ESCAPE = &H1B
Private Sub Command1_Click()
   Dim wb As Excel.Workbook
   Dim ws As Excel.Worksheet
   Dim w As Excel.Window
   App.OleServerBusyRaiseError = True
   App.OleServerBusyTimeout = 1000
   App.OleServerBusyMsgText = ""
   Set wb = WebBrowser1.Document
   On Error GoTo ErrorHandler
   Debug.Print wb.Application.ActiveWindow.Caption
   Exit Sub
ErrorHandler:
Debug.Print Err.Description
Call Command3_Click
wb.Application.Cells(1, 1) = "c"
End Sub

Private Sub Command3_Click()
Dim hwnd As Long
hwnd = FindWindow("ThunderFormDC", vbNullString)
hwnd = FindWindowEx(hwnd, 0, "Shell Embedding", vbNullString)
SetFocus1 hwnd
   Dim wVkKey(1) As Integer
   Dim UpDown(1) As Integer
   wVkKey(0) = VK_ESCAPE:    UpDown(0) = 0
   wVkKey(1) = VK_ESCAPE:    UpDown(1) = 1
   sKeyEventSet 2, wVkKey, UpDown
End Sub

Private Sub Form_Load()
   WebBrowser1.Navigate2 "file:///C:/test.xls"
End Sub
Private Sub sKeyEventSet(nInput As Long, _
                        wVkKey() As Integer, UpDown() As Integer)
   Dim inputevents() As INPUT_TYPE
   Dim keyevent As KEYBDINPUT
   Dim Count As Integer
   ReDim inputevents(nInput - 1) As INPUT_TYPE
   For Count = 0 To nInput - 1
       With keyevent
           .wVk = wVkKey(Count)
           .wScan = MapVirtualKey(wVkKey(Count), 0)
           If UpDown(Count) = 0 Then
               .dwFlags = KEYEVENTF_EXTENDEDKEY Or 0
           Else
               .dwFlags = KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP
           End If
           .time = 0
           .dwExtraInfo = 0
       End With
       inputevents(Count).dwType = INPUT_KEYBOARD
       CopyMemory inputevents(Count).xi(0), keyevent, Len(keyevent)
   Next Count
   Dim rt As Long
   rt = SendInput(nInput, inputevents(0), Len(inputevents(0)))
   Debug.Print rt
   Debug.Print Err.LastDllError
End Sub
gswitz - 29 Sep 2004 18:39 GMT
I have written the VB App to send the enter key and shelled out to it from
the .net app running as a service, and this doesn't succeed at getting the
Adobe Message box to go away.

I think it may be time to cut my losses.

Thanks,

Geoff
gswitz - 30 Sep 2004 16:01 GMT
I opened the MMC and set the Allow service to Interact with Desktop to true
on the Log On tab of the properties for the service.

After this, I was able to use...
                   AppActivate(PartialTitle)
                   System.Windows.Forms.SendKeys.SendWait("{Enter}")
to bring the window to the front and click on it using code.  The problem
now is that this only works when a user is currently logged onto the computer.

I need this to work when there is no logged on user so the auditors will be
happy.  The auditors require that we not leave our servers logged into.

Any ideas, any one?
gswitz - 30 Sep 2004 21:49 GMT
I can set the service to allow it to interact with the desktop, at which
point the service can bring the Adobe Messagebox to the foreground of any
currently logged in user and click the ok button.  This still requires that
some user be logged into the computer.

I am now testing...
           myprocess = Process.GetProcessesByName("Acrobat")(0)
           myprocess.Kill()
as a method for killing the process.  This doesn't really do what I want,
but it will keep the service from hanging.  It will prevent me from sending
out a populated PDF with the email or fax, but I can still send an empty pdf
with the email or fax.

I am now wondering whether it is possible to log a user in and out of the
server to enable the service to click the message box using code.  I don't
know, but I'm curious.

Thanks,

Geoff

Rate this thread:







Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.