How to send a request and get a response using system.io.pipes - named-pipes

I'm trying to write a client and server that communicate through named pipes. I need the the client to be able to send a query to the server and have the server send a response. All of the samples on the web for System.IO.pipes either send a single string to the client or a single string to the server. I have found some examples of how to do that with the old Win32 pipes. But, I can't use that.
Here is what I have so far. I know it is a lot of code. But, I think the issue may just be in one of the constructors for the pipe streams.
Client Code
Private Sub cmdSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSend.Click
Dim strMsg As String = ""
Dim strRequest As String = "Send Key"
Dim strErrMsg As String = ""
PrintText("Creating new pipe client")
'pipeStream = New NamedPipeClientStream(strPipeName)
'pipeStream = New NamedPipeClientStream(strPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None)
pipeStream = New NamedPipeClientStream(strServerName, strPipeName, PipeDirection.InOut, PipeOptions.None, Security.Principal.TokenImpersonationLevel.None)
PrintText("Connecting to server")
pipeStream.Connect()
pipeStream.ReadMode = PipeTransmissionMode.Message
PrintText("Sending request")
'Send Request
If SendPipeMessage(pipeStream, strRequest, strErrMsg) Then
Else
PrintText(strErrMsg)
End If
PrintText("Receiving response")
'Process Response
If ReadPipeMessage(pipeStream, strMsg, strErrMsg) Then
PrintText(strMsg)
Else
PrintText(strErrMsg)
End If
pipeStream.Dispose()
pipeStream = Nothing
End Sub
Private Function SendPipeMessage(ByRef pipeStream As NamedPipeClientStream, ByVal strMsg As String, ByRef strErrMsg As String) As Boolean
Dim blnRetVal As Boolean = True
Dim bytMessage() As Byte = Nothing
Dim encoding As UTF8Encoding = Nothing
Try
encoding = New UTF8Encoding
bytMessage = encoding.GetBytes(strMsg)
pipeStream.Write(bytMessage, 0, bytMessage.Length)
Catch ex As Exception
blnRetVal = False
strErrMsg = ex.ToString
End Try
Return blnRetVal
End Function
Private Function ReadPipeMessage(ByRef pipeStream As NamedPipeClientStream, ByRef strPipeText As String, ByRef strErrMsg As String) As Boolean
Dim blnRetVal As Boolean = True
Dim strTextChunck As String = ""
Dim intNumBytes As Integer = 0
Dim intNumChars As Integer = 0
Dim bytMessage(10) As Byte
Dim chars(10) As Char
Dim decoder As Decoder = Nothing
Try
decoder = Encoding.UTF8.GetDecoder
strPipeText = ""
Do
strTextChunck = ""
Do
intNumBytes = pipeStream.Read(bytMessage, 0, bytMessage.Length)
intNumChars = decoder.GetChars(bytMessage, 0, intNumBytes, chars, 0)
strTextChunck = strTextChunck & New String(chars, 0, intNumChars)
Loop While Not pipeStream.IsMessageComplete
strPipeText = strPipeText & strTextChunck
Loop While intNumBytes <> 0
Catch ex As Exception
blnRetVal = False
strErrMsg = ex.ToString
End Try
Return blnRetVal
End Function
Server Code
Private Sub cmdListen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdListen.Click
bw.RunWorkerAsync()
End Sub
Private Sub bw_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
Dim strRequest As String = ""
Dim strErrMsg As String = ""
bw.ReportProgress(0, "Create new pipe server stream")
pipeStream = New NamedPipeServerStream(strPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None)
'Wait for connection
bw.ReportProgress(0, "Listening for connection")
pipeStream.WaitForConnection()
bw.ReportProgress(0, "Receiving request")
'Receive Request
If ReadPipeMessage(pipeStream, strRequest, strErrMsg) Then
bw.ReportProgress(0, "Request : " & strRequest)
Else
bw.ReportProgress(0, strErrMsg)
End If
'Get response
strResponse = GetResponse(strRequest)
bw.ReportProgress(0, "Sending response")
'Send Response
If SendPipeMessage(pipeStream, strResponse, strErrMsg) Then
bw.ReportProgress(0, "Sent response")
Else
bw.ReportProgress(0, strErrMsg)
End If
bw.ReportProgress(0, "Done!")
pipeStream.Disconnect()
pipeStream.Dispose()
pipeStream = Nothing
End Sub
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bw.ProgressChanged
PrintText(e.UserState)
End Sub
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
PrintText(e.Result)
End Sub
Private Function SendPipeMessage(ByRef pipeStream As NamedPipeServerStream, ByVal strMsg As String, ByRef strErrMsg As String) As Boolean
Dim blnRetVal As Boolean = True
Dim bytMessage() As Byte = Nothing
Dim encoding As UTF8Encoding = Nothing
Try
encoding = New UTF8Encoding
bytMessage = encoding.GetBytes(strMsg)
pipeStream.Write(bytMessage, 0, bytMessage.Length)
Catch ex As Exception
blnRetVal = False
strErrMsg = ex.ToString
End Try
Return blnRetVal
End Function
Private Function ReadPipeMessage(ByRef pipeStream As NamedPipeServerStream, ByRef strPipeText As String, ByRef strErrMsg As String) As Boolean
Dim blnRetVal As Boolean = True
Dim strTextChunck As String = ""
Dim intNumBytes As Integer = 0
Dim intNumChars As Integer = 0
Dim bytMessage(10) As Byte
Dim chars(10) As Char
Dim decoder As Decoder = Nothing
Try
decoder = Encoding.UTF8.GetDecoder
strPipeText = ""
Do
strTextChunck = ""
Do
intNumBytes = pipeStream.Read(bytMessage, 0, bytMessage.Length)
intNumChars = decoder.GetChars(bytMessage, 0, intNumBytes, chars, 0)
strTextChunck = strTextChunck & New String(chars, 0, intNumChars)
Loop While Not pipeStream.IsMessageComplete
strPipeText = strPipeText & strTextChunck
Loop While intNumBytes <> 0
Catch ex As Exception
blnRetVal = False
strErrMsg = ex.ToString
End Try
Return blnRetVal
End Function
When I start the client, it hangs on ReadPipMessage() and the server hangs on ReceivingRequest(). If I kill the client, the server reads in the request sent by the client. But, it bombs on sending the response because the client is no longer running.
Is it not possible for the server to send and receive a message in the same connection? I thought that was what PipeDirection.InOut meant.
Thanks,
Mike

