Here it is, very similar to what I think you want.
http://www.experts-exchang
Main Topics
Browse All TopicsI have two VB6 projects. the first one is a exe (deviceManager.exe) and the second one is a dll (ersptdev.dll). Currently the EXE calls the dll like this:
--------------------------
Public Function PrinterStatus(ByVal msgCurrent As MSMQMessage, ByVal strDevice As String) As String
On Error GoTo Err_HandlePrintJob
Dim q As MSMQQueue
Dim qi As MSMQQueueInfo
Dim msgPrinter As MSMQMessage
'to locate the existing queues
Dim objqiSet As MSMQQueue
Dim objQuery As MSMQQuery
Dim PlotControl As Object
Dim lngPCValue As Long
Set PlotControl = CreateObject("ERSPtDev.c" & strDevice)
'Send Job to ERSPtDev to do the work
Call PlotControl.iPrintDevice_P
ExitRoutine:
On Error Resume Next
Set PlotControl = Nothing
Exit Function
Err_HandlePrintJob:
[handling stuff removed]
Resume ExitRoutine
End Function
--------------------------
What I want to do is make this line:
Call PlotControl.iPrintDevice_P
run in an independent process. I am not conserned with any return values, and it may take a long time for it to finish. I just want to let ersptdev.dll do it's thing and let DeviceManager.exe continue doing its thing.
Also there is a possibliity that if Devicemanager.exe is able to continue doing it's thing it may call ersptdev again, in which case I would expect both 'threads' to run Simultaneously and independently. Can this be done and if so how?
Thanks for you help. I can provide further details if needed and I have the source code for both projects.
This Question has been solved and asker verified All Experts Exchange premium technology solutions are available to subscription members.
Experts Exchange has been collecting answers to technology questions since 1996…3 million and counting! If you have a question, chances are we already have your answer.
If you can't find the exact answer you're looking for, ask our exclusive community of 50,000 experts. You’ll get a personalized answer from a trusted professional.
Thousands of free tech tips, tricks, how-to’s and tutorials are available in our peer reviewed articles section. See for yourself how smart our experts are, no login required.
Access the answers to your technology questions today.
30-day free trial. Register in 60 seconds.
Members of the expert community talk about why the experience at Experts Exchange is different than what you will find anywhere else.

