Page 1 of 1

Windowed DLL GUI (+bug ?)

Posted: Sat Feb 07, 2015 7:58 pm
by Ludo
****************************************************************
*** updated with object oriented examples (vtables)
*** added a simple DLL-validation
****************************************************************

Initially, I tried to make an application that was able to run DLLs with GUi elements inside a containergadget.
@Luis : thank for your suggestions and help.
http://www.purebasic.fr/english/viewtop ... =4&t=61559


The last few days, I took a sidestep, and tried to put these in child-windows, which to my :D :D :D seems to works better then hoped for - tought I should share with all. Please forward any feedback and suggestions.

By no means, the code is finished nor polished. In the end the DLLs management should eventually go "object oriented with virtual tables" etc., the main program should query the DLLs for properties and capabilities such required screen size, shared inputs, outputs ... But the basics are there.

Remember : I am not a pro at all, so there may be oddities in there.

The idea is to have a main program that opens DLLs inside windows (when clicking the buttons). The windows as such are under control of the main calling program, the GUI-parts within the assigned windows are handled by the DLLs. The DLLs also have to handle their own gadgets etc ... The DLLs have to follow a minimal set of conventions (required functions e.g.), the main program doesn't want to know anything about the DLLs.

Luis suggested to stay away from the #WM_COMMAND stuff - at the time of write, I'm glad he did :mrgreen:

A couple of weird things occured to me however. @Fred : this is really feels to me like a bug ... :? I would really hope to find an explanation here.
The thing is : the example ddl2 contains a lot of gadgets within their own window. It surprises me that some gadgets such as listviewgadget() report to their own windows, but the buttongadget() some others are not. These are reporting to the main calling program. That might well be lack of understanding, but nevertheless feels like awkward. :?

Eventually, I added a bit of code in the main program that intercepts the gadget events from the child windows, and forwards them back to the originating window. This might be a bit clumsy and way of the beaten tracks, but it seems to work.


The DLLs need few functions (and again I learnt it the hard way : names are case sensitive) :

[*] AttachProcess() : define the global variables here
[*] Create() : initalize the variables and create the GUI elements - only ran once
[*] HandleEvents() : is run every cycle to allow the DLL to handle the local events, and also the ones forwarded by the main program
[*] Run() : is run every cycle to do things - whatever

Please let me know what you think and/or suggestions for improvements.

Below are the demo-programs - running PB 5.31 on Windows 8.

Ludo


Main calling program

Code: Select all

XIncludeFile "WindowedDLLCommon.pbi"

Enumeration
   #MainWindow
   #GLib
   #GObjects
   #GListView
   #GFrameRate
EndEnumeration

Structure DLLInfo
   Name.s
   Library.i
   CreateAddress.i
EndStructure

Global NewMap DLLs.DLLInfo()
Global NewList *Objects.WindowedDLLInterface()

Global tmpDir.i,tmpLib.i
Global libName.s