You may want to look at the named pipes binding for WCF. There are many good examples of how to use it available out there, for instance: http://www.jmedved.com/2010/03/named-pipes-in-wcf/

Related

Use countif to determine boolean in a class module

I'm trying to use a Boolean within a class module however the code fails at “MsgBox LOADPROPS.DUPLICATES” with “Public Property Get DUPLICATES() As Range” Please can someone assist?
Sub INITIALIZE_CLASS()
Dim LOADPROPS As cLoadData
Set LOADPROPS = New cLoadData
LOADPROPS.DUPLICATES = PasteLoadingForm.Columns("K")
MsgBox LOADPROPS.DUPLICATES
End Sub
and in the class module
Public Property Get DUPLICATES() As Range
DUPLICATES = pDUPLICATES
End Property
Public Property Let DUPLICATES(Value As Range)
Dim lcount As Long
lcount = Application.WorksheetFunction.CountIf(Value, "DUPLICATE")
'
pDUPLICATES = lcount
Select Case pDUPLICATES
Case Is = 0
pDUPLICATES = False
Case Is >= 0
pDUPLICATES = True
Case Else
MsgBox "Error"
End Select
End Property
From what I can tell, you're mixing up the data type of DUPLICATES. You are declaring it as a range in the get function, yet pDUPLICATES can take on the value of a boolean and long (initially is the count then converted to true or false in the select statement).
The quick fix would be to change them to variants, so your class module becomes:
Private pDUPLICATES As Variant
Public Property Get DUPLICATES() As Variant
DUPLICATES = pDUPLICATES
End Property
Public Property Let DUPLICATES(Value As Variant)
Dim lcount As Long
lcount = Application.WorksheetFunction.CountIf(Value, "DUPLICATE")
'
pDUPLICATES = lcount
Select Case pDUPLICATES
Case Is = 0
pDUPLICATES = False
Case Is >= 0
pDUPLICATES = True
Case Else
MsgBox "Error"
End Select
End Property
EDIT
To avoid variants, a better approach might be to restructure your class module so you're not actually using get/set properties (which you're not really doing here). You could do it with a function as follows:
Class cLoadDataV2
Option Explicit
Public pDUPLICATES As Boolean
Public Function fDuplicates(rngInputRange As Range)
Dim lcount As Long
lcount = Application.WorksheetFunction.CountIf(rngInputRange, "DUPLICATE")
Select Case lcount
Case Is = 0
pDUPLICATES = False
Case Is >= 0
pDUPLICATES = True
Case Else
MsgBox "Error"
End Select
End Function
Regular module
Sub Initialise_v2()
Dim LOADPROPS As cLoadDataV2
Set LOADPROPS = New cLoadDataV2
LOADPROPS.fDuplicates (Sheet1.Columns("K"))
MsgBox LOADPROPS.pDUPLICATES
End Sub

