Link to home
Start Free TrialLog in
Avatar of dve01
dve01

asked on

In VB6, how can I call a procedure from Timer1 and not have Timer1 wait for the procedure to finish.

Application:  Multi-Station Conveyor that performs a different task per station.

This is my problem and how I bandaided the problem in order to run production.  There is a better way to do what I want to do, but I don't know how.  I don't have much experience with threading or parallel processing in VB.

PseudoCode simplified:  The Problem reside where I check photoeye 3

Main Conveyor Timer1()    ' 200msec

          If Photoeye1 = true  output solenoid A

          If Photoeye2 = true  output solenoid B

          If Photoeye3 = true   Enable Timer2 which Calls Elect_Test_Part()  

' PROBLEM:  The Elect_Test_Part procedure takes about 12seconds to complete and Timer1 will not continuing scanning until the Elect_Test_Part procedure is finished.  By using Timer2, instead of putting a direct call to Elect_Test_Part in Timer1,  I thought would free up Timer1 to continue running.  This was not the case.  So, my bandaid was that I periodically check status of Photoeye1,2,4 in the Elect_Test_Part procedure.  I kind of created my own interrupt, which isn't very efficient.  I realize I can Shell an ".exe", because I've done this before as a bandaid and it was pretty efficient.  This is not as easy in this application though, because I lose AND/OR masking ability between the 2 ".exe's" with the I/O handler I'm using.

          If Photoeye4 = true output solenoid C

          If Photoeye5 = true output solenoid D
     
End

     
Avatar of jmundsack
jmundsack
Flag of United States of America image

VBAccelerator has an excellent example of implementing multi-threading in VB6:

http://www.vbaccelerator.com/home/VB/Code/Libraries/Threading/Multi-threading_using_classes_in_ActiveX_EXEs/article.asp

HTH-Jon
Avatar of dve01
dve01

ASKER

I checked out the example.  It's very unclear to me on how to translate that example to my code.  
What you would need to do is:

1. Create a new VB ActiveX exe project (name it, say, ElectTestProj)
2. Set the thread pool to 1 thread
3. Add the file mStart.bas from the VBAccelerator demo to it
4. Create a class module (name it, say, cElectTest)
5. Copy the code from cAsync.cls from the VBAccelerator demo into it
6. Copy the code from Elect_Test_Part into the Runnable_Start sub
7. Compile it

In the main program, go into Project | References and add the ElectTestProj component.  If you don't care when the async procedure finishes (which is what it sounds like), you can ignore the stuff with the Complete and Cancelled events, and declaring the object WithEvents at the module level.  To just launch the process in its own thread your code would be roughly:

    Dim objElectTest As ElectTestProj.cElectTest
    ...
    If PhotoEye3 = True Then
        Set objElectTest = New ElectTestProj.cElectTest
        objElectTest.Start
    End If
    ...

Each time an ElectTestProj.cElectTest object is created it will run in its own thread.

HTH-Jon
Avatar of dve01

ASKER

Much clearer.  It's the end of my day, so give me a day to try it out.
Note, there is a .tlb file you need to download separately from the demo project on VBAccelerator.  That was the only gotcha I noticed.  Good luck.
The easiest way would be to set up an event using CreateEvent API and one of the I/O wait API's such as WaitForMultipleObjects
Avatar of dve01

ASKER

jmundsack,

I created an ActiveX exe and called it TestProject.  TestProject consisted of a small amount of code to append Date and Time to a file 100,000 times.  I used the objTest.Start in my Mainproject to run the TestProject ActiveX. While the ActiveX was doing it's thing, my MainProject was adding Date and Time to a list on my main form.  Everything was asynchronous.   It worked great.

Here is the problem though.  My ElectTestProject is very large and shares many global variables with my Mainproject.  I also show a lot of test data to the main form in my Mainproject, which I lose.   I could spend a lot of time making my Main project completely seperate from the ElecTestProject, which might be worthwhile, but I don't think so.

