Florent,
I've got to do some rewriting but If this works!!
Thanks a lot already, I'll let you know!
Cheers
jeroen
------------------
Announcement
Collapse
No announcement yet.
multi user dll
Collapse
X
-
Hi Jeroen
You were very close, I took the liberty of modifying your code.
Here's what it could look like:
Code:#COMPILE EXE $INCLUDE "WIN32API.INC" DECLARE FUNCTION CoTaskMemAlloc LIB "ole32.dll" ALIAS "CoTaskMemAlloc" (BYVAL DWORD) AS DWORD DECLARE SUB CoTaskMemFree LIB "ole32.dll" ALIAS "CoTaskMemFree" (BYVAL DWORD) TYPE SessionInfo InputString AS STRING * 200 OutputString AS STRING * 200 END TYPE TYPE MyInfo PtrToNext AS DWORD Info AS STRING * 1000 END TYPE SUB RunThrough_Order ALIAS "RUNTHROUGH_ORDER" (BYREF Session AS SessionInfo) EXPORT DIM pMyInfo AS MyInfo PTR LOCAL hThread AS LONG LOCAL hResult AS LONG LOCAL dwResult AS DWORD pMyInfo = CoTaskMemAlloc(SIZEOF(@pMyInfo)) IF pMyInfo THEN @pMyInfo.Info = TRIM$(Session.InputString) THREAD CREATE MyThread(pMyInfo) TO hThread dwResult = WaitForSingleObject( hThread, %INFINITE ) 'wait for thread completion SELECT CASE dwResult CASE %WAIT_OBJECT_0 Session.OutputString = TRIM$(@pMyInfo.Info) + CHR$(0) 'terminate the string MSGBOX Session.OutputString CASE %WAIT_TIMEOUT MSGBOX "WaitForSingleObject returned and hThread is not signaled" END SELECT THREAD CLOSE hThread TO hResult CoTaskMemFree pMyInfo END IF END SUB FUNCTION MyThread(pMyInfo AS MyInfo PTR) AS LONG IF pMyInfo THEN @pMyInfo.Info = TRIM$(@pMyInfo.Info) + " - delta!!" END IF END FUNCTION FUNCTION PBMAIN() AS LONG LOCAL Session AS SessionInfo Session.InputString = "1e input" CALL RunThrough_Order(Session) END FUNCTION
Florent
------------------
Leave a comment:
-
-
Thanks Semen,
Could you look at this shortly and tell me what I'm doing wrong here?
I tried using your code example from the forum and apply it to my example.
I need to pass the info to my thread. As you can see from the remmed out stuff
Code:#COMPILE EXE "MUTEST1.EXE" $INCLUDE "WIN32API.INC" DECLARE FUNCTION CoTaskMemAlloc LIB "ole32.dll" ALIAS "CoTaskMemAlloc" (BYVAL DWORD) AS DWORD DECLARE SUB CoTaskMemFree LIB "ole32.dll" ALIAS "CoTaskMemFree" (BYVAL DWORD) TYPE SessionInfo InputString AS STRING * 200 OutputString AS STRING * 200 END TYPE TYPE MyInfo PtrToNext AS DWORD Info AS STRING * 1000 END TYPE SUB RunThrough_Order ALIAS "RUNTHROUGH_ORDER" (BYREF Session AS SessionInfo) EXPORT LOCAL PtrToRoot AS DWORD, PtrToEl AS DWORD DIM pMyInfo AS MyInfo PTR LOCAL hThread AS LONG LOCAL hResult AS LONG LOCAL lResult AS LONG PtrToEl = CoTaskMemAlloc(LEN(MyInfo)) IF PtrToRoot = 0 THEN PtrToRoot = PtrToEl ELSE @pMyInfo.PtrToNext = PtrToEl END IF pMyInfo = PtrToEl @pMyInfo.Info = TRIM$(Session.InputString) THREAD CREATE MyThread(pMyInfo) TO hThread 'THREAD CREATE MyThread(PtrToEl) TO hThread lResult = WaitForSingleObject( pMyInfo, %INFINITE ) 'lResult = WaitForSingleObject( PtrToEl, %INFINITE ) THREAD CLOSE hThread TO hResult Session.OutputString = TRIM$(@pMyInfo.Info) CoTaskMemFree pMyInfo END SUB FUNCTION MyThread(pMyInfo1 AS MyInfo PTR) AS LONG 'FUNCTION MyThread(PtrToEl AS DWORD) AS LONG DIM pMyInfo AS MyInfo PTR pMyInfo = pMyInfo1 'pMyInfo = PtrToEl MSGBOX "@pMyInfo.Info In Thread = " + @pMyInfo.Info @pMyInfo.Info = TRIM$(@pMyInfo.Info) + " - delta!!" END FUNCTION FUNCTION PBMAIN() AS LONG LOCAL Session AS SessionInfo Session.InputString = "1e input" CALL RunThrough_Order(Session) SLEEP 3000 MSGBOX "Ending : " + TRIM$(Session.OutputString) END FUNCTION
Any ideas?
Hope you can help
Regards
Jeroen
------------------
Leave a comment:
-
-
A song about iMalloc (by MSDN)
The OLE Memory Allocator
The COM Library provides an implementation of a memory allocator that is thread-safe (cannot cause problems in multi-threaded situations). Whenever ownership of an allocated chunk of memory is passed through a COM interface or between a client and the COM library, you must use this allocator to allocate the memory. Allocation internal to an object can use any allocation scheme desired, but the COM memory allocator is a handy, efficient, and thread-safe allocator.
A call to the API function CoGetMalloc provides a pointer to the OLE allocator, which is an implementation of the IMalloc interface. Rather than doing this, it is more efficient to call the helper functions CoTaskMemAlloc, CoTaskMemRealloc, and CoTaskMemFree, which wrap getting a pointer to the task memory allocator, calling the corresponding IMalloc method, and then releasing the pointer to the allocator.
------------------
E-MAIL: [email protected]
Leave a comment:
-
-
Thanks Lance,
but just to be a bit more specific:
How do I do this??
Is this getting in the right direction?
I declare two vars before starting the threads:
LOCAL SessionPtr as SESSIONINFO PTR
LOCAL InternalPtr as INTERNALINFO PTR
Then create the threads (that only read the sessioninfo and fill different values in the IternalInfo)
And then - in each thread - declare the same vars and use
SessionPtr = HeapAlloc( GetProcessHeap(), %HEAP_ZERO_MEMORY, LEN(SessionInfo) )
InternalPtr = HeapAlloc( GetProcessHeap(), %HEAP_ZERO_MEMORY, LEN(InternalInfo) )
Something like this??
regards
Jeroen
------------------
Leave a comment:
-
-
If I'm reading your question correctly, simply pass the address of the target UDT to the thread function. This UDT would contain any necessary "startup" values that the thread function will need to use.
------------------
Lance
PowerBASIC Support
mailto:[email protected][email protected]</A>
Leave a comment:
-
-
Hi,
Hope someone can help me al little further.
What I've done: I've put all global variables in a type and I already had one type that serves as input/output. Then I've rewritten my DLL to pass these types from function to function. Working fine, up to the point where I start my (internal) threading. This is where I get stuck.
I can only send the paramater value to the thread functions. How do I pass my variables then? It's got to do with this pointer to the type like in Florent's example but I don't really get it. Reading Rector & Newcomer doesn't do the trick. I'm missing something basic but don't know what.
So I have the two types:
Code:Type Session Inputstring as string * 200 Outputstring as string * 200 end type Type InternalInfo PrintInfo as double testString as string * 200 TempString as String * 200 end type
Furthermore, I don't start the dll instance like in Florent's example myself. The Dll is called from a VB COM from the internet. My problem is to get this thread safe with multi-user.
Hope you guys can help
Regards
Jeroen
------------------
Leave a comment:
-
-
using global variables in threads is very risky if the target variable is larger than 32-bits, since two or more threads may try to read and write to the variable's memory at the same time (actually it involves a context-switch when the data is only partially written and therefore contains undefined data). accessing global and static variables requires the use of a synchronization object, such as a critical section, mutex, etc.
if the variable is 32-bits (or smaller), then the read/write cannot be interrupted by a context-switch, so the variable is "guaranteed" to contain legitimate data.
for more information on writing multi-threaded app's, get yourself a copy of rector/newcomer's "win32 programming". see the faq forum posting "win32 reference books" for isbn numbers, etc. http://www.powerbasic.com/support/pb...hread.php?t=39
regarding freefile in threads - rather than go to long lengths to deal with mutexes, etc, it is much easier to just restrict each thread to use a unique range of file numbers, say, based upon the thread id value. this placed the file number management in the hands of the programmer, but it is a simple and effective solution.
for example, if each thread requires a maximum of 50 files, do something like this:
Code:%maxthreadfiles = 50 ' maximum per thread %primarythreadhandles = 100 ' reserved for primary thread ... function mythread(byval id&) as long local hfile1&, hfile2& hfile1& = id& * %maxthreadfiles + %primarythreadhandles hfile2& = hfile1 + 1 ... end function
------------------
lance
powerbasic support
mailto:[email protected][email protected]</a>
Leave a comment:
-
-
Hi Florent, thanks for responding.
You're right: this is not a real program. BTW: I thought freefile was ALWAYS thread safe (I read that somewhere).
The log file is not really important, but in my dll I use it for debugging.
I know now that passing all local vars is thread-safe. But I want to learn TLS too.
That's why I put the code there: it looks a bit like what I have : incoming, parsing, outgoing.
How would I make this piece of code (no matter how redicule) thread-safe??
Can't i use globals at all or do I have to put them in an array somehow?
The answers to those question are important to me (just forget about the writing of the log file).
Hope this provides more info on what I would like.
Greetings
Jeroen
------------------
[This message has been edited by jeroen brouwers (edited January 08, 2001).]
Leave a comment:
-
-
Hi Jeroen
you're initialiing the TLS index in LibMain but you are not using
it to store/retrieve information. In the example you show you
are outputting information to file.
FREEFILE is not thread-safe. If you're going to do disk
access across sessions you'll need to encapsulate the FILE access
functions using mutexes.
You are using a lot of globals to store information and write to it
which you should not do with a thread safe app. I'm not quite
sure what you want since in the test code you include you
could directly modify the passed in VBSession and then call
ParseInput() passing it the VBSession as a parameter (which would
make it thread safe) instead of using global variables to access
the info.
Remember that if you pass variables around on the stack your app
is automatically thread-safe and you don't need to use TLS at all.
In the example shown you do not need TLS to make it thread-safe.
Just get rid of all Globals, get rid of the TLS index and modify
the VBSession and pass it from function to function as needed.
What you'd need to do is to make sure that Disk Access and writes
are guarded by Named Mutexes if you want to make sure that writes to
file are synchronised accross processes and threads. Named mutexes
are valid across processes whereas Critical Sections are valid
for all threads in a process.
I take it this is not a real life example - could you tell me what
you are trying to do? Do you want to write a log of all VB apps
that access your DLL? Would all the apps write to the same disk
file? Should your app be synchronised across processes or
just within the threads of one process?
Cheers
Florent
------------------
Leave a comment:
-
-
OK Florent,
I checked your example from the forum, but I don't really get it.
Humour me if you will: Just a simple example
The SessionInfo is passed from a VB app. I would like multiple apps to use this DLL.
I already included the LibMain and adapted it according to your example.
And then what?
Here's the code:
Code:#COMPILE DLL "C:\PBDLL\TestMU\test1.dll" $INCLUDE "C:\PBDLL\TESTMU\SQLTools\WIN32API.INC" ' UDT TYPE SessionInfo InputString AS STRING * 200 OutputString AS STRING * 700 TestInfo AS STRING * 250 debugger AS STRING * 50 PrintInfo AS STRING * 10 UserInfo AS STRING * 100 Root AS STRING * 100 END TYPE ' DECLARES DECLARE SUB ENTRY ALIAS "ENTRY" (BYREF VBSession AS SessionInfo) DECLARE SUB TestSub(VBSession AS SessionInfo) DECLARE SUB ParseInput() ' GLOBAL / CONSTANTS GLOBAL TLSINDEX AS DWORD GLOBAL InputString AS STRING GLOBAL OutputString AS STRING GLOBAL TestInfo AS STRING GLOBAL Debugger AS DOUBLE GLOBAL PrintInfo AS DOUBLE GLOBAL UserInfo AS STRING GLOBAL Root AS STRING GLOBAL FreeFile1& '------------------------------------------------ '------------------------------------------------ '------------------------------------------------ FUNCTION LIBMAIN(BYVAL hInstance AS LONG, _ BYVAL fwdReason AS LONG, _ BYVAL lpvReserved AS LONG) EXPORT AS LONG SELECT CASE fwdReason CASE %DLL_PROCESS_ATTACH TLSINDEX = TLSALLOC() ' allocate TLS IF TLSINDEX = %TLS_OUT_OF_INDEXES THEN FUNCTION = %FALSE ELSE CALL TLSSETVALUE(TLSINDEX, BYVAL %NULL) FUNCTION = %TRUE END IF CASE %DLL_PROCESS_DETACH CALL TLSFREE(TLSINDEX) FUNCTION = %TRUE CASE %DLL_THREAD_ATTACH CALL TLSSETVALUE(TLSINDEX, BYVAL %NULL) FUNCTION = %TRUE CASE %DLL_THREAD_DETACH FUNCTION = %TRUE END SELECT EXIT FUNCTION END FUNCTION SUB Entry ALIAS "ENTRY" (BYREF VBSession AS SessionInfo) EXPORT ' make it thread safe for multi-user ' ???? ' do work CALL testSub(VBSession) END SUB SUB TestSub(VBSession AS SessionInfo) ' convert values InputString = " " + TRIM$(VBSession.InputString) + " " OutputString = "" TestInfo = TRIM$(VBSession.InputString) Debugger = VAL(TRIM$(VBSession.InputString)) PrintInfo = VAL(TRIM$(VBSession.InputString)) Root = TRIM$(VBSession.InputString) ' open log IF PrintInfo >= 5 THEN FreeFile1& = FREEFILE IF RIGHT$(Root, 1) = "\" THEN Root = MID$(Root, 1, LEN(Root) - 1) END IF OPEN Root + "\test.log" FOR OUTPUT AS #FreeFile1& END IF ' do some work CALL ParseInput() ' convert values back VBSession.OutputString = TRIM$(OutputString) IF TRIM$(OutputString) <> "" AND TestInfo = "example" THEN VBSession.Debugger = "OK" END IF ' close log IF PrintInfo >= 5 THEN CLOSE #FreeFile1& END IF EXIT SUB END SUB SUB ParseInput() IF INSTR(1, InputString, " hello ") <> 0 THEN IF TestInfo <> "example" THEN OutputString = "hello to you too." ELSE OutputString = "OK1" END IF ELSEIF INSTR(1, InputString, " byebye ") <> 0 THEN IF TestInfo <> "example" THEN OutputString = "CU." ELSE OutputString = "OK2" END IF END IF END SUB
Sincerely
Jeroen
------------------
Leave a comment:
-
-
Hi Jeroen
(sorry for misspelling your name in my previous post)
As long as you pass variables between subs and functions
these variables are passed on the stack and this is
inherently thread safe as long as you don't mix this
with global/static variables.
Cheers
Florent
------------------
Leave a comment:
-
-
Thanks Florent,
Is it also thread safe to keep passing variables around between subs and functions (instead of making them global) ?
I'll have to look into your example. It'll take some time.... not as easy as you say
Cheers
Jeroen
------------------
Leave a comment:
-
-
hi jerome
tls is useful when retrofitting code to make it thread-safe.
remember that if your application only uses stack variables
(no static or global variables) then it will automatically
be thread safe without having to resort to using tls.
tls is quite simple to use. see
http://www.powerbasic.com/support/pb...ad.php?t=22648 for an example.
if you want to pass a lot of information arounf the best
way to use tls would be to create a type containing the info,
you want to pass around, allocating memory for the type on the
heap and storing the pointer to the type in the tls index.
there are at least tls_minimum_available (64) tls indexes available
per process but it's best to minimize the amount of indexes
you use since dlls your application may link to may also be using
tls.
cheers
florent
[this message has been edited by florent heyworth (edited january 07, 2001).]
Leave a comment:
-
-
multi user dll
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:
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
------------------
Tags: None
-
Leave a comment: