Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

DarkBASIC Professional Discussion / DBPro Inside a mini tutorial for running DBPro app inside .net (VB in this case) using file mapping for comms.

Author
Message
jasuk70
21
Years of Service
User Offline
Joined: 3rd Dec 2002
Location: Hemel Hempstead
Posted: 4th Jan 2006 01:13 Edited at: 4th Jan 2006 02:02
Tutorial on Using a DBPro program inside a .NET application

This is a small tutorial which goes through the basics launching a DBPro program from inside a .NET Application.
In this case it is a VB.NET 2005 Express edition program. And the source code provided is for VB .NET 2005 express.
This can be downloaded from Microsoft's web site currently for free using This Link
If you just want to try the compiled code version of this example you will need to have .NET Framework 2.0 (Installing the VB Express will also install the framework)

Another thing this relies upon is memory mapped files which was made available in the DBPro Enhancement Pack. Hence this is required to run the DBPro code as well. (Though the compiled code in the attached file will probably work)

Due to the fact that memory mapped files are not native to .NET, I have used this code from the Microsoft’s MSDN website to handle the file mapping.

Phew, now all that is over I'll star by going through the Dark Basic Pro program which we will launch inside a .net application. The program is included inside the attached zip file. Go into the DBProInsideDBProInsidebinDebug directory to find this.

DBProInside.dba

First we need to setup constants to make reading the code easier, in this example we only use the one for now.

#CONSTANT Msg_WindowSize 1


The heart beat: This is a way we can make sure the .NET application and this application know that they are both alive and kicking. The way this works is; this application will set HeartBeat mapped file to Tock, and then we set a variable with the current time and wait till we see the HeartBeat file get reset to Tick. If it doesn’t get reset to Tick before 10 seconds have elapsed then we thing there is a problem and shut down silently.

So here we open the heart beat file (Created by the .NET application). Also setup the variable that holds the heartbeat time AND then set the initial time.

Open File Map 1, "dbpvb_HeartBeat"
GLOBAL lastHeartBeat AS INTEGER
lastHeartBeat=TIMER()

Mouse positions: When running inside a .net application all input via normal methods does not work. So what we need to do is pass the relevant information from the .net application into this application. The main thing we are interested in is the mouse position. And as this information only needs to go one way (From .net to DBP) we will create a dedicated channel for this. The MouseReady channel we will use just to make sure we do not read the mouse coordinates half way through them being written. While the .net app is sending these coordinates it sets the Ready to 0 and then sets the ready to 1 when it has finished. The .net application doesn't wait to see if we have picked up the coordinates as this isn't really important, as long as we have the current up-to-date information we are not interested in every single movement of the mouse.

So here we open the mouse and mouse ready mapped files and also set up the global variables to hold the mouse coordinates. Then set these variables to the default values.

Open File Map 2, "dbpvb_MouseReady"
Open File Map 3, "dbpvb_Mouse"
GLOBAL Mx AS INTEGER
GLOBAL My AS INTEGER
Mx=0
My=0

General message data: We have the mouse and heartbeat information set up. Next we need a way of receiving messages from the .net app to do other things. We handle this in a similar way to the mouse information, but this time we are interested in receiving every message that comes through so we need to implement a kind of hand shaking. When the .net application has set a message, it will then set the SendDataReady file to 1 and will not try to send any more messages until that file is set to 0. This end we process the message and then set the file to 0 to signify that we have finished with the message. If the .net app has any more messages it will set the message and then set the ready file back to 1. We will ignore the file until the ready file is set back to 1.

So here we open two send data mapped files. We also setup a last message variable so we can display debug information on screen of the last message we received.

Open File Map 4, "dbpvb_SendDataReady"
Open File Map 5, "dbpvb_SendData"
GLOBAL lastMessage AS STRING
lastMessage=""


Now comes the Main Loop. This controls everything this program does. First it runs the heart beat check routine, then the mouse coordinate fetch routine and finally processes any waiting messages,