How to convert a string to a variant in jscript under wsh?

I need to append a string of text to the end of a binary file.
This is what I'm trying:
inStream = WScript.CreateObject("ADODB.Stream") ;
inStream.type = 1 ;
inStream.open() ;
inStream.LoadFromFile('test.bin') ;
outStream = WScript.CreateObject("ADODB.Stream") ;
outStream.type = 1 ;
outStream.open() ;
outStream.write( inStream.read() ) ;
outStream.write( "\nCONTENT AT THE END" ) ; // this gives an error
outStream.SaveToFile('test2.bin',2) ;
The reported error is "wrong argument".
The documentation of that method says the argument must be of type variant.
How can I convert a string to a variant?
The solution is to use auxiliary ADODB.Stream instance .copyTo() method.
var inStream = WScript.CreateObject('ADODB.Stream'); // source stream
inStream.Type = 1; // adTypeBinary
inStream.Open();
inStream.LoadFromFile('C:\\Test\\src.bin');
var outStream = WScript.CreateObject('ADODB.Stream'); // target stream
outStream.Type = 1; // adTypeBinary
outStream.Open();
outStream.Write(inStream.read());
inStream.Close();
var bufStream = WScript.CreateObject('ADODB.Stream'); // auxiliary stream
bufStream.Type = 2; // adTypeText
bufStream.Open();
bufStream.WriteText('\nCONTENT AT THE END'); // strings held as Unicode in memory
bufStream.Position = 2; // skip BOM bytes FF FE
bufStream.CopyTo(outStream); // append to the end of target stream
bufStream.Close();
outStream.SaveToFile('C:\\Test\\dst.bin', 2);
outStream.Close();

Referencing reusable forms (2 deep) in MS Access