The problem with having seperate exe's with my application is that I use a I/O handler that my Main project and ElectTestProject both use.   Because my Main project uses I/O on the same computer relay card that the ElectTestProject uses, the I/O handler software must reside in an exe that contains all my code.  I've tried putting the I/O handler in 2 different exe's and shelling an exe from my Main project and it doesn't work out.  The only way to get around this is to have a dedicated I/O card to Main project completely seperate from I/O card to ElectTestProject.  This would reguire an upgraded computer since I'm out of slots, not to mention  a lot rewiring.  Not the best option.

The ActiveX exe seems much more effecient than shelling a seperate exe,  But in regards to not sharing variables it seems the same.

Let me know if I'm off base.  My unfamilarity with ActiveX exe's could easily make me spew ignorance.
Well, this is getting a little more sophisticated, but one possibility is to create a component that exposes a "singleton" object (an object for which there is one instance running on the machine accessible to multiple processes).  If you could encapsulate the global variables and I/O functions in this singleton object, and then your main project and the async project could both access it.  There is a good article explaining how to implement a singleton in VB6 at the following address:

http://users.skynet.be/wvdd2/General_techniques/Singletons/singletons.html

Of course this would represent a lot of rewriting.  Another possible solution (which I am unfamiliar with) is described in this thread I found on Google:

http://groups.google.com/group/microsoft.public.vb.winapi/browse_thread/thread/e26d4ddb79095460/d81ef9a66fc4d0cb?lnk=st&q=multithreading+api+vb6+group%3A*vb*&rnum=1&hl=en#d81ef9a66fc4d0cb

Sorry but any way you slice it, it seems you're staring down a lot of revision.
Avatar of dve01

ASKER

Since I already have all my global variables and I/O functions in a seperate module, it might not be too bad to revise.  I'll give it a try on a smaller scale, just to see if I understand it.  I don't know if I could somehow reference the main form from my main project in the singleton in order display data from the async project.
Avatar of dve01

ASKER

egl1044,
You mention an easy way using a Create Event API  and an I/O wait API.  If you would like to expound, I'm listening.  I'm all for easy.  
As far as exposing your main form to the singleton, I have this recommendation.

Since your main project will need a reference to the singleton project, create an interface class in the singleton project that defines the properties and methods that you'll want the singleton to access on the main form.  For example:

(class IParent)

Public Property Get Something() As String
End Property

Public Property Let Something(ByVal NewValue As String)
End Property

Public Sub DoSomething()
End Sub

(etc.)

Then in your main form put this in the declarations section:

Implements IParent
Private mSomething As String

Then you can drop down the object selector in the code view of the main form and select IParent, and define the implementation of this interface, e.g.:

Private Sub IParent_DoSomething()
    'implementation of the DoSomething method
End Sub

Private Property Let IParent_Something(ByVal RHS As String)
    mSomething = RHS
End Property

Private Property Get IParent_Something() As String
    IParent_Something = mSomething
End Property

In the singleton object, include a parameter of type IParent on the procedures that you want to access the main form, e.g.:

(in the singleton class)

Public Sub SomeAction(ByVal Parent AS IParent)
    'now you can access these properties and methods defined on the main form
    Parent.Something = "blah"
    Parent.DoSomething
End Sub

And in the main project when you want to execute the SomeAction method on the singleton:

    'lets assume you have a global variable named gMainForm to reference your main form
    'and a global variable named gSingleton to reference your singleton object
    gSingleton.SomeAction gMainForm

Good luck.

Jon
Avatar of dve01

ASKER

ok,
I've been trying to create a singleton object and need some clarification.  This is what I'm working with, just don't know how to piece everything together.

1. I have a MainProject
2. I have a  GlobVar.bas in Mainproject  (assume similar to myModule.bas in article)
3. Globvar.bas contains over 200 global variables.  I'm just try work with 1 global variable called 'public VoltVal as double'.
4. I have a TestProject which is an ActiveX exe.

I think I need MainProject and TestProject to be able to access global variables in GMU class.

Where does GMU class go?  In Mainproject? or TestProject?  or a 3rd Project?

In the article under 'My way', he refers to myClass in his code.  What would myClass be in my code.

Once I get this singleton object understood, I'll pursue the Mainform sharing.