do
` First we check the heart beat
HeartBeat()
` Then we update the mouse x and y
GetMouseCoords()
` Finally process any messages we receive from the .net app.
ProcessMessage()
loop

Next we have the function that will handle the heart beat. This function will first store the heart beat file contents into a temporary string, check for a shutdown signal, then check to see if the contents is set to Tick, if it is, then we reply with a Tock value, if not then we check to see if it's been 10 seconds or more since we last received a heart beat signal from the .net application. If it is over 10 seconds then we shut down the DBPro program as we don’t want to leave this program running if the .net app has stop responding or closed for some reason. The last thing this routine does is display some debug info so we get feedback in the .net view of the DBPro program.

function HeartBeat()
LOCAL beat AS STRING
` Get the heart beat value
beat=Get File Map String$(1,0)
` First a quick check to see if we have been
` signaled to close down.
if beat="Dead"
END
endif
` Check to see if the heart beat has changed
if beat="Tick"
` Great the heart beat has changed. So we reset
` the beat back to Tock and then reset the timer.
Set File Map String$ 1,0,"Tock"
lastHeartBeat=TIMER()
else
` The heart beat hasn't changed so we check to
` see if 10 seconds has passed (Timer function
` works in 1000'th of a second so 10 seconds is
` 10,000)
if (TIMER()-lastHeartBeat)>10000
` Oh dear we have lost contact with the .NET application
` better shut down.
END
endif
endif
` Display debug information on the screen
SET TEXT OPAQUE
SET CURSOR 0,0
PRINT "X=";Mx;", Y=";My;" "
PRINT "Last Message=";lastMessage;" "
endfunction

This next function will get the mouse coordinates from the .net application. This will first check to see if the Mouse Ready is set to 1, if it is, then it will copy the contents of the mouse coordinates into a temporary string variable in its raw text format. Then we use a function to get the different components of the string (I.e. x and y values) from the string using the "," as a delimiter. Then update the global mouse coordinates which will get displayed as part of the heart beat routine.

function GetMouseCoords()
` Declare a temporary string to store the raw information
LOCAL crds AS STRING

` now we check that the Mouse Data is valid.
if MouseReady()=1
` It is so we get the coordinates in their raw form
crds=Get File Map String$(3,0)
` We get the values from raw data and turn them into
` integers, The raw data is the numbers in string format
` separated by a ",". The Field function will extract
` the relevant data field. Then we update the global
` variables with the coordinates.
Mx=VAL(field(crds,",",1))
My=VAL(field(crds,",",2))
endif
endfunction

This function will get the Mouse Ready flag from the .net application. And return the result. It can be either 1 or 0

function MouseReady()
LOCAL Result AS INTEGER
Result=Get File Map DWORD(2,0)
endfunction Result

This function will get the Data Ready flag from the .net application and return the result. It can also be either 1 or 0

function RecieveDataReady()
LOCAL result AS INTEGER
result=Get File Map DWORD(4,0)
endfunction result

This function is used to signify that we have received the last message sent by the .net application by setting the Data Ready flag to 0.

function MessageRecieved()
Set File Map DWORD 4,0,0
endfunction

This function is used to Process the Messages from .net application. This function could get quite large if there are lots of different types of messages, so we only use it to work out what type of message we have and then pass the raw data to a handling function. In this first instance we are only sending a window re-size message.
First it will check to see if we have the Data Ready flag set, if it is then we get the data from this message mapped file. We then extract the message type, This will be a number so we convert the string number into an integer. The delimiter between the message type and the message detail is a "|" character so we use the field function again to extract the relevant information. We then check what the message type is and call the relevant routine to process the data.

