ThreadSafe returning stringss from DLL

Share your advanced PureBasic knowledge/code with the community.
jassing
Addict
Addict
Posts: 1885
Joined: Wed Feb 17, 2010 12:00 am

ThreadSafe returning stringss from DLL

Post by jassing »

This is how I did it... not saying it's the best, but it has been fairly well tested and I haven't run into any problems...
tested with win10 x64, pb6x64
An improvement would be to use threads instead of timers, to make it cross-platform...

Code: Select all

; updated 2023-04-22
;    Use a mutex to update buffer map to make it thread-safe.
;
; If your DLL already has a "DetachThread", "DetachProcess",  or  "AttachProcess" 
;            rename them To "DetachThread2","DetachProcess2", and "AttachProcess2"
;            You must Declare them prior to including the ProcedureDLLReturnString.pbi file.
;            that way, we can clean up memory before releasing the dll & call your functions after that.
;
; Example shows how I handle returning strings from a dll in a threadsafe way.
; the dll reverses the string; so the program reverses that, and we get the original string
; just a simple test to demonstrate...

EnableExplicit
CompilerIf #PB_Compiler_ExecutableFormat=#PB_Compiler_DLL
  Macro ProcedureDLLReturnString( str )
    ProcedureReturn ReturnDLLStringPtr( str )
  EndMacro
  
  #UseTimerToCleanUp = #True 
  #pdrsTimeout = 60000 ; use the return buffer w/in 1 minute otherwise it will be cleared.
  
  CompilerIf #UseTimerToCleanUp
    Import "kernel32.lib"
      CreateTimerQueue()
      CreateTimerQueueTimer(  hTimer, hTimerQueue, timerCallback, param, dueTime, period, flags)
      ChangeTimerQueueTimer(  hTimerQueue, hTimer, dueTime, period)
      DeleteTimerQueueTimer(  hTimerQueue, hTimer, completionEvent)
      DeleteTimerQueueEx(     hTimerQueue, completionEvent)
    EndImport
    Global pdrsTimerQueue
    
    ProcedureDLL pdrsChangeTimer( newInterval )
      ProcedureReturn ChangeTimerQueueTimer(pdrsTimerQueue, 1, newInterval, newInterval)
    EndProcedure 
  CompilerEndIf 
  
  Structure pdspStruct
    *ptr
    time.i
  EndStructure
  
  Global pdrsReturnBufferMutex = CreateMutex()
  Global NewMap pdrsReturnBuffer.pdspStruct()
  
  Procedure ReturnDLLStringPtr( str.s )
    Protected key.s = Str(GetCurrentThreadId_())
    LockMutex(pdrsReturnBufferMutex)
    
    If FindMapElement( pdrsReturnBuffer(), key) 
      pdrsReturnBuffer( key )\ptr = ReAllocateMemory(pdrsReturnBuffer(key)\ptr,StringByteLength(str)+2)
    Else 
      pdrsReturnBuffer( key )\ptr = AllocateMemory(StringByteLength(str)+SizeOf(Character))
    EndIf
    pdrsReturnBuffer( key )\time = ElapsedMilliseconds()
    PokeS(pdrsReturnBuffer( key )\ptr, str, StringByteLength(str))
    
    UnlockMutex(pdrsReturnBufferMutex)
    ProcedureReturn( pdrsReturnBuffer(key)\ptr )
  EndProcedure
  
  Procedure pdrsCleanMap(param.i, timer.i)
    ;  OutputDebugString_("Cleanup timer fired")
    LockMutex(pdrsReturnBufferMutex)
    ForEach pdrsReturnBuffer()
      If pdrsReturnBuffer()\time + #pdrsTimeout > ElapsedMilliseconds()
        DeleteMapElement( pdrsReturnBuffer() )
      EndIf
    Next
    UnlockMutex(pdrsReturnBufferMutex)
  EndProcedure
  
  Procedure pdrsClear(n=0)
    Protected i, id.s = Str(GetCurrentThreadId_())
    LockMutex(pdrsReturnBufferMutex)
    If FindMapElement( pdrsReturnBuffer(), id )
      FreeMemory(pdrsReturnBuffer(id)\ptr)
      DeleteMapElement( pdrsReturnBuffer(), id )
      i=1
    EndIf
    UnlockMutex(pdrsReturnBufferMutex)
    ProcedureReturn i
  EndProcedure
  
  ;- Exported procedures 
  ProcedureDLL AttachProcess( n ) 
    CompilerIf #UseTimerToCleanUp
      Protected timer
      pdrsTimerQueue = CreateTimerQueue()
      CreateTimerQueueTimer(@timer, pdrsTimerQueue, @pdrsCleanMap(), 1, 5000, 5000, 0)
    CompilerEndIf   
    CompilerIf Defined(AttachProcess2,#PB_Procedure)
      AttachProcess2( n ) 
    CompilerEndIf
  EndProcedure
  ProcedureDLL DeteachThread(n) 
    pdrsClear()
    
    CompilerIf Defined( DetachThread2, #PB_Procedure )
      DetachThread2( n )
    CompilerEndIf
  EndProcedure
  ProcedureDLL DetachProcess(n) 
    CompilerIf #UseTimerToCleanUp
      DeleteTimerQueueTimer( pdrsTimerQueue,1,0)
      DeleteTimerQueueEx( pdrsTimerQueue, 0)
    CompilerEndIf 
    LockMutex(pdrsReturnBufferMutex)
    ForEach pdrsReturnBuffer() 
      FreeMemory( pdrsReturnBuffer()\ptr )
    Next
    ClearMap( pdrsReturnBuffer() )
    UnlockMutex(pdrsReturnBufferMutex)
    CompilerIf Defined( DetachProcess2, #PB_Procedure)
      DetachProcess2( n )
    CompilerEndIf
  EndProcedure
  
  ;}
  
  ;XIncludeFile "ProcedureDLLReturnString.pbi"
  
  ;- Sample DLL procedure to return a string, demonstration only...
  ProcedureDLL RevStr_( str.s )
    Protected retstr.s = ReverseString( str )
    ; Using a global with ProcedureReturn is not threadsafe.
    ProcedureDLLReturnString( retstr )
  EndProcedure
  
CompilerElse ;- test program
  Import "DLLStringTest.lib"
    RevStr_( str.s )
  EndImport
  
  Global s = CreateSemaphore(0) ; Use a semaphore to get threads to execute as close to the same time as possible.
  
  Procedure ThreadedTest( *str )
    WaitSemaphore(s)
    Debug "Reverse "+PeekS(RevStr_( PeekS(*str) ))
    Debug "Normal "+ReverseString(PeekS(RevStr_( PeekS(*str) )))
  EndProcedure
  
  Define t1, t2, t3
  Define *p
  
  *p = revstr_("1 I am there") 
  Debug ReverseString(PeekS(*p))
  
  *p = revstr_("2 you are here")
  Debug ReverseString(PeekS(*p))
  
  t1 = CreateThread(@ThreadedTest(),@"3 Here is a test")
  t2 = CreateThread(@ThreadedTest(),@"4 Here is another test")
  t3 = CreateThread(@ThreadedTest(),@"5 Here is the last one test")
  
  Delay(10) 
  SignalSemaphore(s): SignalSemaphore(s) : SignalSemaphore(s)
  
  Repeat ;- Wait for threads to complete.
    Delay(5)
  Until IsThread(t1) = #False And IsThread(t2)=#False And IsThread(t3)=#False
CompilerEndIf 

; IDE Options = PureBasic 6.02 beta 2 LTS (Windows - x64)
; ExecutableFormat = Shared dll
; CursorPosition = 10
; Folding = BaB1-
; Optimizer
; EnableThread
; EnableXP
; Executable = DLLStringTest.dll
; EnablePurifier
; EnableExeConstant
Last edited by jassing on Mon Apr 24, 2023 10:50 am, edited 3 times in total.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5526
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: ThreadSafe returning stringss from DLL

Post by Kwai chang caine »

I don't know if it's the good result of your nice code :oops:
ereht ma I 1
ereh era uoy 2
tset rehtona si tset rehtona si ereH 4tset eno tsal eht si ereH 5
eno tsal eht si ereH 5
tset rehtona si ereH 4tset eno tsal eht si ereH 5
If yes...that works here :wink:
Thanks a lot for this safe example, i need often it 8)
ImageThe happiness is a road...
Not a destination
Fred
Administrator
Administrator
Posts: 18390
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: ThreadSafe returning stringss from DLL

Post by Fred »

It doesn't look good, the map write access are not thread safe, so you need a mutex around it.
jassing
Addict
Addict
Posts: 1885
Joined: Wed Feb 17, 2010 12:00 am

Re: ThreadSafe returning stringss from DLL

Post by jassing »

Fred wrote: Mon Apr 24, 2023 9:21 am It doesn't look good, the map write access are not thread safe, so you need a mutex around it.
Thanks for confirming that; I'll update the code here, I did that here after discovering a problem and suspected that might be the issue...
jassing
Addict
Addict
Posts: 1885
Joined: Wed Feb 17, 2010 12:00 am

Re: ThreadSafe returning stringss from DLL

Post by jassing »

Kwai chang caine wrote: Mon Apr 24, 2023 9:17 am
ereht ma I 1
You should have seen the "normal" text - as the DLL reverses the string, then the program re-reverses it.
What PB version are you using? OS?

I only tested on PB 6 x64 running on windows.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5526
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: ThreadSafe returning stringss from DLL

Post by Kwai chang caine »

I use v5.73 LTS (X86) :oops:

DLLStringTest.pb

Code: Select all

ProcedureDLL.s RevStr_(str.s)
 ProcedureReturn str
EndProcedure
ImageThe happiness is a road...
Not a destination
Post Reply