thanks

ASKER CERTIFIED SOLUTION
Avatar of jmundsack
jmundsack
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of dve01

ASKER

Nice explanation.  I got everything setup, but not working like I thought.  For instance:

In my Main Project I set  SingletonProj.Singleton.VoltVal=5.434

When I activate my ActiveX EXE project I have a procedure that prints SingletonProj.Singleton.VoltVal to a file.  The value printed to the file is 0. I thought it would be 5.434.

If I set SingletonProj.Singleton.VoltVal = 1.2345 in my ActiveX EXE project. The value printed to the file is 1.2345.

It appears that I can set the VoltVal in each project, but if the variable changes in one of the projects it doesn't reflect in the other project.
Is the Threading Model "Single Threaded" on your SingletonProj?
Avatar of dve01

ASKER

The threading model was "Apartment Threaded".  I changed to "Single Threaded" on SingletonProj and get same results.
Fascinating.  I've used this technique before with a few variations, and apparently those variations are what made it work.  Because using the directions I gave you it certainly didn't.  But, I may have found a solution.  By simply changing the SingletonProj type from ActiveX DLL to ActiveX EXE, suddenly it works.  Why, I have no idea.
Actually, now it makes sense.  A DLL runs in-process with the project that references it.  An EXE runs in its own process.
Avatar of dve01

ASKER

Excellent!  It's a good place to start tommorrow.  Now that the global variable issue is resolved, I'll try the same techniques with the I/O handler and use your suggestions to access the MainForm from my ActiveX Exe.  My day is coming to a close, so I communicate tommorrow.

Thanks so much
Avatar of dve01

ASKER

I couldn' access the Mainform from the ActiveX exe, but I think a better way is to have the ActiveX project launch it's own form and not worry about the Mainform.  I've already done this, but I would like to know how do you know when the ActiveX exe is done running and how come the AcitveX exe shows it's still running in task manager.  Even when I unload the form in the ActiveX it still shows it's running in the task manager.
Does it stay there even if you close the main program?
Avatar of dve01

ASKER

Yes,  the Singular ActiveX exe is also still running.  So I have to delete the tasks in task manager if I want to recompile changes in the ActiveX projects .
Avatar of dve01

ASKER

I have to take off for a few hours.  I'll check back around 2 EST.
Avatar of dve01

ASKER

Ok,  
I think I found a solution for both problems.  I should have dug into this deeper, before submitting a question. I got a little lazy, I guess.

1. To know that "ElectTest" ActiveX program is done, I just created a ElectTestDone variable in the Singleton ActiveX.  I set the ElectTestDone variable to false in my MainProject just before I activate "ElectTest".  In "ElectTest", I set the variable to true when the test is complete.

2. As far as the ActiveX programs always running in the background, it appears that it doesn't matter when I call the ActiveX program  "ElectTest" multiple times.  So I don't have duplicate ActiveX programs running in the task manager, which is what I thought would happen.  All I did was use API functions "FindWindow(ElectTest)", "PostMessage(WM_close), and "WaitforSingleObject" to close the ActiveX programs when I unload the MainProject.  When "ElectTest" closes the "Singleton" activeX closes also.

Last Issue, before I start rewriting my project.

I'm gonna tackle using the I/O handler in the Singleton class.  Before I accept the "Singleton" answer, I would like to keep this question open in case I run into problems with the I/O handler stuff in the Singleton class.  

You've been a great help

thanks




Avatar of dve01

ASKER

I've hit another roadblack in the Singleton class.

The I/O handler uses Type Definitions and some constants.  The Singleton class module won't compile a Public Type Def or Constant.   When I make them private it compiles, but can't see variables as objects in MainProject or ElectTest project.  Am I screwed? or is there a way around this?

For constants you can use a public Enum.  For public types define a public class that has public properties matching the type members.  The only thing you lose is the ability to define fixed-length string properties, but you can enforce the length in the property let statement.
Avatar of dve01

ASKER

Thanks, I'll try it and get right back with you.
Note, the public enum will only work for numeric constants--if you have constants of other data types let me know--there are other things you can do for that.
Avatar of dve01