function ProcessMessage()
` Setup the temporary vars for this function
LOCAL aMessage AS STRING
LOCAL messageType AS INTEGER
LOCAL messageData AS STRING

` Firstly we are checking to see if we have a message to process
if RecieveDataReady()=1
` Great we do have one, lets get the raw message
aMessage=Get File Map String$(5,0)
` Now we extract the message type, This will be a number so we
` convert the string number into an integer. The delimiter between
` the message type and the message detail is a "|" character so
` we use the field function again to extract the relevant information.
messageType=VAL(field(aMessage,"|",1))
` now we extract the message data ready to pass onto the handling
` function.
messageData=field(aMessage,"|",2)
` Right lets see what message type we have (All the message numbers
` have constants to make the reading of the program easier.
select messageType
case Msg_WindowSize
` We have the window Size has changed message so lets
` resize ourselves to match.
ResizeWindow(messageData)
endcase
endselect
` we have now processes our message so lets tell the .net app this.
MessageRecieved()
endif
endfunction

This is our function that works out the new window size from the message data and then resizes this app to match. We also set the last message string so this will get displayed in the next heart beat routine.

function ResizeWindow(msg AS STRING)
LOCAL width AS INTEGER
LOCAL height AS INTEGER

` Extract the width and height
width=VAL(field(msg,",",1))
height=VAL(field(msg,",",2))

` now we change the size of this application
SET DISPLAY MODE width, height, 32

` and set the last message variable to we know what has just happened.
LastMessage="Window Resize "+STR$(width)+","+STR$(height)+",32"
endfunction

Finally, this is the Field Function that will look through the source string for the delimiter character and return the text between the two delimiters.

function Field(sourceString AS STRING, delimiter AS STRING, count AS INTEGER)
LOCAL lth AS INTEGER
LOCAL lp AS INTEGER
LOCAL result AS STRING
LOCAL section AS INTEGER
LOCAL chr AS STRING
result=""
section=1
lth=LEN(sourceString)

for lp=1 TO lth
chr=MID$(sourceString,lp)
if chr=delimiter
section=section+1
else
if section=count
result=result+chr
endif
endif
next lp
endfunction result

DBProInside.ini
To make the DBPro program look for the VB App to attach to, you will need to create an .ini file of the same name as the exe. This will need to contain the App title. (Main Form text property) and child window text (The group box text property) In out example these are:
[EXTERNAL]
Main Window = DBPro Inside
Child Window = ShowHere

DBProInside.vb

This is an image of the form which the below code is associated with.



Now we move on to the visual basic code. This project (Also in the attached zip file) contains a win32.vb class which is the code from the MSDN site to handle the file mapping.

There is one thing to watch out for if running the VB code from inside the VB IDE, make sure the design view of frmMain is closed. The DBPro program will very likely try to display inside the IDE .

So firstly, we just need to include the win32 classes so we don’t have to keep referring to the win32 file. And the System,IO as we use the Path Class

Imports DBProInside.Win32
Imports System.IO

The rest is inside the Public Class frmMain class.

Firstly we will create the memory mapped files when this form starts.

Private _heartBeat As New SharedMemory(1024, "dbpvb_HeartBeat")
Private _mouseCoords As New SharedMemory(1024, "dbpvb_Mouse")
Private _mouseReady As New SharedMemory(10, "dbpvb_MouseReady")
Private _SendData As New SharedMemory(2048, "dbpvb_SendData")
Private _SendDataReady As New SharedMemory(10, "dbpvb_SendDataReady")

Then we declare our message stack which we use to send messages to the DBPro application and the constant for the window resize message type

Private _messages As New Stack(Of String)
Private Const msg_WindowResize As Integer = 1

When the top button is clicked, this subroutine will launch the DBPro application from the same directory where this program is run from. It will also start the heart beat timer and send the window size to the DBPro application

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
' disable the button to stop it being launched twice
Button1.Enabled = False
' store the first heart beat in the heard beat mapped file)
SendString("Tick", _heartBeat)
' Now launch the Dark Basic Professional application. (Make sure the ini file exists otherwise
' the app will launch in its own window)
Process.Start(Path.GetDirectoryName(Application.ExecutablePath) & "" & "DBProInside_dbp.exe")
' Now we need to enable a timer to handle the sending of messages.
Timer1.Enabled = True
' Send our first message to make sure the DBPro app opens with the correct screen size.
SendWindowSize()
End Sub

When the lower button is clicked the window resize message is sent again with the current size of the window. Try resizing the app and then clicking the button to see it in action.

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
SendWindowSize()
End Sub

This routine handles all the message sending/heart beat etc. like our main loop does in the DBPro application.

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
' Handle the heart beat.
If ReadString(_heartBeat) = "Tock" Then
SendString("Tick", _heartBeat)
End If
' Process any messages on the message stack.
SendMessages()
End Sub

As the method that came from the MS File Mapping code only reads and writes bytes at an offset, I've written routines to set and get text from the mapped file. What this routine does, is convert the string into an array of characters and convert each character to a byte and increment the offset to write the string into the mapped file passed in. And at the end put a 0 byte to signal the end of the string.

Private Sub SendString(ByVal data As String, ByVal mappedFile As SharedMemory)
Dim lp As Integer
' Turn the string into an array of characters
Dim bits() As Char = data.ToCharArray

' loop through the array of characters
For lp = 0 To bits.Length - 1
' convert the next character to a byte
Dim ch As Byte
ch = CByte(Asc(bits(lp)))
' write the byte into the next space of the mapped file
mappedFile.SetByte(lp, ch)
Next
' Now write a 0 to signify the end of a string.
mappedFile.SetByte(bits.Length, 0)
End Sub

For the same reasons as the SendString, this routine will scan through the passed in mapped file and building a string from the bytes until it gets to a 0 byte. Then it returns the string back to the calling function. You will need to be careful with this in case it goes over the end of a file into memory.

Private Function ReadString(ByVal mappedFile As SharedMemory) As String
' Set up the string to store the characters in.
Dim result As String = ""
Dim lp As Integer

' loop through the mapped file
For lp = 0 To 2048
' Get the byte from the next position in the mapped file
Dim ch As Byte
ch = mappedFile.GetByte(lp)

' Is the byte a 0?
If ch = 0 Then
' Great it is, so we can return the string we have built
Return result
Else
' Nope, so we need to add the character to the string
result &= Chr(CInt(ch))
End If
Next
' something nasty has happened so we just return nothing.
Return ""
End Function

This routine will take a Boolean parameter and based on this, set the mouse ready mapped file to 1 or 0

Private Sub SetMouseReady(ByVal flag As Boolean)
If flag = True Then
_mouseReady.SetByte(0, 1)
Else
_mouseReady.SetByte(0, 0)
End If
End Sub

This function handles the event that the mouse has moved over the DBPro window. In this case we need to send the new mouse coordinates.

Private Sub GroupBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles GroupBox1.MouseMove
' Firstly are we talking to the DBPro program yet?
' We can tell this if the timer has been started which controls
' the sending of information.
If Timer1.Enabled = True Then
' Great we are talking to the DBPro program so get the coordinates from
' the event for the current mouse position.
Dim coords As String = ""
coords = e.X.ToString & "," & e.Y.ToString & ",0"
' Set the mouse ready flag to 0 so we stop the DBPro program from
' reading this data until we have finished updating the mouse coordinates
' mapped file.
SetMouseReady(False)
' Update the mapped file.
SendString(coords, _mouseCoords)
' No set the mouse ready flag to 1 to let the DBPro program know we have
' finished writing the data.
SetMouseReady(True)
End If
End Sub

If the form closes, we need to catch this and send a fatal heart beat to the DBPro application to make it shut down.

Private Sub frmMain_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
' We are closing the form so we need to give the DBPro program a shutdown message
If Timer1.Enabled = True Then
' Well it was either that or BEEEEEEEP
SendString("Dead", _heartBeat)
End If
End Sub

This simple routine will add a message to the message stack which will be processed by the timer tick event.

Private Sub AddMessage(ByVal message As String)
_messages.Push(message)
End Sub


This is the routine to send the next message in the stack to the DBPro program.
Private Sub SendMessages()
' First check to see if the previous message has been processed.
If SendDataReady() = False Then
' Great, it has so we now check to see if there are any messages to send.
If _messages.Count <> 0 Then
' There is, pop the message from the stack and send it to the SendData mapped file.
SendString(_messages.Pop, _SendData)
' Notifiy the DBPro program that there is a new message to be read.
SetDataReady(True)
End If
End If
End Sub

This function will check if the SendDataReady mapped file and return a true or false based on the file being 1 or 0.

Private Function SendDataReady() As Boolean
If _SendDataReady.GetByte(0) = 1 Then
Return True
Else
Return False
End If
End Function

This routine will update the Send Data Ready mapped file to 1 or 0 depending on whether the flag passed in was true or false.

Private Sub SetDataReady(ByVal flag As Boolean)
If flag = True Then
_SendDataReady.SetByte(0, 1)
Else
_SendDataReady.SetByte(0, 0)
End If
End Sub

Finally, this routine will workout the size of the group box and create a message to send to the DBPro program and add it to the message stack. It will use the window resize command number and the "|" character for the separator, then add the width and height of the box separated with a ",".
Private Sub SendWindowSize()
Dim msg As String
msg = msg_WindowResize.ToString & "|" & GroupBox1.Width.ToString & "," & GroupBox1.Height.ToString
AddMessage(msg)
End Sub


Phew made it through. I'm sure it will make a lot more sense when you see the code running.

A screen shot of the DBPro app running inside the form.



Cheers,

Jas

----
"What is this talk of 'release'? Klingons do not'release' software. It escapes leaving a bloody trail of developers and quality assurance people in its wake!"

Attachments

Login to view attachments
Bush Baby
18
Years of Service
User Offline
Joined: 23rd Apr 2005
Location: A cave beneath Jerusalem
Posted: 4th Jan 2006 01:55
Cool! Thanks!


||Secretive project: ||16.5% complete. || Rewrite: 3rd || Genre: Adventure ||
jasuk70
21
Years of Service
User Offline
Joined: 3rd Dec 2002
Location: Hemel Hempstead
Posted: 4th Jan 2006 02:00 Edited at: 17th May 2007 15:22
Cheers, sorry about the code formatting, i used {code}{/code} (I know it's [ ] ) but it didnt seem to work. All the code is in the download any how.

Jas

----
"What is this talk of 'release'? Klingons do not'release' software. It escapes leaving a bloody trail of developers and quality assurance people in its wake!"

Login to post a reply

Server time is: 2024-04-20 08:09:25
Your offset time is: 2024-04-20 08:09:25