How to make SendKeys act Synchronously in IBM Host Access Library - vb.net

I use the IBM Host Access Class Library for COM Automation as a way to communicate with an IBM AS400 (aka iSeries, IBM i, green screen, 5250) through a terminal emulator. I notice that when you issue a "SendKeys" instruction, control returns to your application before the IBM emulator finishes with the command. This can lead to timing problems because you might then send another "SendKeys" instruction before the system is ready to accept it.
For example:
Imports AutPSTypeLibrary
Imports AutConnListTypeLibrary
Imports AutSessTypeLibrary
Sub Example
Dim connections As New AutConnList
connections.Refresh()
If connections.Count < 1 Then Throw New InvalidOperationException("No AS400 screen can currently be found.")
Dim connection As IAutConnInfo = DirectCast(connections(1), IAutConnInfo)
_Session = New AutSess2
_Session.SetConnectionByHandle(connection.Handle)
Dim _Presentation As AutPS = DirectCast(_Session.autECLPS, AutPS)
_Presentation.SendKeys("PM70[enter]", 22, 8)
_Presentation.SendKeys("ND71221AD[enter]", 22, 20)
End Sub
would work correctly when stepping through code in a debugger, but would fail when running normally because the second instruction was sent too soon.
One way to work with this is to put a timer or loop after each command to slow the calling program down. I consider this less than ideal because the length of time is not always predictable, you will often be waiting longer than necessary to accommodate an occasional hiccup. This slows down the run time of the entire process.
Another way to work around this is to wait until there is a testable condition on the screen as a result of your sent command. This will work sometimes, but some commands do not cause a screen change to test and if you are looking to abstract your command calling into a class or subroutine, you would have to pass in what screen condition to be watching for.
What I would like to find is one of the "Wait" methods that will work in the general case. Options like the autECLScreenDesc class seem like they have to be tailored to very specific conditions.
The autECLPS (aka AutPS) class has a number of Wait methods (Wait, WaitForCursor, WaitWhileCursor, WaitForString, WaitWhileString, WaitForStringInRect, WaitWhileStringInRect, WaitForAttrib, WaitWhileAttrib, WaitForScreen, WaitWhileScreen) but they also seem to be waiting for specific conditions and do not work for the general case. The general case it important to me because I am actually trying to write a general purpose field update subroutine that can be called from many places inside and outside of my .dll.
This example is written in VB.NET, but I would expect the same behavior from C#, C++, VB6, Java; really anything that uses IBM's Personal Communications for Windows, Version 6.0
Host Access Class Library.

The "Operator Information Area" class seems to provide a solution for this problem.
My general case seems to be working correctly with this implementation:
Friend Sub PutTextWithEnter(ByVal field As FieldDefinition, ByVal value As String)
If IsNothing(field) Then Throw New ArgumentNullException("field")
If IsNothing(value) Then Throw New ArgumentNullException("value")
_Presentation.SendKeys(Mid(value.Trim, 1, field.Length).PadRight(field.Length) & "[enter]", field.Row, field.Column)
WaitForEmulator(_Session.Handle)
End Sub
Private Sub WaitForEmulator(ByVal EmulatorHandle As Integer)
Dim Oia As New AutOIATypeLibrary.AutOIA
Oia.SetConnectionByHandle(EmulatorHandle)
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
End Sub
I give thanks to a user named "khieyzer" on this message board for pointing our this clean and general-purpose solution.
Edit:
After a few weeks debugging and working through timing and resource release issues, this method now reads like:
Private Sub WaitForEmulator(ByRef NeededReset As Boolean)
Dim Oia As New AutOIA
Oia.SetConnectionByHandle(_Presentation.Handle)
Dim inhibit As InhibitReason = Oia.InputInhibited
If inhibit = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
End If
If Not Oia.WaitForInputReady(6000) Then
If Oia.InputInhibited = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
Else
Marshal.ReleaseComObject(Oia)
Throw New InvalidOperationException("The system has stopped responding.")
End If
End If
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
Marshal.ReleaseComObject(Oia)
End Sub

Related

Objects are Nothing when referenced by remote client