Procedure OpenDLLs()
Protected lcount,tmpDir,tmpName,tmpLib
tmpDir=ExamineDirectory(#PB_Any,"","*.dll")

;first, examine all the DLLs in the folder
While NextDirectoryEntry(tmpDir)
   libName=DirectoryEntryName(tmpDir)
   tmpLib=OpenLibrary(#PB_Any,libName)
   If tmpLib
      AddMapElement(DLLs(),libName)
      DLLs(libName)\Name=libName
      DLLs(libName)\Library=tmpLib
      
      If GetFunction(tmpLib,"Validate") And CallFunction(tmpLib,"Validate")=#WDLL_Validation
         DLLs(libName)\CreateAddress=GetFunction(tmpLib,"Create") ; remember, so the we can callfunctionfast()
      EndIf
   EndIf
Wend
FinishDirectory(tmpDir)

; then clear out which are invalid
ForEach(DLLs())
   If Dlls()\CreateAddress=0
      CloseLibrary(Dlls()\Library)
      DeleteMapElement(Dlls())
   Else
      lcount=lcount+1
   EndIf
Next

ProcedureReturn lcount
EndProcedure

Procedure MoveWindows()
Protected px=WindowX(#MainWindow),py=WindowY(#MainWindow),npx,npy,wind
*oldelement=@*Objects()  ;remember the current element   
ForEach(*Objects())
   npx=*Objects()\GetP(#WDLL_x)
   npy=*Objects()\GetP(#WDLL_y)
   wind=*Objects()\GetP(#WDLL_Window)
   ResizeWindow(wind,px+npx,py+npy,#PB_Ignore,#PB_Ignore)
Next
ChangeCurrentElement(*Objects(),*oldelement) ; and set it back afterwards - otherwise crash !
EndProcedure

; *************************************
;-*** MAIN PROGRAM STARTS HERE      ***
; *************************************
; Developed by Ludo Roose - free to use
; Thanks to Luis @ Purebasic forum for spending lots of his time
; Thanks to all who shared stuff on the forum
; PB 5.31 - Windows

OpenDLLs()

OpenWindow(#MainWindow,10,10,1400,700,"Windowed DLLs")

ComboBoxGadget(#GLib,10,10,200,22)           ; list DLLs
ForEach(DLLs())
   AddGadgetItem(#GLib,-1,DLLs()\Name)
Next

StringGadget(#GFrameRate,10,40,90,20,"0")    ; list framerate

ListViewGadget(#GListView,10,70,480,60)      ; list local events
AddGadgetItem(#GListView,-1,Str(#GListView))
AddGadgetItem(#GListView,-1,Str(GadgetID(#GListView)))

ObjectCount=0
Loopcount=0
LoopTime=ElapsedMilliseconds()+1000

BindEvent(#PB_Event_MoveWindow,@MoveWindows())

offsx=3
offsy=200

Repeat
   
   If ElapsedMilliseconds()>LoopTime
      SetGadgetText(#GFrameRate,Str(Loopcount))
      LoopTime=ElapsedMilliseconds()+1000
      Loopcount=0
   Else
      Loopcount=Loopcount+1
   EndIf
   
   SomeEvent=#False
   
   ForEach(*Objects())
      *Objects()\HandleEvents(0,0,0)
      *Objects()\Run()
   Next
   
   MainEvent=WindowEvent()
   
   If MainEvent
      MainEventWindow=EventWindow()
      MainGadget=EventGadget()
      MainEventType=EventType()
      
      If MainEventWindow<>#MainWindow
         ForEach(*Objects()) ; could  be done with a map too ? maybe faster ?
            If *Objects()\GetP(#WDLL_Window)=MainEventWindow
               AddGadgetItem(#GListView,0,"FWD  >>> "+Str(MainEventWindow)+" "+Str(MainEvent)+" "+Str(MainGadget)+" "+Str(MainEventType))
               *Objects()\HandleEvents(MainEvent,MainGadget,MainEventType)
               Break
            EndIf
         Next
      Else
         If MainEvent=#PB_Event_Gadget
            AddGadgetItem(#GListView,0,"MAIN >>> "+Str(MainEventWindow)+" "+Str(MainEvent)+" "+Str(MainGadget)+" "+Str(MainEventType))
         EndIf
      EndIf
      
      If MainEvent=#PB_Event_Gadget
         Select EventGadget()
            Case #GLib
               
               ObjectCount=ObjectCount+1
               tmpLibname.s=GetGadgetText(#GLib)
               tmpName.s=tmpLibname+" "+Str(ObjectCount)
               
               AddElement(*Objects())
               
               tmpWin=OpenWindow(#PB_Any,WindowX(#MainWindow)+offsx,WindowY(#MainWindow)+offsy,50,50,"test",#PB_Window_BorderLess,WindowID(#MainWindow))
               ButtonGadget(#PB_Any,1,1,15,15,"°")
               
               tmpWinID=WindowID(tmpWin)
               *Objects()=CallFunction(DLLs(tmpLibName)\Library,"Create",@tmpName,tmpWin,tmpWinID)
               *Objects()\SetP(#WDLL_Window,tmpWin)
               *Objects()\SetP(#WDLL_WindowID,tmpWindID)
               *Objects()\SetP(#WDLL_x,offsx)
               *Objects()\SetP(#WDLL_y,offsy)
               ResizeWindow(tmpWin,#PB_Ignore,#PB_Ignore,*objects()\GetP(#WDLL_Width),*Objects()\GetP(#WDLL_Height))
               offsx=offsx+*objects()\GetP(#WDLL_Width)+1
               
         EndSelect
      EndIf
   EndIf
   
Until MainEvent=#PB_Event_CloseWindow

CloseWindow(#MainWindow)



Include file that goes in all : WindowedDLLCommon.pbi

Code: Select all

Enumeration
   #WDLL_Name
   #WDLL_Window
   #WDLL_WindowID
   #WDLL_x
   #WDLL_y
   #WDLL_Width
   #WDLL_Height
   ; more to come here ...
EndEnumeration
#WDLL_Validation=3256680939 ; any random number

Structure WindowedDLL ; basic structure, custom DLLs must extend these
   Vtable.i
   name.s{32}
   Window.i
   WindowID.i
   x.i
   y.i
   Width.i
   Height.i
   thumbWidth.i
   thumbHeight.i
   minWidth.i
   minHeight.i
   Variables.i
EndStructure

Interface WindowedDLLInterface ; must be defined for every DLL
   Create(*Name,Window,WindowID)
   Run()
   HandleEvents(ExtEvent,ExtGadget,ExtEventType)
   Get()
   Set()
   GetP(Item)
   SetP(Item,Value,Value2=0)   
EndInterface

ProcedureDLL SetP(*This.WindowedDLL,Item,Value,Value2=0) 
; set private properties of the window
; only to be used for stuff that is common to all DLLs
Select Item
   Case #WDLL_Window
      *This\Window=Value
   Case #WDLL_WindowID
      *This\WindowID=Value
   Case #WDLL_x
      *This\x=Value
   Case #WDLL_y
      *This\y=Value
   Default
      ProcedureReturn 0
EndSelect
EndProcedure

ProcedureDLL GetP(*This.WindowedDLL,Item) 
; get private properties of the window
; only to be used for stuff that is common to all DLLs
Select Item
   Case #WDLL_Window
      ProcedureReturn *This\Window
   Case #WDLL_WindowID
      ProcedureReturn *This\WindowID
   Case #WDLL_x
      ProcedureReturn *This\x
   Case #WDLL_y
      ProcedureReturn *This\y
   Case #WDLL_Width
      ProcedureReturn *This\Width
   Case #WDLL_Height
      ProcedureReturn *This\Height
   Default
      ProcedureReturn 0
EndSelect
EndProcedure

ProcedureDLL Validate() 
; make sure the DLL is compliant 
ProcedureReturn #WDLL_Validation
EndProcedure

minimal framework dll

Code: Select all

EnableExplicit
XIncludeFile "WindowedDLLCommon.pbi"

Structure FrameWorkWindowedDLL Extends WindowedDLL ; always build a local structure that extends on WindowedDLL

;custom code from here
   Gadget.i ;*** 
;to here

EndStructure

ProcedureDLL AttachProcess(Instance)

Global *this.FrameWorkWindowedDLL ; make sure to create a global object

EndProcedure

ProcedureDLL Create(*Name,Window,WindowID)
Protected prevID=UseGadgetList(WindowID)

*this.FrameWorkWindowedDLL=AllocateMemory(SizeOf(FrameWorkWindowedDLL))
*this\Vtable=?WindowedDLLTable
*this\name=PeekS(*name)
*this\Width=80
*this\Height=70

;custom GUI building and initalization code from here
   *this\Gadget=StringGadget(#PB_Any,5,30,70,20,"framework")
   FrameGadget(#PB_Any,0,0,*this\Width,*this\Height,"",#PB_Frame_Flat)
;to here

UseGadgetList(prevID) 
ProcedureReturn *this
EndProcedure

ProcedureDLL Run(*this.FrameWorkWindowedDLL)
; put code here that needs to run every cycle
EndProcedure

ProcedureDLL HandleEvents(*this.FrameWorkWindowedDLL,ExtEvent,ExtGadget,ExtEventType)
If Not ExtEvent ; nothing was passed from the main program
   ExtEvent=WindowEvent()
   ExtGadget=EventGadget()
   ExtEventType=EventType()
EndIf

; put code here to handle local events 

EndProcedure

ProcedureDLL Get(*this.FrameWorkWindowedDLL)
; put code here the retrieve information from your object
EndProcedure 

ProcedureDLL Set(*this.FrameWorkWindowedDLL)
; put code here that sets information in your object
EndProcedure

DataSection
   WindowedDLLTable:
   Data.i @Create()
   Data.i @Run()
   Data.i @HandleEvents()
   Data.i @Get()
   Data.i @Set()
   Data.i @GetP()
   Data.i @SetP()
EndDataSection

OpenGL example (rework of Purebasic demoprogram)

Code: Select all

EnableExplicit
XIncludeFile "WindowedDLLCommon.pbi"

Structure GLWindowedDLL Extends WindowedDLL
   
   GOpenGL.i
   
   RollAxisX.f
   RollAxisY.f
   RollAxisZ.f
   
   RotateSpeedX.f
   RotateSpeedY.f
   RotateSpeedZ.f 
   
   ZoomFactor.f  ; Distance of the camera. Negative value = zoom back
   
   GZoomIn.i
   GZoomOut.i
   NextRun.i
   
EndStructure

ProcedureDLL AttachProcess(Instance)
Global *this.GLWindowedDLL
EndProcedure


ProcedureDLL Run(*This.GLWindowedDLL)
If ElapsedMilliseconds()>*this\NextRun
   SetGadgetAttribute(*this\GOpenGL, #PB_OpenGL_SetContext, #True)
   
   glPushMatrix_()                  ; Save the original Matrix coordinates
   glMatrixMode_(#GL_MODELVIEW)
   
   glTranslatef_(0, 0, *this\ZoomFactor)  ;  move it forward a bit
   
   glRotatef_ (*this\RollAxisX, 1.0, 0, 0) ; rotate around X axis
   glRotatef_ (*this\RollAxisY, 0, 1.0, 0) ; rotate around Y axis
   glRotatef_ (*this\RollAxisZ, 0, 0, 1.0) ; rotate around Z axis
   
   *this\RollAxisX + *this\RotateSpeedX 
   *this\RollAxisY + *this\RotateSpeedY 
   *this\RollAxisZ + *this\RotateSpeedZ 
   
   ; clear framebuffer And depth-buffer
   
   glClear_(#GL_COLOR_BUFFER_BIT | #GL_DEPTH_BUFFER_BIT)
   
   ; draw the faces of a cube

   ; draw colored faces
   
   glDisable_(#GL_LIGHTING)
   glBegin_(#GL_QUADS)
   
   ; Build a face, composed of 4 vertex ! 
   ; glBegin() specify how the vertexes are considered. Here a group of
   ; 4 vertexes (GL_QUADS) form a rectangular surface.
   
   ; Now, the color stuff: It's r,v,b but with float values which
   ; can go from 0.0 To 1.0 (0 is .. zero And 1.0 is full intensity) 
   
   glNormal3f_(0,0,1.0)
   glColor3f_(0,0,1.0)
   glVertex3f_(0.5,0.5,0.5)   
   glColor3f_(0,1.0,1.0)         
   glVertex3f_(-0.5,0.5,0.5)
   glColor3f_(1.0,1.0,1.0)
   glVertex3f_(-0.5,-0.5,0.5)
   glColor3f_(0,0,0)
   glVertex3f_(0.5,-0.5,0.5) 
   
   ; The other face is the same than the previous one 
   ; except the colour which is nice blue To white gradiant
   
   glNormal3f_(0,0,-1.0)
   glColor3f_(0,0,1.0)
   glVertex3f_(-0.5,-0.5,-0.5)
   glColor3f_(0,0,1.0)
   glVertex3f_(-0.5,0.5,-0.5)
   glColor3f_(1.0,1.0,1.0)
   glVertex3f_(0.5,0.5,-0.5)
   glColor3f_(1.0,1.0,1.0)
   glVertex3f_(0.5,-0.5,-0.5)
   
   glEnd_()
   
   ; draw shaded faces
   
   glEnable_(#GL_LIGHTING)
   glEnable_(#GL_LIGHT0)
   glBegin_ (#GL_QUADS)
   
   glNormal3f_(   0, 1.0,   0)
   glVertex3f_ ( 0.5, 0.5, 0.5)
   glVertex3f_ ( 0.5, 0.5,-0.5)
   glVertex3f_ (-0.5, 0.5,-0.5)
   glVertex3f_ (-0.5, 0.5, 0.5)
   
   glNormal3f_ (0,-1.0,0)
   glVertex3f_ (-0.5,-0.5,-0.5)
   glVertex3f_ (0.5,-0.5,-0.5)
   glVertex3f_ (0.5,-0.5,0.5)
   glVertex3f_ (-0.5,-0.5,0.5)
   
   glNormal3f_ (1.0,0,0)
   glVertex3f_ (0.5,0.5,0.5)
   glVertex3f_ (0.5,-0.5,0.5)
   glVertex3f_ (0.5,-0.5,-0.5)
   glVertex3f_ (0.5,0.5,-0.5)
   
   glNormal3f_ (-1.0,   0,   0)
   glVertex3f_ (-0.5,-0.5,-0.5)
   glVertex3f_ (-0.5,-0.5, 0.5)
   glVertex3f_ (-0.5, 0.5, 0.5)
   glVertex3f_ (-0.5, 0.5,-0.5)
   
   glEnd_()
   
   glPopMatrix_()
   glFinish_()
   
   SetGadgetAttribute(*this\GOpenGL, #PB_OpenGL_FlipBuffers, #True)
   
   *this\NextRun=ElapsedMilliseconds()+1000/60-1 ; about 60 Hz = framerate of display
EndIf

EndProcedure

Procedure SetupGL()
glMatrixMode_(#GL_PROJECTION)
gluPerspective_(30.0, 200/200, 1.0, 10.0) 

; position viewer
glMatrixMode_(#GL_MODELVIEW)
glTranslatef_(0, 0, -5.0)
glEnable_(#GL_DEPTH_TEST)   ; Enabled, it slowdown a lot the rendering. It's to be sure than the
                            ; rendered objects are inside the z-buffer.

glEnable_(#GL_CULL_FACE)    ; This will enhance the rendering speed as all the back face will be
                            ; ignored. This works only with CLOSED objects like a cube... Singles
                            ; planes surfaces will be visibles only on one side.

glShadeModel_(#GL_SMOOTH)
EndProcedure

ProcedureDLL HandleEvents(*This.GLWindowedDLL,ExtEvent,ExtGadget,ExtEventType)
If Not ExtEvent ; nothing was passed from the main program
   ExtEvent=WindowEvent()
   If ExtEvent=#PB_Event_Gadget
      ExtGadget=EventGadget()
      ExtEventType=EventType()
   EndIf
EndIf

If ExtEvent=#PB_Event_Gadget
   If ExtGadget=*this\GZoomIn
      *this\ZoomFactor=*this\ZoomFactor/0.7
      ProcedureReturn #True
   ElseIf ExtGadget=*this\GZoomOut
      *this\ZoomFactor=*this\ZoomFactor*0.7
      ProcedureReturn #True  
   EndIf
EndIf

ProcedureReturn #False
EndProcedure

ProcedureDLL Create(*Name,Window,WindowID)
Protected previd=UseGadgetList(WindowID)

*This.GLWindowedDLL=AllocateMemory(SizeOf(GLWindowedDLL))
*This\Vtable=?WindowedDLLTable
*This\name=PeekS(*name)

*this\NextRun=ElapsedMilliseconds()+14

*this\RollAxisX.f=0
*this\RollAxisY.f=0
*this\RollAxisZ.f=0

*this\RotateSpeedX.f = 1.0
*this\RotateSpeedY.f = 0
*this\RotateSpeedZ.f = 1.0
*this\ZoomFactor.f = 1.0 ; Distance of the camera. Negative value = zoom back

SetWindowColor(Window,Random($FFFFFF)) ; just for the fun

*this\GOpenGL=OpenGLGadget(#PB_Any,25,25,200,200)
SetupGL()
*this\GZoomIn=ButtonGadget(#PB_Any,10,240,50,20,"+")
*this\GZoomOut=ButtonGadget(#PB_Any,70,240,50,20,"-")
*this\Width=240
*this\Height=270

UseGadgetList(previd)
ProcedureReturn *This
EndProcedure

ProcedureDLL Get(*This.GLWindowedDLL)
EndProcedure

ProcedureDLL Set(*This.GLWindowedDLL)
EndProcedure

DataSection
   WindowedDLLTable:
   Data.i @Create()
   Data.i @Run()
   Data.i @HandleEvents()
   Data.i @Get()
   Data.i @Set()
   Data.i @GetP()
   Data.i @SetP()
EndDataSection


Example with just a log of gadgets

Code: Select all

EnableExplicit
XIncludeFile "WindowedDLLCommon.pbi"

Structure Dll2WindowedDLL Extends WindowedDLL
   GList.i
   GComboBox.i
   GButton.i
   GCanvas.i
   GCheckBox.i
   GImage.i
   IImage.i 
   GSpin.i
   delayTime.i
   nextUpdate.i
EndStructure

; make these variables global for the DLL, but don't assign values
Procedure AttachProcess(instance)   
Global *this.Dll2WindowedDLL
EndProcedure

; just to report that the "globals" are visible
ProcedureDLL ReportThings(*this.Dll2WindowedDLL)
AddGadgetItem(*this\GList,-1,"------------------")
AddGadgetItem(*this\GList,-1,Str(*this\GButton))
AddGadgetItem(*this\GList,-1,Str(*this\GCanvas))
AddGadgetItem(*this\GList,-1,Str(*this\GCheckBox))
AddGadgetItem(*this\GList,-1,Str(*this\GImage))   
AddGadgetItem(*this\GList,-1,Str(*this\delayTime))
AddGadgetItem(*this\GList,-1,Str(*this\nextUpdate))
EndProcedure

; try to handle events
ProcedureDLL HandleEvents(*this.Dll2WindowedDLL,ExtEvent,ExtGadget,ExtEventType)   
If Not ExtEvent ; nothing was passed from the main program
   ExtEvent=WindowEvent()
   If ExtEvent=#PB_Event_Gadget
      ExtGadget=EventGadget()
      ExtEventType=EventType()
   EndIf
EndIf

If ExtEvent=#PB_Event_Gadget
   Select ExtGadget
      Case *this\GComboBox
         AddGadgetItem(*this\GList,0,"lib "+GetGadgetText(*this\GComboBox))
         ProcedureReturn #True
      Default
         AddGadgetItem(*this\GList,0,"lib "+Str(ExtEvent)+" "+Str(ExtGadget)+" "+Str(ExtEventType))
         ProcedureReturn #True
   EndSelect
EndIf
ProcedureReturn #False
EndProcedure 

; the "run" will be called every windows cycle, as a sort of "callback()"
; you can do things here
ProcedureDLL Run(*this.Dll2WindowedDLL)
If ElapsedMilliseconds()>*this\nextUpdate
   SetGadgetState(*this\GSpin,GetGadgetState(*this\GSpin)+1)
   *this\nextUpdate=*this\nextUpdate+*this\delayTime
EndIf
EndProcedure

; build the GUI, initialize variables
ProcedureDLL Create(*Name,Window,WindowID)
Protected previd

previd=UseGadgetList(WindowID)

*this.Dll2WindowedDLL=AllocateMemory(SizeOf(Dll2WindowedDLL))
*this\Vtable=?WindowedDLLTable
*this\name=PeekS(*name)

*this\delayTime=200+Random(5000)
*this\nextUpdate=ElapsedMilliseconds()

*this\Width=390
*this\Height=480

SetWindowColor(Window,Random($FFFFFF))

*this\GComboBox=ComboBoxGadget(#PB_Any,10,10,370,25)   
AddGadgetItem(*this\GComboBox,-1,Str(*this\GComboBox)+":"+Str(GadgetID(*this\GComboBox)))
AddGadgetItem(*this\GComboBox,-1,Str(WindowID))
SetGadgetState(*this\GComboBox,1)

*this\GCanvas=CanvasGadget(#PB_Any,10,40,370,50,#PB_Canvas_Border)
StartDrawing(CanvasOutput(*this\GCanvas))
DrawText(0,0,Str(*this\GCanvas)+":"+Str(GadgetID(*this\GCanvas)))
StopDrawing()

*this\GButton=ButtonGadget(#PB_Any,10,100,200,20,"")
SetGadgetText(*this\GButton,Str(*this\GButton)+":"+Str(GadgetID(*this\GButton)))

*this\GCheckBox=CheckBoxGadget(#PB_Any,220,100,150,20,"CBG")

*this\GList=ListViewGadget(#PB_Any,10,140,370,200)
AddGadgetItem(*this\GList,-1,Str(*this\GList))
AddGadgetItem(*this\GList,-1,Str(GadgetID(*this\GList)))

*this\GImage=ImageGadget(#PB_Any,10,350,370,20,0)
*this\IImage=CreateImage(#PB_Any,370,20)
StartDrawing(ImageOutput(*this\IImage))
DrawText(0,0,Str(*this\GImage)+":"+Str(GadgetID(*this\GImage))+" < "+Str(*this\IImage)+":"+Str(ImageID(*this\IImage)))
StopDrawing()
SetGadgetState(*this\GImage,ImageID(*this\IImage))

*this\GSpin=SpinGadget(#PB_Any,10,400,200,20,0,1000,#PB_Spin_Numeric)
SetGadgetState(*this\GSpin,0)

UseGadgetList(previd)

ProcedureReturn *this

EndProcedure 

ProcedureDLL Get(*This.WindowedDLL)
EndProcedure 

ProcedureDLL Set(*This.WindowedDLL)
EndProcedure

DataSection
   WindowedDLLTable:
   Data.i @Create()
   Data.i @Run()
   Data.i @HandleEvents()
   Data.i @Get()
   Data.i @Set()
   Data.i @GetP()
   Data.i @SetP()
EndDataSection