Announcement

Collapse
No announcement yet.

multi user dll

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • jeroen brouwers
    replied
    Florent,

    I've got to do some rewriting but If this works!!

    Thanks a lot already, I'll let you know!

    Cheers
    jeroen

    ------------------

    Leave a comment:


  • Florent Heyworth
    replied
    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
    Cheers

    Florent

    ------------------

    Leave a comment:


  • jeroen brouwers
    replied
    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:


  • Semen Matusovski
    replied
    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:


  • jeroen brouwers
    replied
    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:


  • Lance Edmonds
    replied
    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:


  • jeroen brouwers
    replied
    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
    Then what?

    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:


  • Lance Edmonds
    replied
    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:


  • jeroen brouwers
    replied
    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:


  • Florent Heyworth
    replied
    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:


  • jeroen brouwers
    replied
    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
    Hope someone can help,

    Sincerely
    Jeroen



    ------------------

    Leave a comment:


  • Florent Heyworth
    replied
    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:


  • jeroen brouwers
    replied
    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:


  • Florent Heyworth
    replied
    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:


  • jeroen brouwers
    started a topic multi user dll

    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
    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 :
    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




    ------------------
Working...
X
😀
🥰
🤢
😎
😡
👍
👎