I have made several VB apps that communicate using Windows Remoting, but this is the first time I ran into this problem.
There are two programs in my project, a client and a server. The server program has a queue. The client program adds items to the queue by calling a server method. But when the server program checks the queue, it is empty.
Furthermore, the server program instantiates several classes, but when the client tries to use them, it finds that they are Nothing. So this is a general problem not just an issue with the queue per se.
I have had experience with threading problems in the past, so I assumed that this was some kind of threading problem. I tried using a delegate function, but that did not help.
Here is a snippet of code to illustrate where the problem appeared. My apologies for not knowing how to make it properly formatted, this is my first attempt.
' ====================================================================
' this class is instantiated on the server at startup time
Public Class CPAutoDispatcher
' EXAMPLE #1
Public mWLQueue As New Collection
' This function is called from the remote client using Windows Remoting
Public Function SendWorkList(ByVal theList As String) As Boolean
Dim objWL As New AutoWorkList
If Not parseWorkList(theList, objWL) Then Exit Function
Call mWLQueue.Add(objWL)
SendWorkList = True
End Function
' This function is called from the server
Public Sub Tick()
If mWLQueue.Count = 0 Then Exit Sub ' <-- THIS ALWAYS EXITS!
Dim objWL As AutoWorkList = mWLQueue.Item(1)
Call mWLQueue.Remove(1)
' ... do something with objWL ...
End Sub
' EXAMPLE #2
Private mServerReports() As CPAutoServerReport
Private mDelGNR As DEL_GetNewReport = AddressOf getNewReportDelegate
' This function is called from the server
Public Function ProcessMessage(objSR As CPAutoServerReport) As Boolean
If mServerReports Is Nothing Then
ReDim mServerReports(0)
mServerReports(0) = objSR
Else
' ... do something else ...
End If
End Function
' This function is called from the remote client using Windows Remoting
Public Function GetNewReport() As CPAutoServerReport
GetNewReport = mDelGNR.Invoke
End Function
Private Function getNewReportDelegate() As CPAutoServerReport
If mServerReports Is Nothing Then Exit Function ' <-- THIS ALWAYS EXITS!
' ... do something with mServerReports ...
End Function
End Class
' ================================================================
Example #1: Similar code in other projects works, so I expected mWLQueue and mServerReports to be reachable by both the server and the client. But they are not. The client finds mWLQueue, but it is empty.
Example #2: If this was simply a threading issue, I would expect the delegate to make thing right. But it does not. The client finds that mServerReports Is Nothing, even after it has been set.
My code is behaving as if there are TWO instances of my CPAutoDispatcher class, one for the server thread, and another for the client thread (the remoting calls). But there is only one global variable, which is referenced by both threads.
I am baffled by this situation. Am I missing something that should be obvious?
The ultimate cause of my problems was the presence of duplicate declarations, which did not cause a compile error, and so went unnoticed. When I removed the extra declarations, all of the weird behavior went away.
I suspect that somehow, an instance of the alternate class was instantiated automatically, and being uninitialized, had an empty queue and references that were Nothing. But I don't understand how that came about.

VB.net Process.Start starts process but leaves variable as nothing

To start I would only rate myself as a novice programmer as I only know the concepts I've needed to learn to accomplish specific tasks at my work. That being said I'm using Process.Start to open the built-in Windows to camera program but when I do it leaves the process variable empty (nothing). Because of this I can't use Process.WaitForExit() as the code causes an error during execution.
Imports System.Diagnostics
Sub Main()
Dim camTimeout as integer = 5 * 60000
Dim camProcess as new System.Diagnostics.Process
camProcess = System.Diagnostics.Process.Start("microsoft.windows.camera:")
If Not camProcess.WaitforExit(camTimeout) then
MsgBox("timeout")
Else
MsgBox("picture")
End if
End Sub
During execution the camera app opens but I get a "NullReferenceException" at camProcess.WaitForExit. This is because camProcess is Nothing and I don't understand why that is. Can someone explain why camProcess isn't set properly when the camera app starts or point me to some novice-level reference material. Thanks!
Edit: This code is in a Windows Form Application.

Invoke method for multi thread application?