ASKER

ok,
My constants are numeric.  I think I need further explananation though.  I tried some things to no avail.  Here is what I'm working with:


SingletonClass

    Public VoltVal as double    ' shows up ok in other projects

    Type D_IO   ' stands for digital i/o        ' compiling error
        Board as integer
        Port as integer
        Bit as integer
        On as boolean
    end Type

    Public Const Estop = 1                         'compiling error
    Public Const Alarm= 2
    Public Const Running=3

   Not sure what you mean by defining a public class that has public properties matching the type numbers.

  Also not clear on how to use the Enum.  I've tried some things using help, but didn't work out.
In your Singleton class, define the enum:

(declarations section of the class)
Public Enum IOStateEnum
    Estop = 1
    Alarm = 2
    Running = 3
End Enum

This should compile and then you can access these in the main project as though they were constants.

For defining a class to replace the type, so something like:

(in the singleton class, add a new multi-use class named, say, clsIO)

Private mBoard As Integer
Private mPort As Integer
Private mBit As Integer
Private mOn As Boolean

Public Property Get Board() As Integer
    Board = mBoard
End Property
Public Property Let Board(ByVal NewValue As Integer)
    mBoard = NewValue
End Property

(etc...)

Then wherever you would declare a variable of type D_IO, declare a varaible of type clsIO instead:

Dim objIO As clsIO
Set objIO = New clsIO
With objIO
    .Board = 1
    'etc....
End With
How To Use Events to Generate Asynchronous Callbacks
http://support.microsoft.com/kb/q176951/
Avatar of dve01

ASKER

I got the clsIO to work.  thanks

Enum still can't access in main project.  It's about the end of my day.  I'll check back tommorrow.
Avatar of dve01

ASKER

I can access the Enum in both projects now.  I was using "Singleton.", instead of "SingletonProj."    I'm not sure why some objects show up when you just type just the Class, but others you have to type the project.

The last thing I'm trying to figure out is whether I can use a component (I/O Device) in the Singleton Project and access it in both the MainProject and ElectTest Project to turn Outputs On/OFF through either project.  Any advice?
Avatar of dve01

ASKER

I know I've asked many follow up questions to the original question.  I can accept the Singleton object answer and post new questions related to the Singleton object with new point totals.  I'm a new account member, so not sure of proper way to handle followup questions to original question.
About the question, "The last thing I'm trying to figure out is whether I can use a component (I/O Device) in the Singleton Project and access it in both the MainProject and ElectTest Project to turn Outputs On/OFF through either project.  Any advice?"  That's sort of the whole point of the Singleton, isn't it?  To have two distinct processes referencing a single object in memory?  Unless I'm missing something....?  (And don't worry about the follow-ups, I just hope the advice I've given you actually ends up working.)
Avatar of dve01

ASKER

Since the component is dropped on a Form in the Singletonproj, I wasn't sure if the component object on the Form could be accessed just the same as global variables in a Class from the Singletonproj.  Didn't know if there would be differences between a Form and a Class in this regard.  I'm new to VB classes, that's why I second guess.
Avatar of dve01

ASKER

For instance, I created a SingletonFrm and dropped 2 Output_BD components on SingletonFrm.   In the SingletonProj I can do everything with the component, such as,   SingletonFrm.Output_BD(0).DeviceName
       SingletonFrm.Output_BD(0).DeviceNum
       SingletonFrm.Output_BD(0).OpenDevice
       SingletonFrm.Output_BD(0).CloseDevice

How do I do this from MainProject and ElectTestProject? I thought possible  SingletonProj.SingletonFrm.Output_BD(0).OpenDevice

This isn't correct.  Maybe have to transfer to another class in SingletonProj and reference this class from MainProject?

Avatar of dve01

ASKER

I tried another thing to no avail.  In trying to access the component from other projects,  I tried to mimic the clsIO for the DAQ component.

I defined a class to replace the DAQDevice and DAQDO (component)

(in the singleton class, I add a new multi-use class named, say, DAQIO)

