Hi,
Maybe I'm on a limb here but would it be OK to walk through the steps of 'converting' a dll to be multi-user?
I've already figured out that I need to get into Thread Local Storage / Support and I've already used Threads in my DLL using Critical Section. What I've learned this far on using threads is this (maybe it's of any use to someone):
Suppose you have two functions (i.e. thread1 and thread2) that you want to at the same time, both using SQL function using SQL Tools. Both functions bring the same result, but use a different method of getting it.
First of all:
- the threads have to be functions
- the functions have X as long as passthrough vars.
Steps to build a thread:
1) include win32api.inc
2) declare two global values (i.e. Hit1 as double, Hit2 as double) the value is filled when the function provides a result or is finished (e.g. 1 if OK, 2 if exit without result)
3) make a sub that's gonna call the function by means of threading
4) declare a var in this sub (say g_CriticalSection1) 'AS CRITICAL_SECTION',
3) InitializeCriticalSection g_CriticalSection1
The CS will protect your code
4) right the next line: EnterCriticalSection g_CriticalSection1
5) when using SQL Tools, use a wrapper function (i.e. thread1_wrap(x as long), thread2_wrap(x as long)
6) start the threads (THREAD CREATE ... see PB help) using the wrapper functions, use a do while loop to wait for the return values, like this:
7) when done, use LeaveCriticalSection g_CriticalSection1 and DeleteCriticalSection g_CriticalSection1
The wrapper functions are important for SQL Tools and make it easier for yourself :
- In the function itself: USE FIXED STATEMENTNUMBERS for each thread. Do not try to get a free statement number because it will possibly crash.
- The wrapper function with the counter value make sure each SQL Thread is closed. If you fail to do so and start another SQL Thread using the same number, it will wait till the running thread is finished, i.e. the thread number is free. This is exactly NOT what you want.
- The StatementCancel command is also very important: use fixed statementnumbers for each thread.
OK, that's how I do it (and after quite some time, I got it to work). Of course, any comment for improvements are welcome!
Now my current problem: this DLL of mine is called from a VB app and is part of the installation (i.e. present on each workstation). We are working towards getting it all on the internet.
Then there's suddenly the problem of having the DLL on the server which is called from a VB COM. This still works great in multi-user when the Com is compiled as 'single threaded'. What IIS does in this case is just queue the requests, so one after another, where request 2 starts when request 1 is finished.
So the solution for the Com is simple: use 'apartment threaded' but then my DLL is still not safe! The same behavior is seen when I start my app two time and both have it doing some work using the (same) dll. The results are all mixed up.
So from reading Rector and Newcoming, as well as the forums, I have to use Thread Local Storage. So I included LibMain to my DLL.
My question is: then what? My C++ experience is VERY limited, so it's hard to read the code example for R&C or MSDN....
Do I have to assign all global vars in a UDT with a counter for each thread? Can LibMain keep track of the number of threads and how ?
Hope you guys can shed some light here.
Regards
Jeroen
------------------
Maybe I'm on a limb here but would it be OK to walk through the steps of 'converting' a dll to be multi-user?
I've already figured out that I need to get into Thread Local Storage / Support and I've already used Threads in my DLL using Critical Section. What I've learned this far on using threads is this (maybe it's of any use to someone):
Suppose you have two functions (i.e. thread1 and thread2) that you want to at the same time, both using SQL function using SQL Tools. Both functions bring the same result, but use a different method of getting it.
First of all:
- the threads have to be functions
- the functions have X as long as passthrough vars.
Steps to build a thread:
1) include win32api.inc
2) declare two global values (i.e. Hit1 as double, Hit2 as double) the value is filled when the function provides a result or is finished (e.g. 1 if OK, 2 if exit without result)
3) make a sub that's gonna call the function by means of threading
4) declare a var in this sub (say g_CriticalSection1) 'AS CRITICAL_SECTION',
3) InitializeCriticalSection g_CriticalSection1
The CS will protect your code
4) right the next line: EnterCriticalSection g_CriticalSection1
5) when using SQL Tools, use a wrapper function (i.e. thread1_wrap(x as long), thread2_wrap(x as long)
6) start the threads (THREAD CREATE ... see PB help) using the wrapper functions, use a do while loop to wait for the return values, like this:
Code:
local hThread1 as long local hThread2 as long local result1 as long, result2 as long ' create threads sleep 1 THREAD CREATE THREAD1_WRAP(50) TO hThread1 sleep 1 THREAD CREATE THREAD2_WRAP(50) TO hThread2 ' wait for result Do While Hit1 = 0 and Hit2 = 0 Sleep 1 ' to provide the time slice to the other function Loop ' parse the result and do action if Hit1 = 1 Then ' thread1 has got result so thread2 can stop: pass a counter value Hit2 = -1 ' in thread2 there's a lot of "if Hit2 = -1 then exit function" to make sure it exist as fast as possible end if ' wait for possible other running thread to finish do while hit1 <= 0 or hit2 <= 0 sleep 1 loop ' when all is finished: close threads SLEEP 1 THREAD CLOSE hThread1 TO Result1 SLEEP 1 THREAD CLOSE hThread2 TO Result2 SLEEP 1
The wrapper functions are important for SQL Tools and make it easier for yourself :
Code:
function Thread1_Wrap(x as long) as long ' set the return value to zero Hit1 = 0 ' start the thread for SQL tools also (SQL data also have to be part of the thread so inititalise the SQL thread) SQL_THREAD %THREAD_START, 1 ' call you actual worker function result& = Thread1(X) ' cancel possible opened statement number SQL_STATEMENTCANCEL <db nr>, <fixed stmt Nr> ' close the SQL thread SQL_THREAD %THREAD_STOP, 1 ' to prevent a loop above if Hit1 <= 0 Then Hit1 = 2 end if exit function end function
- In the function itself: USE FIXED STATEMENTNUMBERS for each thread. Do not try to get a free statement number because it will possibly crash.
- The wrapper function with the counter value make sure each SQL Thread is closed. If you fail to do so and start another SQL Thread using the same number, it will wait till the running thread is finished, i.e. the thread number is free. This is exactly NOT what you want.
- The StatementCancel command is also very important: use fixed statementnumbers for each thread.
OK, that's how I do it (and after quite some time, I got it to work). Of course, any comment for improvements are welcome!
Now my current problem: this DLL of mine is called from a VB app and is part of the installation (i.e. present on each workstation). We are working towards getting it all on the internet.
Then there's suddenly the problem of having the DLL on the server which is called from a VB COM. This still works great in multi-user when the Com is compiled as 'single threaded'. What IIS does in this case is just queue the requests, so one after another, where request 2 starts when request 1 is finished.
So the solution for the Com is simple: use 'apartment threaded' but then my DLL is still not safe! The same behavior is seen when I start my app two time and both have it doing some work using the (same) dll. The results are all mixed up.
So from reading Rector and Newcoming, as well as the forums, I have to use Thread Local Storage. So I included LibMain to my DLL.
My question is: then what? My C++ experience is VERY limited, so it's hard to read the code example for R&C or MSDN....
Do I have to assign all global vars in a UDT with a counter for each thread? Can LibMain keep track of the number of threads and how ?
Hope you guys can shed some light here.
Regards
Jeroen
------------------
Comment