Try it out and discover for yourself.
30-day free trial. Register in 60 seconds.
Join the community of experts here and help other tech pros by answering question in your area of expertise. You can earn FREE access to all Experts Exchange's premium features and resources.
Here it is, very similar to what I think you want.
http://www.experts-exchang
JohnBPrice,
Thanks for your input. Unfortunately I do not believe your example applies to my problem. your example has a do..While loop that would hit the DoEvents command. My program does not. Once I hit the line that calls the ersptdev.dll there is no chance to run a DoEvents or PostMessage Function until it is completely finished, at which point it is no longer necessary anyway.
Are you saying that you do not have the source/ability to change the code that implements PlotControl.iPrintDevice_P
Perhaps the easiest way would be to wrap PlotControl.iPrintDevice_P
I have not actually tried this, but are you saying that if I put a DoEvents in the .dll the process would continue in the main app? This does not make sense to me. What if I was expecting a return value from the dll? I do not have any events in the dll to raise and I do not know how to declare it WithEvents when I am using a late binding to create the object.
The problem with converting it to an exe is that there are several different classes in the dll that need to be called (thus the late binding) and I do not know of a way to do the same thing with an exe. However creating a wrapper exe with some command line arguments may be a thought. Definitely not as nice but it may be a solution if multithreading is not an option.
I'll look into it, but would still like to see if there is a actually a way to call the dll directly in a seperate process.
Rewrite your DLL in the following way. Create a VB EXE that contains a single class (clsTest). Add the following function to the class.
Public Sub Start()
EnableOneShot 100
End Sub
Then add a module (modTest) and add this code.
Private Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long, _
ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long) As Long
Public Sub EnableOneShot(ByVal ulTime As Long)
glob_TimerID = SetTimer(0, 0, ulTime, AddressOf TimerCallback)
End Sub
Public Sub TimerCallback(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long)
'Kill the timer - we do not need it anymore
KillTimer 0, glob_TimerID
'Call your printing function here!!!!!!
Print_Job
End Sub
Private Sub Print_Job()
'Do your thing!!!!
End Sub
Now, in your main program you simple add the object as a component and open it in the normal way.
Set objTest As New clsTest 'or whatever you called things
clsTest.Start
Of course, you probably want to add some additional functions to the CLASS so that you can initialize the print job and also some method to check the status of the current print job and to prevent multiple calls to Start from crashing things. But you get the idea.
Yes, putting a DoEvents in the .dll (as long as you use one of the tricks to call it in a separate event) will allow the main process to continue, sort of. The VB6 runtime translates windows messages into VB Events, and will wait until one event is processed & returned before it raises the next event. A DoEvents in an event processing loop essentially "pauses" the loop and VB will go ahead and raise any other events waiting to be processed. When these events finish & return, execution returns to the line after the DoEvents. Thus your main app could still process new events, but note they must be new events, an polling loop in the main app will not work, even with the separate event trick.
Technically, if you convert the DLL to an ActiveX EXE, you can call the objects in the exact same way that you would a DLL and it WILL run in a separate process, but the VB runtime won't return control to the calling process until the object is done, so it's about the same situation as using a DLL.
Oh, and you probably need to set the Class Instancing property to SingleUse and Presistable property to NotPresistable.
And also under the Project Properties of the Class object you should check the Unattended Execution and check the Upgrade ActiveX Controls. And also select the "Thread Per Object" option.
Then compile it before you try to add it as a component into your main application. I recommend that you set the compile location to the same directory as your main applications compile location so that you don't have to always reassign the component every time you compile your code. But you will discover those quarks as you work with this method of multi-threading in VB6.
Enlade's code is one of the tricks to call your print process in a separate event, the other is to post yourself a message as in my link above. It actually doesn't matter if it is a DLL or EXE. Notwithstanding, the key to having your main app be responsive to new events is to have the lengthy print process call DoEvents frequently, and have your main app process events & return without doing any lengthy process itself.
JohnBPrice: If you set it up like an ActiveX EXE it isn't the same as just calling a DLL. That is if you use the timer callback method that I mentioned above. I didn't want to call it an ActiveX though since it is a little strange what I am suggesting. But it is really the only way you can create a seperete thread in VB6 (that I know of). Try it and see.
Yes, it is not the same from a windows perspective, but you see the same behavior from within a VB6 app. In fact it doesn't even need to be in a separate executable at all, it can be in your main app, because it is not true Windows mutlit-hreading, it is the VB runtime controlling execution. I've done it hundreds of times
Well, if you think it makes no difference then you should try what I have suggested. You will find that it will add a new weapon to your coding arsinal. It does make a difference. The EXE that I created above will run on a seperete thread then your main application and you will not need any DoEvents. Give it a try.
Oh you will need DoEvents to get the ActiveX component to respond to any requests for status or to respond to a request to cancel. But not if you just want it to go off and print unattendend. In any case, that would be the same restriction for any true multithread solution. And since I want to allow my example to give you some indication of status I will add a DoEvents (though I could leave it out and still give you an example).
If your application is particularly tricky you could employ a third process to control the communications between the main application and your printing ActiveX EXE. You would then simply send requests to the thrid process and it would pass it on to the printing process whenever that printing process got around to checking for any messages (so to speak). Then you would not need to use any DoEvents and you still would be able to get the status and/or control the printing process from your main application. But that is an overkill. I'll get you the example shortly.
Enable,
Actually I already have a method to know if the print job was sucessful or not. I am using MSMQ and I am going to have ERSptDev.dll just send a message indicating success of failure of the job and a completely seperate service picks up the status, so it is not an issue. I just want my app (DeviceManager) to start the jobs and not get hung on the larger print jobs when a smaller one comes down the pike. I will experiment with your example tomorrow.
Sorry about that, I got tied up with dinner and forgot to get back to you. Anyway, I threw together some code that might help you.
The code consists of two processes Test.EXE and axTest.EXE. axTest.EXE is a process that simply counts to 20 seconds and then stops. It could be doing anything, but I just thought of whatever was easiest. axTest.EXE is a sub process of Test.EXE (its really just an ActiveX EXE with a little twist due to the timer callback). This means that there is some dependence that axTest.EXE has on Test.EXE. Which means that in this case you probably want to have your main process (Test.EXE in my case) be able to measure the status of its axTest.EXE sub process (I'm being very liberal with my use of words here and do apologies).
Still, maybe you could get away with getting rid of any status related communications between the main process and the axTest.EXE process. I'll let you consider that in the context of your environment.
You can find the code here:
http://www.enlade.com/Samp
You will need to start by compiling axTest.EXE. After which you then load Test.EXE and go to the References. Uncheck the axTest reference and close the References, and then reopen the References and find axTest again and check it (I think that’s a bug/quark in VB that Microsoft should fix). Then compile Test.EXE and run. Hope it helps or at least points you in the right direction.
Keep in mind that your solution might be to create a third process (as I said earlier) that will control the communication between Test.EXE and axTest.EXE. Having a third process makes things run very smoothly from the users perspective and allows you to eliminate all the DoEvents as well. Keep that in mind as you work through my code.
Actually, this code is a little better. I just took out a button and added a timer to show the status of axTest.EXE's count.
http://www.enlade.com/Samp
Basically, the first button starts axTest.EXE counting. While the second button shows you that you can do other things without axTest.EXE interfering. The third button was suppose to get the current status of axTest.EXE, but I just made it a Timer that updates the label with the status.
Now, axTest.EXE needed the DoEvents in the program, but not because it interfered with Test.EXE's exicution (because it doesn't since it is running on a seperate thread). However, it does need it in order to respond to any status requests from Test.EXE. If you don't include it then you when Test.EXE trys to talk to axTest.EXE then Test.EXE will lock up waiting for a reply. Keep in mind that you don't need to have a loop in your printing program. You simple need to throw in DoEvents every so often to give your main process a chance to get a word in.
If you don't want to use any DoEvents then you need to create that thrid process that I was talking about earlier. Hope that helps.
mmm, it seems cool enough, but what you have done is hijack the Apartment-Thread for the COM object using a windows callback under the nose of the VB runtime for the COM component. This is fine for many things, and it probably mostly works as long as you use apartment threading and thread per object so you get a new thread to hijack for each instance and avoid certain high risk activity, but there are constraints and situations that could cause it to intermittently fail, hang, or GPF out.
First, as you said, you can't touch anything in the object unless you put in DoEvents. Indeed, you can't even let the object go out of scope. If the hijack hasn't started, windows will terminate the thread and the hijack will never start. If the thread is running, VB will hang, eventually getting a COM timeout. So you either constantly add to an array of new objects forever to get new threads, eventually having lots of finished threads that will never terminate, or you check if it is done and to do so you must put DoEvents in the loop, which is what you proposed to avoid. (I suppose you could keep track of some ID, perhaps the thread ID, associated with each object and have the object post back a message to yourself (with MSMQ or windows), but that brings it's own complications).
Second, the VB runtime has finished it's marshalling from the initial call, and has finished all the COM cleanup such as CoGetInterfaceAndReleaseSt
Third, the hijacked execution is no longer under VB runtime control, so if your main app ends it will not end your threads (windows will keep the exe in memory and running), you have to incorporate a way to shut them down or wait for all the objects to finish. If you app finishes unexpectedly, the object threads will continue to run until they are killed, crash, or finish. Furthermore, you have to be responsible for you own cleanup for some things because the VB runtime doesn't know the hijack is running and possibly allocating things, but you don't really know which ones are safe and which ones are not. Thus, an invitation for slow memory leaks.
Lastly, you probably won't be able to use the VB interactive debugger to debug the hijacked portions. Not that you may care, you also won't get any tech support from MS because they do not support unmarshalled execution of a VB 6 COM object.
For a quick hack to achieve a specific goal, fine. To build a Windows Service in VB 6 (which isn't recommended anyway) hijacking an unmarshalled "stealth" COM thread is pretty risky. If it works for your need, great, if you see weird unexplained issues, try taking it out and go with a clean approach (they are basically the same code, it only differs in how you trigger the second execution). At the end of the day, rather than Hijacking a thread, I would just whip up a safe multi-threaded app in VB.net.
Enlade,
Thank you for your code examples. they seem to work as intended, but as JohnBPrice mentioned it does not work for my purpose. Specifically I need to be able to run the object multipule times in seperate threads at the same time, which your code does not allow. At least not without creating an array of objects. Also What John said about this line of coding being unstable is worrysome to me, this service NEEDS to be robust, I cannot have a solution that gives me errors and I spend weeks trying to troubbleshoot the problem.
JohnBPrice,
You seem to be saying that multiThreading in VB6 is not available and that I am going to have to convert the Service Application to .NET, assuming I can do this (I do have some .NET experience, but never wrote a service, used multithreading or actually converted a project from VB6) can I leave the .dll in VB6? or do I have to convert both of them? Does .NET multithreading allow for objects to run out of context? some advice would be appreciated. The other option discussed was to write a wrapper .exe to call it and let it run in it's own thread. since I am Already using MSMQ to get the callback that I need, do you think that this would be an easier solution or should I be conserned with these exe's running independently and not being able to shut them down.
Again thanks to both of you for your help so far...
Technically, Enlades code will allow multiple objects on multiple threads at the same time, you just need to create a new object for each instance, but since your example method is "iPrintDevice_PrintJob", is it a print job? If so, why would you care if multiple instances run at the same time, since the print spooler will queue them up anyway? Just queue them up in your main app.
Well, not that it is not available, just that it is easier & safe to do it in VB.Net. You don't have to rewrite it in VB.Net, you could just make a wrapper for your class, and make the wrapper multi-threaded. Then everything would be kosher, since all the COM init will be handled normally. If you are short on time, the exe wrapper would be faster than learning VB.Net, but if you have the time, then VB.Net is cleaner.
BTW, Building a service in VB.Net is a LOT easier than you'd think, since the .Net framework includes service, service installer, and service controller classes.
Yes it is a print job, but it does not just print to one printer. this application is the hub for a print server that controlls all the printers at a particular location and there are times when a user sends a very large print job to a large format plotter (I have seen examples of over 2 GB) and it ties up the server for an hour or more and anyone who is trying to print a one page word doc to their local (local meaning proximity) laserjet printer has to wait until the job is completely done spooling before ersptdev.dll lets go of it.
How would I make a multi-threaded wrapper for the class in VB.net? Let me make sure I understand what you are saying. I create a .NET exe that uses multithreading to call the .dll and then call the .NET exe from the main VB6 app.
The .NET exe calls out the .dll in a seperate thread and returns the original thread back to the VB6 app so it can do it all over again.
Do I have it essentially right?
I may just look into converting the application to .NET. it is really not all that big, and if creating a service is as easy as you say that may be the way to go.
Well, here is a crack at a VB.Net wrapper for a DLL, but I haven't really tried it yet. You'd have to wrap every function you wanted to spin in a thread.
This is a VB.Net DLL from the wizard, in the project build properties for the app you need to set "Register for COM Interop"
Imports System.Threading
Public Interface IClass1
Function StartOne() As Boolean
End Interface
Public Class Class1
Implements IClass1
Implements IDisposable
Dim x As Thread
Function StartOne() As Boolean Implements IClass1.StartOne
'Start a new thread
x = New Thread(AddressOf handler)
x.Start() 'now the thread is running, we can exit
StartOne = True 'whatever
End Function
Private Sub handler()
'this guy gets called on his own thread
Dim x As New Project1.Class1 'this is your real worker object
x.Start() 'This is the real start to your worker object
End Sub
Public Sub Dispose() Implements System.IDisposable.Dispose
'I haven't tried this, but I think it might work, essentially kill the thread if we die
If x.ThreadState <> ThreadState.Unstarted Then
x.Abort()
End If
End Sub
End Class
JohnBPrice,
I had to add some aruments that I pass to ERSPtDev but Here is what I have:
Function StartOne(ByVal strDevice As String, ByVal msgCurrent As MSMQ.MSMQMessage) As Boolean Implements IThreading.StartOne
Dim PlotControl As Object = CreateObject("ERSPtDev.c" & strDevice)
'Start thread
x = New Thread(AddressOf PlotControl.iPrintDevice_P
x.Start()
StartOne = True
End Function
on the line where the new thread is created I get this: "'AddressOf' operand must be the name of a method; no parentheses are needed."
How do I pass these arguments to the thread? Am I doing this right?
You can't spin your COM control directly as a thread, use AddressOf a method in your class, and have that method call PlotControl.iPrintDevice_P
I'm not sure that you could pass args to the start, you might have to copy them to class storage. Might want to check the docs, but I believe VB or CLR will ensure that references to class storage thread safe.
BTW, I'm thinking that if you have a lot of methods, you could make the VB.Net class as "Implements ERSPtDev.cDevice", in which case you could interchange your VB.Net calls (which spin off threads) with direct calls to ERSptDev.cDevice, thus making converting your code pretty easy, just changing the library name, with all the calls being exactly the same. Haven't had time to try it though.
Oh yeah I missed the handeler sub - that makes more sense
>>BTW, I'm thinking that if you have a lot of methods, you could make the VB.Net class as "Implements ERSPtDev.cDevice", in which case you >>could interchange your VB.Net calls (which spin off threads) with direct calls to ERSptDev.cDevice, thus making converting your code pretty >>easy, just changing the library name, with all the calls being exactly the same. Haven't had time to try it though.
Good Point I will give this a try.
I will spend some more time hashing things out and let you know how it works, it may be next week though with how things are going today.
Thanks again for your help so far...
JohnBPrice: True enough, but keep in mind that my intention was not to avoid the DoEvents altogether, but simply to avoid its use to keep the main process exicuting. This is to say that in the first instance the DoEvents is being used to give the main process a change to continue exicution, but in the second case it is only being used to allow the second process to communicate with the main process. To illistarate the difference I suggested that a thrid process be created that simply stores the state of each of the other processes. Then there is no need for any DoEvents though there would be a need to add code so that each process checks with that third process for instruction on the state and/or desires of the other process (whew).
Most of the other issues that you mentioned are simply a byproduct of using multithreading to begin with. They are all very important issues that any multithreading environment will need to consider when writting code. So, it would do well for Thras to consider them all when he works out his solution. My point was not that VB6 is the best language to do Multithreading, because I would lose that arguement very quickly. What I am proposing is a bit strange and clearly not a method that was intended by Microsoft. However, its still the only way that I can see to do anything close to what we would consider a true multithreaded application in VB6. Worth investigating to say the least.
If you application simply prints jobs then why would you create more then one instance. You simply create a queue function that you call to queue jobs on the same object. Right?
Maybe I don't understand your application, but it seemed like you just wanted some thread that printed jobs in the background. If thats the case then you just need to write the code so that it queues each job before it prints and that way you would only need one object. Just a thought.
I didn't know that moving to VB.Net is an option for you. If you can move to .Net then you will have some other things you can try. However, VB6 limits what you can do by way of MultiThreading. If VB.Net is an option then you probably should move to .Net for this type of a problem.
In any case, it sounds like your problem is not really a matter of writting a multiThreaded application. It sounds like you simply want to write a stand alone application that other application use to print jobs. So really you are looking for examples of ways that you can communicate between applications (right?). The DLL that you would link into your application would simple be an API for your stand alone printing application. So you really need three sets of code. You need to write your main application, you need to write a stand alone printing application, and you need to write a DLL API for you stand alone printing application. Right?
I'm just trying to understand the problem better. But MultiThreading doesn't seem to be what you are interested in (at least in the sense of a "MultiThreaded application").
OK, check this out, It is not complete. Dispose does not work, it never gets called, I'll have to look up the correct destructor name. It has the nice attribute that you can debug into the threads using the VB.net debugger.
Here is my DLL to emulate your ERS, I named the project ERSPtDev to match yours, it has one class, cPlotter
Option Explicit
Private gabort As Boolean
Public Sub PrintDevice_PrintJob(Param
'This is just a really long loop to run for a long time
Dim I As Long
Dim j As Long
Dim L As Long
Dim x As Long
gabort = False
For L = 1 To 10000
For I = 1 To 10000
For j = 1 To 10000
x = I * j
DoEvents
If gabort Then Exit Sub
Next j
Next I
Next L
End Sub
Public Sub PrintDevice_GracefullTermi
gabort = True
End Sub
Here is the VB.Net wrapper,
Imports System.Threading
Public Class cPlotter
Implements ERSPtDev.cPlotter 'Note this implements the original DLL class!
Implements IDisposable
Private t As Thread
Private RealWorker As New ERSPtDev.cPlotter 'This is the real object that does the work
Private MyParamOne As String
Private MyParamTwo As Short
Public Sub PrintDevice_GracefullTermi
'I threw this in, didn't try to see if it worked. I'm presumng your class might have some "Cancel Print" kind of call
t.Suspend() 'waits until the thread reaches a safe point
RealWorker.PrintDevice_Gra
t.Resume()
End Sub
Public Sub PrintDevice_PrintJob(ByRef
'This implements the PrintDevice_PrintJob call, but in a separate thread
MyParamOne = ParamOne
MyParamTwo = ParamTwo
t = New Thread(AddressOf MyPrintDevice_PrintJob)
t.Start()
End Sub
Private Sub MyPrintDevice_PrintJob()
'This is the new thread, which just does the call
RealWorker.PrintDevice_Pri
End Sub
Public Sub Dispose() Implements System.IDisposable.Dispose
t.Abort() 'just kill it
End Sub
End Class
And finally, here is the main app
Private Sub cmdStartOne_Click()
Dim c As New ERSPtDevAsynch.cPlotter
Call c.PrintDevice_PrintJob("Bl
End Sub
Enlade, I guess my point could be summed up as "true multi-threading in VB6 scares me". Your trick is very cool, and I know lots of people do that, and some even attempt to do CreateThread which comes with a lot more details to fret over. I'd rather either simulate it with straight VB6 style DoEvents, or switch to .net where I can delude myself that MS has taken care of or at least has documented the threading issues.
OK I think I have the beginning to something that will work, but I have never actually created a dll in .NET before. Is there a way to debug the dll? I have been able to debug dll's in VB6 and it just waits until it is called by an external app and it runs in debug great. When I try to debug the dll in .NET I get: "A Project with an Output Type of Class Library cannot be started directly....."
Any ideas?
I know this may be outside the original scope of the question, but I will add points if I can get this all worked out.
Thanks again
I created PTDevAsync.dll that should do what want, but I cannot either add a refrence to it in VB6 ("Cannot add refrence to specified File") nor can I register it using regsvr32 ("Entry point cannot be found"). I perfer to add a refrence so that I can use early binding in my main VB6 app since there is only one class in the .NET async dll
I assume that there is something I am missing about the .NET stuff to work with unmanaged (vb6) code, but I am at a loss to figureout what it is.
Here is the Entire code that I have:
Imports System.Threading
Public Interface IThreading
Function StartOne(ByVal strDevice As String, ByVal msgCurrent As MSMQ.MSMQMessage) As Boolean
End Interface
Public Class Threading
Implements IThreading
Implements IDisposable
Dim x As Thread
Dim DevCls As String
Dim msgCur As MSMQ.MSMQMessage
Public Function StartOne(ByVal strDevice As String, ByVal msgCurrent As MSMQ.MSMQMessage) As Boolean Implements IThreading.StartOne
DevCls = strDevice
msgCur = msgCurrent
'Start thread
x = New Thread(AddressOf Handler)
x.Start()
StartOne = True
End Function
Public Sub dispose() Implements IDisposable.Dispose
End Sub
Private Sub Handler()
Dim PlotControl As Object = CreateObject("ERSPtDev.c" & DevCls)
PlotControl.iPrintDevice_P
End Sub
End Class
I did not Implelemt ERSPtDev.cXXX becasue there are alot of classes and potential for more down the road, so it is better to use latebinding in the this new .NET thredding util that way we will not need to modify it in the future.
Also I have one question that is a little worrysome to me and that is the two class level variables I need to make this thing work. This is probably unlikely, but isn't it possible to have the variables set by one thread and used by another, so that you actually passed the wrong 'set' of variables to the wrong thread? There isn't a whole lot going on between the setting of the variables and using them, so I suppose I could just ingore it.
Thanks again.
In my test project, I could not reference the .DLL by browsing, I got the same message you did, but my project name automatically showed up on my VB6 list of available references, so I didn't to browse for it. I did notice that the VB6 list showed the .TLB file from the bin directory as the reference, not the .DLL, so give that a try. There is probably a switch in VB.NEt to include the TLD info in the DLL as VB6 does, but I didn't look for it since it showed up.
If you create a threading object for each thread you start, each threading object will have it's own variables, and this also gives you the option to abort a thread or otherwise communicate with it.
If you used just one threading object to create multiple threads, you still shouldn't have a problem, because the only thing calling the threading object is your VB6 app, which isn't multi-threaded so it will never be able to call StartOne while an instance of StartOne is already running.
I thought of one possible glitch. The second parameter is a MSMQMessage, is MSMQMessage an object? I'm not sure how COM marshall's object references, but I suspect it doesn't do a deep copy, even though you pass it ByVal. It might just add a new reference onto the underlying object. Everything would be fine unless your main app modifies the object underneath (it can go out of scope, the new reference will keep the object alive). If, for example, you change the text of the message object parameter after you have set it printing, that might foul things up.
JohnBPrice
You are right it was already in my list, I just assumed that it would not be.
OK I got it working for the most part. What I mean is that it appears that the multi threading is working, except that my main app really hangs while ERSPtDev is doing it's stuff. I send a very large job through to see if I could get smaller jobs to go through at the same time, and it did work, but it was very slow. Don't get me wrong I KNOW it is going to be slower, even alot slower, but it appears that the main app is still somehow linked to those threads because I was running othere apps that were all still very responsive. Also when I ran the main code in debug I got the thread back right away, but the debugger was 'Not responding' until the job got through.
I'm still gving you the points (adding a few as I said I would too) 'cause I think we have accomplised what I asked for, but I would be intrested in your take on why I am seeing this.
Thanks again for all your help!
I'm guessing you were using the VB6 debugger? The VB6 debugger is not thread safe, so you can't use it to debug multi-threaded apps. If you looked at your running processes, even if you were sitting on the line after the call, you'd see VBRun.EXE churning up CPU cycles. That's because the VB6 debugger runs apps in the VB6 process, not in a separate process like the VB.net debugger. You can to use the VB.net debugger. You might have to revert to the old fashined method of debugging ERSPtDev (e.g. use msgbox or application events (since it is a service)). You might be able to debug ERSPtDev with the VB.Net debugger if you build with symbols. Essentially you are debugging the dissassembly, but the symbols show you comments with what the original VB 6 code was.
Once the VB6 debugger locked up on me I complied it and I am now just running it as a service. Infact I am not actually debugging anything at this point as all components are currently compiled and 'working'. I am not too conserned about debugging ERSPtDev at the moment - I can always write up a wrapper app if I need to.
What I think is happining is that unless I hit a DoEvents in ERSPtDev then the main app (the VB6 Service) Hangs. When I hit a DoEvents then the main app can start the next job. Since I am not debugging I cannot gurantee this, but I have a good understanding of what is happining even if I am not debugging.
I still find this strange. the Service is not all that large so I am considering just rewrighting the whole service in .NET at this point. It would get rid of my extra Async.dll Do you think that it would have any effect on this hanging that I am seeing? I would REALLY rather not try to redo ERSPtDev as it is not small.
What does ERSPtDev do after starting a thread? It should exit out of any subs/functions it is in so that the VB runtime event loop (it's WinProc) can respond to a new print job event can be handled. It's been a while since I wrote a service in VB6, I forget what the correct way is to keep your application from exiting altogether. In a non-service, you would just open a form. The existance of the form would keep the whole EXE in memory.
If ERSPtDev is a service and it itself has long loops (other than the print threads you now spin off), I'm thinking that not having DoEvents might have some effects. Windows might be sending the service events, such as external application start stops or whatever events services get.
Lets just make sure were are clear as to what is going on.
DeviceManager.exe is the Windows Service currently a VB6 exe. I only changed 2 lines of code so that it would call PtDevAsync instead of ERSPtDev
DeviceManager calls PtDevAsync.dll (our new .NET .dll) which creates a new thread and runs ERSPtDev.dll (a VB6 dll) - I have made no changes to ERSPtDev.dll
Essentially what ERSPtDev.dll does is concatinates several files together into one larger print job along with some header information that is baised on how the user requested it to be printed (this is why we need the MSMQmessage - it has all this information). Once the File that contains the whole print job has been completely created it calls a C++ .dll file that sends it off to a windows printer. Then the original function exits.
I can get jobs to go through while the large print job is concatinated by putting DoEvents in the loop and it seems to work well, (or at least well enough) but there is only one line that sends the file to the print Queue and while this happens everything just 'waits' until it is completely finished spooling to the queue.
Does that help you understand what the process is?
Here is some code that seems to work, I get all three events & I can see the threads with Spy++.
Sub Main()
Dim x As ERSPtDev.cPlotter 'this is the interface
Set x = New ERSPtDevAsynch.cPlotter 'this is the asynch starter
App.LogEvent "Starting first thread", 4
x.PrintDevice_PrintJob "Blah", 5
Set x = Nothing
Set x = New ERSPtDevAsynch.cPlotter
App.LogEvent "Starting second thread", 4
x.PrintDevice_PrintJob "Blah2", 5
Set x = Nothing
Set x = New ERSPtDevAsynch.cPlotter
App.LogEvent "Starting Third thread", 4
x.PrintDevice_PrintJob "Blah2", 5
'sit and do nothing
While True
Sleep 500
DoEvents
Wend
End Sub
Still not 100% sure what is going on, but It appears that the timer event is not fireing.
the Service has a timer that checks for new jobs every 1 second. I put logging events in to show me as soon as the event is fired and at the very end of the event (which 'should' then fire again in 1 second). I also am logging events just after handing it off to PtDevAsyc
Every time I get a new job - regardless of how big it is now - all events (in timer event, after thread is handed back to Service, out of timer) occur witin one second.
The delay occurs between the end of the Timer event and the fireing of the next one. One other interesting thing to note is that the delay seems to coincide with a delay in ERSPtDev. I noticed that other small jobs were sent though but were delayed until the NEXT timer event fired, even though they were picked up on and sent off on the LAST timer event.
one other thing to note is that the C++ dll I mentioned that submits the file is also called several times (in a loop) to concatinate the files into one job. could it have anything to do with this? It appears that things are not Exactly running completely independently.
So, a timer (a VB6 timer on a form?) fires, collects the MSMQ message, sends it to ERSPtDevAsync, which starts ERSPtDev in a thread and returns, ERSPtDevAsync returns, control returns to DeviceManager, which exits the timer sub, but the next timer event doesn't occur until the thread started by ERSPtDevAsync finishes?
Business Accounts
Answer for Membership
by: JohnBPricePosted on 2005-01-06 at 11:51:22ID: 12975955
VB6 doesn't like to do multi-threaded, but you can trick it. This only works, I believe, as long as the original thread is running. Basically, you put the request to process in a queue, trigger a new event to yourself, and return. Upon receiving the event, you process the queue (calling DoEvents frequently enough to avoid hosing up your main app). I answered a question a while ago that describes this, give me a minute and I'll look it up.
BTW, the clean way would be to create a service or write it in a language which supports threads, e.g. VB.Net.