Private IO_CARD(0 to 1) As DAQDevice
Private Ouput_BD(0 to 1) As DAQDO

Then tried to use the Public Property functions.

I couldn't get anything to compile.  I'm obviously doing something wrong.  I don't think I can treat the component like the Type Def in clsIO.

Avatar of dve01

ASKER

jmund?   Haven't heard any feedback.  Do you want me to close question and re-enter new questions to board?  Are you on vacation?  Or tired of this thread?  You've been helpful to this point, I just need some feedback to make decision on my next move.  

If you're there,  I'm still struggling to access a component in Singleton ActiveX and define ClsIO variables in main project.   These are the problems I've run into.

1.  In Mainproject I'm trying to initialize I/O variables.  These I/O variables are in Singleton class and defined as a clsIO.  For instance:
        Singleton Class
                   Conveyor_Motor as ClsIO

        Mainproject
                   SingletonProj.Singleton.Conveyor_Motor.board=0
                   SingletonProj.Singleton.Conveyor_Motor.bit=32

*When I run main project I get an "Object variable or with Block Not Set"

2.  I have a DAQclass in singleton project.  This class contains a procedure to Turn On an Output.  OUTPUT_BD(0 to 1) is DAQ output board on form.

            DAQclass  (GLobal multiuse)
                  Public Sub TurnOn_Output(Outputsig as ClsIO)
                          singletonfrm.OUTPUT_BD(outputsig.board).port=outputsig.port
                          singletonfrm.OUTPUT_BD(outputsig.board).bit=outputsig.bit
                          singletonfrm.OUTPUT_BD(outputsig.board).output(true)
                 end sub

When I run main project to TurnOn_Output I get an 'Object Required' error. This probably has something to do with "Object variable or with Block Not Set", when I try to initialize IO variables, Since I'm passing in a ClsIO to TurnOn_Output.

                     singletonproj.daqclass.TurnOn_Output(Conveyor_motor)
In the Class_Initialize() procedure for the Singleton class, you need to put

Private Sub Class_Initialize()
    Set Conveyor_Motor = New clsIO
End Sub

I think that will address the "Object variable not set" error.

When you call a sub, you should avoid parentheses, or use the Call statement:

Instead of:
    singletonproj.daqclass.TurnOn_Output(Conveyor_motor)

Use:
    singletonproj.daqclass.TurnOn_Output Conveyor_motor

Or:
    Call singletonproj.daqclass.TurnOn_Output(Conveyor_motor)

When you use the following:
    singletonproj.daqclass.TurnOn_Output(Conveyor_motor)

VB actually interprets this as:
    singletonproj.daqclass.TurnOn_Output (Conveyor_motor)
    (note the space in between "Output" and the open paren)

Then VB evaluates the expression in parentheses, and passes the result of that evaluation as the parameter to TurnOn_Output.  So, that might have something to do with the "Object required" error too.

I noticed you did a similar thing here:
    singletonfrm.OUTPUT_BD(outputsig.board).output(true)

Again this should be:
    singletonfrm.OUTPUT_BD(outputsig.board).output true

Or:
    Call singletonfrm.OUTPUT_BD(outputsig.board).output(true)

So OUTPUT_BD is an array 0 To 1 of a class?  Note that each array element that is a class must be initialized (probably in form_load) with a Set statement, much like the Set statement I described in the Class_Initialize procedure for the Conveyor_Motor member.  E.g.:

Private Sub Form_Load()
    Set OUTPUT_BD(0) = New <whateverclassitis>
    Set OUTPUT_BD(1) = New <whateverclassitis>
End Sub

Since points have already been awarded for this specific question, I wonder if the admins here at E.E. would permit my offering dve01 my email address for the remaining follow-up questions that are not related to this specific solution?
dve01 you can get my email address from https://www.experts-exchange.com/M_378880.html if you want to follow up on any of the stuff we've talked about.

Jon
Avatar of dve01

ASKER

Thanks for feedback even though points awarded.   I started a new question on 4-4-06 called "Object Variable or With Block not Set" - 500pts.   There is another expert helping.  Feel free to jump in.   I'll mess around with what you sent me.