I have multiple Members, and each one has a record which contains several memo fields:
Member ID Entry A Entry B
1 [memo text] [memo text]
2 [memo text] [memo text]
3 [memo text] [memo text]
In Access 2007, I am creating a Memo Entry form that is the equivalent of Shift-F2 -- A dedicated window to review and edit the content. Unlike Shift-F2, this must be reusable.
I must mention that the form for showing details about a Member is also reusable. Following the plan above (which is abbreviated), I could have up to three Member forms and six Memo Entry forms open at once.
The solution below works well EXCEPT that the critical event UpdateComment() is only triggered once per Member-form instead of once per Memo-form. So if I open A and B for the same member and make edits, only one edit will be passed to the calling form.
Somehow I am failing to provide the calling form, Member Detail, with a method to treat the Memo forms as unique. How do I solve that?
Member Detail form -- this does the spawning
Dim strFieldName As String, varValue
Private WithEvents frmZoom As Form_frmMemberInputZoom
Private Sub btnView_A_Click()
strFieldName = "boxQuality_A"
varValue = Me(strFieldName)
Call OpenMemberInput(Me!boxID, strFieldName, varValue, True)
End Sub
Private Sub btnView_B_Click()
strFieldName = "boxQuality_B"
varValue = Me(strFieldName)
Call OpenMemberInput(Me!boxID, strFieldName, varValue, True)
End Sub
Private Sub frmZoom_UpdateComment(lngID As Long, _
strAssessStage As String, varReturn)
Dim intCount As Integer
For intCount = 1 To collectnZooms.Count
If collectnZooms(intCount)![boxID] = lngID _
And collectnZooms(intCount)![boxAssessStage] = strAssessStage _
Then
Me(strAssessStage) = varReturn
Me.Dirty = False
Exit Sub
End If
Next
End Sub
Private Sub Form_Close()
Dim obj As Object
For Each obj In collectnMembers
If obj.Hwnd = Me.Hwnd Then
collectnMembers.Remove CStr(Me.Hwnd)
End If
Next
End Sub
Sub OpenMemberInput(lngID As Long, strStage As String, _
varComment, booEdit As Boolean)
Set frmZoom = New Form_frmMemberInputZoom
frmZoom.Caption = CStr(frmZoom.Hwnd)
frmZoom.ID = lngID
frmZoom.Stage = strStage
frmZoom.ProviderName = "Dr " & CStr(lngID)
frmZoom.Comment = varComment
frmZoom.Visible = True
collectnZooms.Add Item:=frmZoom, Key:=CStr(frmZoom.Hwnd)
End Sub
Memo Entry form -- this one is spawned
Public Event UpdateComment(lngID As Long, strAssessStage As String, _
varReturn)
Private lngAssess_ID As Long
Private strAssessStage As String
Private strProviderName As String
Private varComment
Public Property Let ID(ByVal MyAssessID As Long)
lngAssess_ID = MyAssessID
Me.boxID = lngAssess_ID
End Property
Public Property Let Stage(ByVal MyAssessStage As String)
strAssessStage = MyAssessStage
Me.boxAssessStage = strAssessStage
End Property
Public Property Let ProviderName(ByVal MyProviderName As String)
strProviderName = MyProviderName
Me.boxProviderName = strProviderName
End Property
Public Property Let Comment(ByVal varExisting)
varComment = varExisting
Me.boxComment = varComment
End Property
Public Property Get Comment()
Comment = varComment
End Property
Private Sub boxComment_AfterUpdate()
varComment = Me.boxComment
Comment = varComment
End Sub
Private Sub Form_Close()
'#################################################################
' Line below will be called for only ONE of the multiple instances
'#################################################################
RaiseEvent UpdateComment(lngAssess_ID, strAssessStage, varComment)
Dim obj As Object
For Each obj In collectnZooms
If obj.Hwnd = Me.Hwnd Then
collectnZooms.Remove CStr(Me.Hwnd)
End If
Next
End Sub
As recommended in this post:
Dismantle the RaiseEvent function.
Use .Visible to avoid closing out the Memo Entry form (which throws things off).
Member Detail form -- this does the spawning
Dim strFieldName As String, varValue
'Private WithEvents ... NAH, WE WON'T GO THERE
Private Sub btnView_A_Click()
strFieldName = "boxQuality_A"
varValue = Me(strFieldName)
Call OpenMemberInput(Me!boxID, strFieldName, varValue, True)
End Sub
Private Sub btnView_B_Click()
strFieldName = "boxQuality_B"
varValue = Me(strFieldName)
Call OpenMemberInput(Me!boxID, strFieldName, varValue, True)
End Sub
'Private Sub frmZoom_UpdateComment(lngID As Long, strAssessStage As String, varReturn)
' ... NAH, we won't be using this...
'End Sub
Private Sub Form_Close()
Dim obj As ObjecT
For Each obj In collectnMembers
If obj.Hwnd = Me.Hwnd Then
collectnMembers.Remove CStr(Me.Hwnd)
End If
Next
End Sub
Sub OpenMemberInput(lngID As Long, strStage As String, _
varComment, booEdit As Boolean)
Dim FoundMe As Boolean
FoundMe = v_MemberComment.FetchForm(lngID, strStage)
If FoundMe Then Exit Sub
Dim frmZoom As New Form_frmMemberInputZoom
Set frmZoom = New Form_frmMemberInputZoom
frmZoom.ID = lngID
frmZoom.Stage = strStage
frmZoom.Comment = varComment
frmZoom.Visible = True
collectnZooms.Add Item:=frmZoom, Key:=CStr(frmZoom.Hwnd)
End Sub
Memo Entry form -- this one is spawned
'' NAH, no use of the Public Event
'Public Event UpdateComment(lngID As Long, strAssessStage As String, varReturn)
Private lngAssess_ID As Long
Private strAssessStage As String
Private varComment
Public Property Let ID(ByVal MyAssessID As Long)
lngAssess_ID = MyAssessID
Me.boxID = lngAssess_ID
End Property
Public Property Let Stage(ByVal MyAssessStage As String)
strAssessStage = MyAssessStage
Me.boxAssessStage = strAssessStage
End Property
Public Property Let Comment(ByVal varExisting)
varComment = varExisting
Me.boxComment = varComment
End Property
Public Property Get Comment()
Comment = varComment
End Property
Private Sub boxComment_AfterUpdate()
varComment = Me.boxComment
Comment = varComment
End Sub
Private Sub CmdCancel_Click()
''TODO revert to before-update text if Cancel is selected
Me.Tag = "Cancel"
Me.Visible = False
End Sub
Private Sub CmdOK_Click()
Dim intCount As Integer, frm As Form, lngHwnd As Long
For intCount = 1 To collectnMembers.Count
Set frm = collectnMembers(intCount)
If frm![boxID] = lngAssess_ID Then
frm(strAssessStage) = varComment
frm.Requery
End If
Next
Me.Visible = False
End Sub
Private Sub Form_Close()
'RaiseEvent .... NAH, DON'T BOTHER
Dim obj As Object
For Each obj In collectnZooms
If obj.Hwnd = Me.Hwnd Then
collectnZooms.Remove CStr(Me.Hwnd)
End If
Next
End Sub
VBA Standard Module to support above form
Public collectnZooms As New Collection
Public Function FetchForm(lngID As Long, strStage As String) As Boolean
Dim intCount As Integer
For intCount = 1 To collectnZooms.Count
If collectnZooms(intCount)![boxID] = lngID _
And collectnZooms(intCount)![boxAssessStage] = strStage Then
FetchForm = True
collectnZooms(intCount).Tag = ""
collectnZooms(intCount).Visible = True
Exit Function
End If
Next
End Function