I have a bug in my application which is the same as here which this person was running into the same problem. My application is multi threaded where the worker thread is updating the Waveformgraph on the UI. I believe that is where my problem is and why, periodically, and on occassion I get a big red X in at least one of my waveformgraph objects when running the application. From reading and research, I need to use an Invoke or BeginInvoke method? Can someone please explain better and provide a sample code that is relevant to my code? The samples that I've found so far still have me hazy on how I need to do this or what I need to do. Thank you for your help.
This code is on the swScopeOnOff click event, main thread.
thread2 = New System.Threading.Thread(AddressOf dataAcquiring)
thread2.Start()
This code is in dataAcquiring Sub
Public Sub dataAcquiring()
'While Scope switch is on, stream each Ai channel's data continuously to its respective WaveForm graph
Do While swScopeOnOff.Value = True
data = reader.ReadWaveform(readRate)
i = 0
For Each WaveformGraph In WFGS
WaveformGraph.PlotWaveformAppend(data(i)) 'This line is updating the UI's waveform graphs
i += 1
Next
i = 0
Loop
End Sub
Proper, thread-safe invocation is actually not as hard as one might think (not even for thread-safe events, but that's irrelevant for this question).
I would recommend you to use the normal Invoke method, such as Me.Invoke() (where Me is the current form, if not, use Form1 or whatever it's called instead). Using BeginInvoke() may be asynchronous but it stacks memory usage and can cause memory leaks if EndInvoke() is not called correctly.
If you target .NET 4.0 or higher you can simply do like this:
Me.Invoke(Sub() WaveformGraph.PlotWaveformAppend(data(i)))
However if you target .NET 3.5 or lower it requires a few more lines of code.
'Outside your Sub.
Delegate Sub WaveformAppendDelegate(ByRef WaveformGraph, ByRef data)
'Create a new sub.
Public Sub AppendData(ByRef WaveformGraph, ByRef data)
WaveformGraph.PlotWaveformAppend(data)
End Sub
'Inside your sub, when you're going to invoke.
Me.Invoke(New WaveformAppendDelegate(AddressOf AppendData), WaveformGraph, data(i))

Multithreading Webbrowsers

I am currently making a vb program that i plan to make very big. I have a decent knowledge of visual basic but today i came across something i do not understand. Because of the huge size of my program , i decided to try and keep the program as organized as possible by putting specific subs in modules. These subs consist of httprequest , webbrowsers(control), webclients and alot of loops. In order to prevent these subs from lagging my main application i thread them using threading.thread and i start them from my main form. But this leads to two problems.
Problem 1: The threads cannot in any way interact with the main form.
Once the a httprequest or webclient collects the information from my desired website, i am trying to make it add the info to a listbox in my main form, So what i did is it typed
Msgbox("Info Sent")
form1.listbox1.items.add(String)
The first messagebox will show but although the code right under it runs, nothing is added to the first forms listbox.I am not using delegates to transfer the information, instead, although its not a good habit, i am using checkforillegalcrossovers.
Problem 2: Threading with a webbrowser.
Threading with a webbrowser using threading.thread also does not work because it causes an active x error. After looking it up i found that a solution was to use a single threaded apartment but this would not work because i may need multiple threads running off the same sub at once.
One solution that i have found to this problem is creating another form completely and setting it invisible, and since the form is its own thread i do not need to use threading.thread , but the problem comes when i am trying to create multiple threads, or else i can somehow dynamically create the threads and put the subs inside of it programically this method wont work And even if it does i feel that it is sloppy so i will leave this for one of two last resorts.
The other solution is the most simple one in which i just put all of the code in the main form, but if i keep on doing that form1 is gonna become huge and sloppy, doing this wont solve the webbrowser problem either and even when using regions i still feel that something that 1000+ lines deserves its own class.
There must be some solution out there that solves these problems. Any help would be appreciated, Thanks.
I checked my code for updating the progress bar, and using a single thread with synclock will NOT work. They way I make it work is perform the step of the pbar each time after a thread is started as I have limited total threads (say less than 5 threads). Thus, even the progress bar steps before the threads are finished, but it will not progress further before new threads started. It is not 100% accurate but it more or less telling the progress
'update the progress bar
some_form.PBar1.PerformStep()
' This while loop is to count the existing running thread,
' and determine whether new thread should start
While 1
Dim t2 = New System.Threading.Thread(Sub() WaitForPermission())
t2.Start()
t2.Join()
If proceed_gen Then
Exit While
End If
End While
'Start doing what I need to do
Dim t1 = SomeSub()
t1.Start()
'End of code, as VB doest not have thread.detach()
Correct me if I am wrong, but you probably have to use a background worker. I know this is annoying, but this is the limitation of VB.net.
Or, you can have something like this (pseudo code, not tested)
structure some_struct
'define the strings you want to update, and their status such that
'main() knows if you need to update the stuff to the form
' You can also put how many threads are running, and the status of each thread,
'such that the main knows if all threads are completed
end structure
sub your_sub()
'Manipulate the website, and update the data structure with
'proper stuff you need
end sub
sub main(){
dim t1 = New System.Threading.Thread(Sub() your_sub())
t1.start()
' I am listing only one threads here, but start as many as you want
'check if there are strings that you need to update to the form
while 1
'check if there are any stuff you want to update from the data structure.
' Make sure you use synclock on the data structure, so each thread won't fight each other on accessing the data struct
dim should_update as boolean = false
should_update = 'Something thatyou should implement to judge whether you should update the form.
'You can start a thread and join it so the thread won't fight with other threads for accessing the data structure
dim some_string as string
if should_update
some_string = 'You may also need a thread to join to get the stuff you need. Dim the string as an array if it is required.
'You can also try pass by ref
'if you need to use thread to access the data structure to know if you need to update the form
form1.listbox1.items.add(some_string )
end if
end while
end sub
This is an ugly solution, but it will help you do the job...

VB.NET multithreading, block thread until notification received

Before I begin, I have to apologize for two things. One is that it is very difficult for me to explain things in a concise manner. Two is that I need to be somewhat vague due to the nature of the company I work for.
I am working on enhancing the functionality of an application that I've inherited. It is a very intensive application that runs a good portion of my company's day to day business. Because of this I am limited to the scope of what I can change--otherwise I'd probably rewrite it from scratch. Anyways, here is what I need to do:
I have several threads that all perform the same task but on different data input streams. Each thread interacts through an API from another software system we pay licensing on to write out to what is called channels. Unfortunately we have only licensed a certain number of concurrently running channels, so this application is supposed to turn them on an off as needed.
Each thread should wait until there is an available channel, lock the channel for itself and perform its processing and then release the channel. Unfortunately, I don't know how to do this, especially across multiple threads. I also don't really know what to search Google or this site for, or I'd probably have my answer. This was my thought:
A class that handles the distribution of channel numbers. Each thread makes a call to a member of this class. When it does this it would enter a queue and block until the channel handling class recognizes that we have a channel, signals the waiting thread that a channel is available and passing it the channel id. I have no idea where to begin even looking this up. Below I have some horribly written PsuedoCode of how in my mind I would think it would work.
Public Class ChannelHandler
Private Shared WaitQueue as New Queue(of Thread)
'// calling thread adds itself to the queue
Public Shared Sub WaitForChannel(byref t as thread)
WaitQueue.enqueue(t)
End Sub
Public Shared Sub ReleaseChannel(chanNum as integer)
'// my own processing to make the chan num available again
End Sub
'// this would be running on a separate thread, polling my database
'// for an available channel, when it finds one, somehow signal
'// the first thread in the queue that its got a channel and here's the id
Public Shared Sub ChannelLoop()
while true
if WaitQueue.length > 0 then
if thereIsAChannelAvailable then '//i can figure this out my own
dim t as thread = ctype(WaitQueue.dequeue(), Thread)
lockTheChannel(TheAvailableChannelNumber) 'performed by me
'// signal the thread, passing it the channel number
t => SignalReady(theAvailableChannelNumber) '// how to signal?
end if
end if
end while
End Sub
End Class
and then
'// this inside the function that is doing the processing:
ChannelHandler.requestChannel(CurrentThread)
while (waitingForSignal) '// how?
block '// how?
dim channelNumber as int => getChannelNumberThatWasSignaledBack
'// perform processing with channelNumber
ChannelHandler.ReleaseChannel(channelNumber)
I am working with the .NET Framework 3.5 in VB.NET. I am sure there has got to be some sort of mechanism already built for this, but as I said I have no idea exactly what keywords I should be searching for. Any input pointing me in the right direction (ie specific .NET framework classes to use or code samples) would be greatly appreciated. If I need to elaborate on anything, please let me know and I will to the best of my ability.
Edit: The other problem that I have is that these channels can be turned on/off from outside of this application, manually by the user (or as a result of a user initiated event). I am not concerned with a channel be shut down while a thread is using it (it would throw an exception and then pick back up next time it came through. But the issue is that there are not a constant number of threads fighting over a constant number of channels (if a user turns one on manually, the count is reduced, etc). Both items are variable, so I cant rely on the fact that there are no external forces (ie, something outside this set of threads, which is why I do some processing via my DB to determine an available channel number)
What I would do:
Switch the System.Threading.Thread by the System.Threading.Tasks.Task class.
If a new Task needs to be created, but the List(Of Task) (or, in your example, Queue(Of Task) ) count greater than the maximum permitted, use the Task.WaitAny method.
EDIT:
As I answered the previous block on my phone (which is pretty challenging for writing code), let now me write an example about how I would do it:
Imports System.Threading.Tasks
Imports System.Collections.Generic
Public Class Sample
Private Const MAXIMUM_PERMITTED As Integer = 3
Private _waitQueue As New Queue(Of Task)
Public Sub AssignChannel()
Static Dim queueManagerCreated As Boolean
If Not queueManagerCreated Then
Task.Factory.StartNew(Sub() ManageQueue())
queueManagerCreated = True
End If
Dim newTask As New Task(Sub()
' Connect to 3rd Party software
End Sub)
SyncLock (_waitQueue)
_waitQueue.Enqueue(newTask)
End SyncLock
End Sub
Private Sub ManageQueue()
Dim tasksRunning As New List(Of Task)
While True
If _waitQueue.Count <= 0 Then
Threading.Thread.Sleep(10)
Continue While
End If
If tasksRunning.Count > MAXIMUM_PERMITTED Then
Dim endedTaskPos As Integer = Task.WaitAny(tasksRunning.ToArray)
If endedTaskPos > -1 AndAlso
endedTaskPos <= tasksRunning.Count Then
tasksRunning.RemoveAt(endedTaskPos)
Else
Continue While
End If
End If
Dim taskToStart As Task
SyncLock (_waitQueue)
taskToStart = _waitQueue.Dequeue()
End SyncLock
tasksRunning.Add(taskToStart)
taskToStart.Start()
End While
End Sub
End Class