use outlook instead of gmail to send mails vb.net 2010

I have a class to send emails with multiple attachments, it uses gmail, but how can I use Outlook to send emails?
Imports System.Net.Mail
Imports System.Net.Mime
Public Sub SendThis(ByVal SubjectText As String, _
ByVal BodyText As String, _
ByVal FromAddress As String, _
ByVal ToAddress As String, _
Optional ByVal FileName As Collection = Nothing _
)
Try
Dim email As New Net.Mail.MailMessage(FromAddress, ToAddress)
email.Subject = SubjectText
email.Body = BodyText
If Not FileName Is Nothing Then
For Each Name As String In FileName
Dim attach As New Net.Mail.Attachment(Name) 'Includes Path
email.Attachments.Add(attach)
Next
For Each At As Attachment In email.Attachments
At.TransferEncoding() = Net.Mime.TransferEncoding.Base64
Next
End If
Dim TheSmtp As New SmtpClient(YourSmtpServerName, 587)
TheSmtp.Credentials = New Net.NetworkCredential("abc#gmail.com","MYPASS")
TheSmtp.DeliveryMethod = SmtpDeliveryMethod.Network
TheSmtp.Send(email)
email.Attachments.Clear()
TheSmtp = Nothing
email = Nothing
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message, "HFB", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End Try
End Sub
I call the function like:
Private Sub BtnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnSend.Click
Dim BodyText As String
Dim SubjectText As String
Dim FromAddress As String
Dim ToAddress As String
Dim Filename As New Collection
If Me.LstBxAttach.Items.Count > 0 Then
For Each TheItem As String In LstBxAttach.Items
Filename.Add(TheItem)
Next
End If
SubjectText = Me.TbSubject.Text
BodyText = Me.TbBody.Text
SendThis(SubjectText, _
BodyText, _
"from#example.com", _
"to#example.com", _
Filename _
)
SubjectText = ""
BodyText = ""
FromAddress = ""
ToAddress = ""
MessageBox.Show("Sent!", "HFB", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
You can use the Outlook object model. The Application object lets you connect to Outlook; to send an e-mail, you will want to create a MailItem object, populate the relevant properties, and call its Send method.

Releasing recursive class module in VBA

I've been playing around with a class module which contains multiple versions of itself to build up a tree structure.
I've noticed the process of building the tree is very fast. Roughly 2 seconds for a 7 level tree with 6-8 branches per subtree. Unfortunately the program runs very slowly. This seems to be caused by the release of the memory used by the tree, which takes at least 60 seconds.
Initially I did not release the class module, and allowed VB to do it at the end of the program, but replacing this with set myTree = nothing makes no difference to the speed.
I also tried writing a sub routine to destroy the tree. This recursively went through each layer and set the sub trees to nothing. Oddly this seemed to save aroung 0.5 of a second, but nothing significant.
Is there anything else I can do to reduce the unload time?
The code is really long but the excert below gives the idea. I'm happy that the tree structure works, but the gap between the final two timer statements is very large
Class treeNode
private aCurrentDepth as integer
private aNodeObject as myObject
private aNodes(maxNodeCount) as treeNode
end class
public function creatreTree(m as myObject,depth as integer) as treeNode
Dim x As Integer
Set createTree = New treeNode
createTree.initialise
createTree.cNodeObject = m
createTree.cCurrentDepth = depth
If depth <> 1 Then
For x = 0 To maxNodeCount
createTree.tNode(x) = createTree(getObject(m,x), depth - 1)
Next x
End If
end function
sub testTree
Dim t as treeNode
dim g as myObject
Set t = New treeNode
g.initialise
t.initialise
set g = startObject
Cells(1, "A") = Timer
Set t = createTree(g, 7)
Cells(1, "B") = Timer
Set t = Nothing
Cells(1, "C") = Timer
end sub
I just created a debugExcelLog class last week. You might find it helpful to track what is happening in your classes. I used it to locate a bug that was happening only occasionally. (Turned out UserRoutine2 was trying to use a global class that UserRoutine1 was in the middle of clearing.)
'------------------------------------------------------------------------------
'| Class Name: debugExcelLog
'| Programmer: Mischa Becker
'| Date: 11/4/2011
'| Purpose: Creates an Excel Workbook log
'------------------------------------------------------------------------------
'| Notes:
'| + Add a DEBUG_PROJECT compiler constant to your project
'| + Add Public Const g_PROJECT_NAME As String = "[ProjectName]" to a module.
'| + sName and sCalledBy are expected to be in one of the following formats:
'| Project.Class.Routine
'| Class.Routine
'| Routine
'------------------------------------------------------------------------------
Option Explicit
Private Const m_CLASS_NAME As String = g_PROJECT_NAME & ".debugExcelLog"
Private Const m_OFFSET As Integer = 3
Private m_wbk As Workbook
Private m_r As Range
Private m_bLogged As Boolean
Private m_iIndent As Integer
Private m_bOkToLog As Boolean
Private m_lInstanceID As Long
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
Private Sub Class_Initialize()
m_bOkToLog = False
m_lInstanceID = CLng(Rnd * 10 ^ 6)
#If DEBUG_PROJECT Then
Debug.Print m_CLASS_NAME; ".Class_Initialize", "Id:"; m_lInstanceID
Me.TurnOn
#End If
End Sub
Private Sub Class_Terminate()
If Not (m_bLogged Or m_wbk Is Nothing) Then
m_wbk.Close False
End If
Set m_wbk = Nothing
Set m_r = Nothing
#If DEBUG_PROJECT Then
Debug.Print m_CLASS_NAME; ".Class_Terminate", "Id:"; m_lInstanceID
#End If
End Sub
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
Public Sub TurnOn()
Set m_wbk = Application.Workbooks.Add
Set m_r = m_wbk.Sheets(1).Range("A1")
m_iIndent = 0
SetTitle
m_bOkToLog = True
End Sub
Public Sub TurnOff()
m_bOkToLog = False
End Sub
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
Public Sub Log_Start(sName As String, lInstance As Long _
, Optional sCalledBy As String = "" _
, Optional sComment As String = "")
Const MY_NAME As String = m_CLASS_NAME & ".Log_Start"
On Error GoTo ErrorHandler
If Not m_bOkToLog Then Exit Sub
m_bLogged = True
m_iIndent = m_iIndent + 1
BreakApartAndLogName sName, ".Start"
Instance = lInstance
TimeStamp = Now
CalledBy = sCalledBy
Comment = sComment
MoveNextRow
Exit Sub
ErrorHandler:
Debug.Print MY_NAME, Err.Number; " - "; Err.Description
Stop
Resume
End Sub
Public Sub Log_End(sName As String, lInstance As Long _
, Optional sCalledBy As String = "" _
, Optional sComment As String = "")
Const MY_NAME As String = m_CLASS_NAME & ".Log_End"
On Error GoTo ErrorHandler
If Not m_bOkToLog Then Exit Sub
BreakApartAndLogName sName, ".End"
Instance = lInstance
TimeStamp = Now
CalledBy = sCalledBy
Comment = sComment
MoveNextRow
m_iIndent = m_iIndent - 1
Exit Sub
ErrorHandler:
Debug.Print MY_NAME, Err.Number; " - "; Err.Description
Stop
Resume
End Sub
Public Sub Log_Other(sName As String, lInstance As Long _
, Optional sCalledBy As String = "" _
, Optional sComment As String = "")
Const MY_NAME As String = m_CLASS_NAME & ".Log_Other"
On Error GoTo ErrorHandler
If Not m_bOkToLog Then Exit Sub
m_bLogged = True
If m_iIndent < 0 Then m_iIndent = 0
BreakApartAndLogName sName
Instance = lInstance
TimeStamp = Now
CalledBy = sCalledBy
Comment = sComment
MoveNextRow
Exit Sub
ErrorHandler:
Debug.Print MY_NAME, Err.Number; " - "; Err.Description
Stop
Resume
End Sub
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
Private Sub SetTitle()
Const MY_NAME As String = m_CLASS_NAME & ".SetTitle"
On Error GoTo ErrorHandler
m_r = "Debug Excel Log Created on " & Date
MoveNextRow
Project = "Project"
Module = "Module"
Routine = "Routine"
Instance = "Instance"
TimeStamp = "TimeStamp"
CalledBy = "Called By"
Comment = "Comment"
With Range(m_r, m_r.End(xlToRight))
.Font.Bold = True
.BorderAround XlLineStyle.xlContinuous, xlMedium
End With
MoveNextRow
m_iIndent = -1
Exit Sub
ErrorHandler:
Debug.Print MY_NAME, Err.Number; " - "; Err.Description
Stop
Resume
End Sub
Private Sub MoveNextRow()
Set m_r = m_r.Offset(1)
End Sub
Private Sub BreakApartAndLogName(ByVal sName As String _
, Optional sExtra As String = "")
Const MY_NAME As String = m_CLASS_NAME & ".BreakApartAndLogName"
On Error GoTo ErrorHandler
Routine = SplitOffLastSection(sName) & sExtra
If Len(sName) > 0 Then
Module = SplitOffLastSection(sName)
If Len(sName) > 0 Then
Project = SplitOffLastSection(sName)
End If
End If
Exit Sub
ErrorHandler:
Debug.Print MY_NAME, Err.Number; " - "; Err.Description
Stop
Resume
End Sub
Private Function SplitOffLastSection(ByRef sName As String) As String
' Passed sName is returned without the Last Section.
Const MY_NAME As String = m_CLASS_NAME & ".SplitOffLastSection"
Dim i As Integer
i = InStrRev(sName, ".")
If i > 0 Then
SplitOffLastSection = Mid(sName, i + 1)
sName = Left(sName, i - 1)
Else
SplitOffLastSection = sName
sName = ""
End If
Exit Function
ErrorHandler:
Debug.Print MY_NAME, Err.Number; " - "; Err.Description
Stop
Resume
End Function
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
Private Property Let Project(sText As String)
m_r = sText
End Property
Private Property Get Project() As String
Project = m_r.Text
End Property
Private Property Let Module(sText As String)
m_r.Offset(0, 1) = sText
End Property
Private Property Get Module() As String
Module = m_r.Offset(0, 1).Text
End Property
Private Property Let Routine(sText As String)
If m_iIndent < 0 Then
m_r.Offset(0, 2) = sText
Else
m_r.Offset(0, 2) = Space(m_OFFSET * m_iIndent) & sText
End If
End Property
Private Property Let Instance(lInstance As Variant)
m_r.Offset(0, 3) = lInstance
End Property
Private Property Let TimeStamp(dTimeStamp As Variant)
m_r.Offset(0, 4) = dTimeStamp
End Property
Private Property Let CalledBy(ByVal sText As String)
' remove Project and Module from sText if same as running Routine
sText = Replace(sText, Project & "." & Module & ".", "")
sText = Replace(sText, Project & ".", "")
m_r.Offset(0, 5) = sText
End Property
Private Property Let Comment(sText As String)
m_r.Offset(0, 6) = sText
End Property
'------------------------------------------------------------------------------
To use:
Add the class to your project and name it debugExcelLog
Add DEBUG_PROJECT=-1 as a conditional compiler constant to your project
Create a global variable of the class. ie Public g_XlLog As debugExcelLog
Logging can be turned on and off with
g_xlLog.TurnOn
g_xlLog.TurnOff
If DEBUG_PROJECT is True, you don't need to call TurnOn, the class will auto turn on when it initializes.
Use the following in any routine you want to track.
g_XlLog.Log_Start "[Class.Routine]", m_lInstanceIdOrZero
g_XlLog.Log_Other "[Class.Routine]", m_lInstanceIdOrZero, ,"Comment"
g_XlLog.Log_End "[Class.Routine]", m_lInstanceIdOrZero
I suggest altering your testTree as follows.
sub testTree
Dim t as treeNode, g as myObject
Dim iLevel as Integer
iLevel = 7
Set g_XlLog = New debugExcelLog
g_XlLog.Log_Start "testTree", 0, , "Initializing test variables"
Set t = New treeNode
g.initialise
t.initialise
set g = startObject
g_XlLog.Log_Other "testTree", 0, , "Create a " & iLevel & " level tree"
Set t = createTree(g, iLevel)
g_XlLog.Log_Other "testTree", 0, , "Terminate a " & iLevel & " level tree"
Set t = Nothing
g_XlLog.Log_End "testTree", 0
set g_XlLog = Nothing
end sub
I would recommend adding logging to Class_Initialize and Class_Terminate for both treeNode and MyObject. If Class_Terminate is calling other routines you can either add logging to them, or use Log_Other to track when each one starts.
If you haven't done so already, I really recommend adding some sort of instance id to treeNode so you will know which instance is being created\terminated. If you aren't worried about Rnd creating duplicate IDs it can be as simple as what I have in the above class.
You will also notice the optional sCalledBy and sComment parameters. sComment should be obvious but sCalledBy is there because the Excel VBE's Call Stack leaves a lot to be desired. For debugging purposes, some of my methods require the routine calling them to pass their name in as a parameter. If you have this info, you can send it to the logger.
Once you have a more precise idea of where the slow-down is happening, it will be a lot easier to figure out how to fix it.