Showing posts with label VB6. Show all posts
Showing posts with label VB6. Show all posts

Sunday, October 04, 2015

[VB6] Map and Reduce

Following on from a previous posting about Fluent VB6 we look now at two routines which pop up a lot these days: Map and Reduce. On offer here is one way of implementing these two functional programming stalwarts. They are presented here as part of a FluentVB6 object, but could just as easily be declared and used separately.

I'm implementing these using the
MSScript object which also appeared in the aforementioned posting. This makes available to the programmer, out of the box, the functionality of two scripting languages, VBScript and JScript. Other languages are available within the Windows Scripting Host environment and these could also be used (e.g. PerlScript as per the link.)

The code below tests the Map and Reduce functions. After this the implementation will be discussed.
Dim words As New Collection
    words.Add "cat"
    words.Add "dog"
    words.Add "cow"
    words.Add "wolf"
    words.Add "rat"
    
    Dim F As New FunctionalObject
    
    Dim upperwords As New Collection
    Set upperwords = F.WorkingWith(words).Map("UCase(Value)").AsCollection()
    
    Dim concatenated As Variant
    F.Reset
    concatenated = F.WorkingWith(upperwords).Reduce(vbNullString, "InitValue = InitValue & Value").asValue()
    
    Dim counted As Variant
    counted = F.Reset().WorkingWith(upperwords).Reduce(0, "initValue = initValue + Len(Value)").asValue()
For the sake of simplicity, I'm limiting things to Collections in, Collections and Variants out. The code could be readily changed to handle Arrays, Dictionaries and other data structures.

The class is called FunctionalObject and it begins with
Option Explicit

Private workingCollection As Collection
Private incomingCollection As Collection
Private workingValue As Variant
Private SC As ScriptControl

Private Sub Class_Initialize()
    Set workingCollection = New Collection
    Set incomingCollection = New Collection
    workingValue = vbEmpty
    Set SC = New ScriptControl
End Sub
No surprises there. Again, the ScriptControl object is the OCX added via the Project menu as a Reference rather than as a Component. One could late-bind with CreateObject but there's not much point in this case.

Next the public function to receive the incoming collection
Public Function WorkingWith(inCol As Collection) As FunctionalObject
    Set incomingCollection = inCol
    Set WorkingWith = Me
End Function
Reset clears the workingCollection (in the event that you reuse the currently instantiation of the FunctionalObject rather than instantiate another one.)
Public Function Reset() As FunctionalObject
    Dim i As Integer
    For i = 1 To workingCollection.Count
        workingCollection.Remove 1
    Next
    workingValue = vbEmpty
    Set Reset = Me
End Function
wrapText you'll seen before from the previous posting. It just makes the incoming collection's value palatable to VBScript.
Private Function wrapItem(v As Variant) As String
    If VarType(v) = vbString Then
        wrapItem = Chr$(34) & v & Chr$(34)
    Else
        wrapItem = CStr(v)
    End If
End Function
Next the Map function. The ScriptControl language is set to VBScript and the "safe subset" of script language functions is selected. Then the code iterates through each element of the incoming collection, and sets a VBScript place-holder variable called Value to that element. Next the map script is evaluated in the context of Value, the result being added to the working collection.

The example at the top of page has the map script as "UCase(Value)", so the value stored in the working collection is the uppercase of the value in the incoming collection.
Public Function Map(Optional script As String = "Value") As FunctionalObject
    SC.Language = "VBScript"
    SC.UseSafeSubset = True
    Dim i As Integer
    For i = 1 To incomingCollection.Count
        SC.ExecuteStatement "Value = " & wrapItem(incomingCollection.Item(i))
        workingCollection.Add SC.Eval(script)
    Next
    Set Map = Me
End Function
Reduce works in a similar manner except the result is a variant. There is the expectation that the reduce script will somehow work toward deriving a single value from the incoming collection, thus the use of an second place-holder called InitValue. The first parameter of the Reduce call is stored in InitValue with the expectation that the reduce script will refer to it and to the Value place-holder.

For example, one of the examples from the first code block reads, in part,
Reduce(0, "initValue = initValue + Len(Value)").
This reduces the collection to a value accruing the lengths of the strings assumed to be in the incoming collection.

Both parameters to Reduce are marked as optional. If neither is specified, the Reduce does nothing except set Value to InitValue, effectively filling the working collection with as many zeroes as there are items in the incoming collection.
Public Function Reduce(Optional initval As Variant = 0, Optional script As String = "Value = InitValue") As Variant
    SC.Language = "VBScript"
    SC.UseSafeSubset = True
    Dim vAnswer As Variant
    Dim vItem As Variant
    Dim vResult As Variant
    Dim i As Integer
    SC.ExecuteStatement "InitValue = " & wrapItem(initval)
    For i = 1 To incomingCollection.Count
        vItem = incomingCollection.Item(i)
        SC.ExecuteStatement "Value = " & wrapItem(vItem)
        SC.ExecuteStatement script
    Next
    workingValue = SC.Eval("InitValue")
    Set Reduce = Me
End Function
Finally the two output functions, asCollection and asValue. The former copies the working collection to an answer collection and returns that to the caller. asValue returns the working value from the Reduce.
Public Function AsCollection() As Collection
    Dim answerCollection As New Collection
    Dim i As Integer
    For i = 1 To workingCollection.Count
        answerCollection.Add workingCollection.Item(i)
    Next
    Set AsCollection = answerCollection
End Function

Public Function asValue() As Variant
    asValue = workingValue
End Function
I will make the sources available on Github in the near future.

© Copyright Bruce M. Axtens, 2015

Friday, October 02, 2015

[VB6] What about Me a.k.a. Method Chaining a.k.a. Fluent VB6

This is nothing to do with Shannon Noll's song, What about Me. No, it's about Method Chaining, the mechanism described in Fluent Javascript and making that same mechanism available to the VB6 programmer.

But first, kudos. I am grateful to Vidar Løvbrekke Sømme. On his zbz5.net blog, he posted an article called
Bending Vb6 in the functional direction. In that article, he gives his take on method chaining. What follows is my take on the subject.

Having found a way to get
VB6 running without emulation in 64bit Windows 10, I've been encouraged to get back into it and revisit some of my older projects.

So what's this got to do "What about Me"? Well, the thing that makes method chaining possible in JavaScript is the this keyword. The VB6 equivalent keyword is Me.

The challenge is to create a mechanism which allows something like Vidar's
concatenated = List.From(originalCollection).SelectProperty("Property").Concat(",")

— But this isn't too bad, is it?

What I've done doesn't use Map or Reduce (I have implemented those but differently and I'll deal with both in a subsequent post.)

First, launch the VB6 IDE, create a new project and remove the default form. Next create a class and call it SelectorObject. In the code editor for the class enter class variables, viz
Dim cWorking As Collection
Dim dWorking As Dictionary
Dim aWorking() As Variant
Dim iWorking As Integer
Dim cOriginal As Collection
Dim dOriginal As Dictionary
Dim aOriginal() As Variant
Dim sPattern As String
Dim bPatternDefined As Boolean
Dim oScript As ScriptControl

Private Enum FROMS
    FromCollection = 1
    FromDictionary = 2
    FromArray = 3
    Fromstring = 4
End Enum

Dim source As FROMS
The above is out of my own project so there's extra stuff in there that goes beyond Vidar's original. Ultimately I'd like to be able to hand a Collection, Dictionary, Array or String to the class and be able to pull out of it a Collection, Dictionary, Array or String. The code you'll see is part way to realising that.

Notice also the Dim oScript As ScriptControl. To make an element selection mechanism, I'm calling in the
MSScript OCX object (as a Project Reference).

Next comes the class initialisation
Private Sub Class_Initialize()
    Set cWorking = New Collection
    Set dWorking = New Dictionary
    sPattern = vbNullString
    bPatternDefined = False
    iWorking = -1
    Set oScript = New ScriptControl
    oScript.Language = "VBScript"
    oScript.UseSafeSubset = True
End Sub
Next the first public function Selecting which will introduce the selection statement. I'd love to be able to use Select, but that's a VB6 reserved word. Notice the use of the Me keyword. Because it's an object, it has to be assigned to the function name (as a return value) using Set.
Public Function Selecting() As SelectorObject
    Set Selecting = Me
End Function
Next the From public function. This ascertains the type of the incoming variable and assigns it to a relevant worker object. An enum takes care of letting the class know what was passed in.
Public Function From(c As Variant) As SelectorObject
    If TypeName(c) = "Collection" Then
        Set cOriginal = c
        source = FromCollection
    ElseIf TypeName(c) = "Dictionary" Then
        Set dOriginal = c
        source = FromDictionary
    Else
        If VarType(c) = vbArray Then
            aOriginal = c
            source = FromArray
        Else
            aOriginal = Split(c, "")
            source = Fromstring
        End If
    End If
    Set From = Me
End Function
Next comes the Where function. Rather that doing any further processing, the Where simply accepts the 'where' text (a script fragment for MSScript to use later) and a flag to say we have it is set.
Public Function Where(pattern As String) As SelectorObject
    sPattern = pattern
    bPatternDefined = True
    Set Where = Me
End Function
Next a helper function which puts double quotes around strings and leaves other data types undecorated. This exists because the current element in the Collection/Dictionary/Array/String is assigned to a VBScript variable before the Where script is evaluated. Being a VBScript assignment statement, double quotes need to be around strings. At this point there is no special handling for booleans and dates etc.
Private Function WrapData(data As Variant) As String
    If VarType(data) = vbString Then
        WrapData = Chr$(34) & data & Chr$(34)
    Else
        WrapData = CStr(data)
    End If
End Function
Finally, one of the three output routines. This is where all the hard work is done. Notice that these functions don't specify SelectorObject as the return type. In this first case, a Collection is returned. The Dictionary output routine has not been written.

Notice also that, if a Where clause has been specified, the current item is set as a variable to the MSScript engine, and then the 'where' text is evaluated by MSScript, using the VBScript language. The result is interpreted as a boolean to decide whether or not to include the current item in the output collection. Otherwise, everything that went in comes out.
Public Function asCollection() As Collection
    Dim i As Integer
    Dim b As Boolean
    For i = 1 To cOriginal.Count
        If bPatternDefined Then
            oScript.ExecuteStatement "Value = " & WrapData(cOriginal.Item(i))
            b = CBool(oScript.Eval(sPattern))
            If b Then
                cWorking.Add cOriginal.Item(i)
            End If
        Else
            cWorking.Add cOriginal.Item(i)
        End If
    Next
    Set asCollection = cWorking
End Function
Next, a string output routine, with an optional argument to supply an inter-item separator.
Public Function asStringSeparatedBy(Optional separator As String = vbNullString) As String
    Dim i As Integer
    Dim b As Boolean
    Dim answer As String
    For i = 1 To cOriginal.Count
        If bPatternDefined Then
            oScript.ExecuteStatement "Value = " & WrapData(cOriginal.Item(i))
            b = CBool(oScript.Eval(sPattern))
            If b Then
                answer = answer & cOriginal.Item(i)
                If i < cOriginal.Count Then
                    answer = answer & separator
                End If
            End If
        Else
            cWorking.Add cOriginal.Item(i)
            If i < cOriginal.Count Then
                answer = answer & separator
            End If
        End If
    Next
    
    If bPatternDefined And Right$(answer, 1) = separator Then
        answer = Mid$(answer, 1, Len(answer) - 1)
    End If
    
    asStringSeparatedBy = answer
End Function
And finally, an output routine returning an array. The array element variable, iWorking, was initialised as -1, thus the pre-increment. Perhaps something else could be done about array initialisation to make things more efficient than a ReDim Preserve for each element.
Public Function asArray() As Variant
    Dim i As Integer
    Dim b As Boolean
    For i = 1 To cOriginal.Count
        If bPatternDefined Then
            oScript.ExecuteStatement "Value = " & WrapData(cOriginal.Item(i))
            b = CBool(oScript.Eval(sPattern))
            If b Then
                iWorking = iWorking + 1
                ReDim Preserve aWorking(iWorking)
                aWorking(iWorking) = cOriginal.Item(i)
            End If
        Else
            iWorking = iWorking + 1
            ReDim Preserve aWorking(iWorking)
            aWorking(iWorking) = cOriginal.Item(i)
        End If
    Next
    asArray = aWorking
End Function
That's all that's currently in the SelectorObject.

Now, out in the main module, in the Main subroutine, a couple of tests.
Dim words As New Collection
    words.Add "cat"
    words.Add "dog"
    words.Add "cow"
    words.Add "wolf"
    words.Add "rat"

    Dim S As New SelectorObject

    Debug.Print S.Selecting().From(words).Where("instr(Value,""o"") > 0").asStringSeparatedBy(",")
    Debug.Print Join(S.Selecting().From(words).Where("instr(Value,""a"") > 0").asArray(), "|")
The output being
dog,cow,wolf
cat|rat
So that's my take on Fluent VB6.

In my experimentations I have used this technique to re-implement Google Adwords SOAP request code that I originally wrote in JScript, viz
Dim pred As String
    Dim sele As String
    Dim campaignID As Long
    campaignID = 1000222

    Dim OPE As New OperationObject
    Dim PRE As New PredicateObject
    Dim SEL As New SelectObject
 
    pred = PRE.Field("CampaignId").Operator("=").Value(campaignID).toXML()
    sele = SEL.Fields("BudgetId").Fields("Amount").Predicates(pred).toXML()
    'sele = SEL.Fields("BudgetId").Fields("Amount").Predicates(PRE.Field("CampaignId").Operator("=").Value(campaignID).toXML()).toXML()

    Dim bid As Long
    bid = 2199199
    Dim amt As Long
    amt = 1000000
    Dim oper As String
    oper = OPE.Operator("SET").Field("budgetId").Value(bid).Amount(amt).toXML()

    Debug.Print pred
    Debug.Print sele
    Debug.Print oper
outputting
<field>CampaignId</field><operator>EQUALS</operator><values>1000222</values>
 <fields>BudgetId</fields><fields>Amount</fields><predicates><field>CampaignId</field><operator>EQUALS</operator><values>1000222</values></predicates>
 <operations><operator>SET</operator><operand><budgetId>2199199</budgetId><amount><microAmount>1000000</microAmount></amount></operand></operations>
Okay, that's it. A long-winded posting, this one. If you have any questions, use the comments.

Enjoy!


© Copyright Bruce M. Axtens, 2015

Sunday, September 27, 2015

[VB6] Installation in Windows 10

I'm running Windows 10 Home, 64bit. We have legacy code that needs to be modified and recompiled. Folk who find such discussions unpalatable should read no further.

I found help for this process in two places:Forty Pound Head's
Install VB6 on Windows 8 and CodeProject's How to install Visual Studio 6 on Windows 7 professional 64bit

(There's another blurb at
SoftwareOK that I'm hanging on to just in case I meet any unforseen gotchas.)

In my case, the creation of the zero length file, MSJAVA.DLL, had to be done in C:\Windows\System32 rather than C:\Windows. I had put it in the latter and ended up with, for a while, the looping install scenario described by fortypoundhead.

The recommendation
Ensure Data access components is deselected. If it's not deselected, setup will hang!

— fortypoundhead

was interesting because the Enterprise installer asserted that the data access tools are required. However, I went with the recommendation as the thought of a hanging setup didn't appeal.

I turned off SourceSafe, FoxPro and InterDev as well. I opted to have environment vars stored in VCVARS32.BAT rather than have them registered: I feared mayhem might occur given the pre-existing Visual Studio Community 2013 and Community 2015 installs.

The install was very speedy.

The installer insisted on a reboot at the end. But before I said yes to that, I set the relevant Compatibility Settings for VB6.EXE as per fortypoundhead, started VB6.EXE, wrote a quick button on a form app, compiled it to EXE and then ran it from outside VB6. No problems at all!

Now I will have to run the various updaters and install some helper apps. But so far it looks like we could get another 17 years out of VB6.


© Copyright Bruce M. Axtens, 2015

Wednesday, September 11, 2013

[Open Source] Github and Bitbucket

The blog's been very quiet lately. Too quiet. But I've been busy.

I got started on
Github, but the only thing I have there is a failed attempt at building a JSON library for the Euphoria programming language.

Most of the more recent stuff is happening on Bitbucket where I have various implementations (javascript v1 and javascript v2 (minimal), euphoria, vb6, php, and c#) of Steve Skiena's integer bignums library and a few other bits and bobs, namely:


All projects are in varying stages of completeness, and looking for users and improvers.


© Copyright Bruce M. Axtens, 2013

Wednesday, October 14, 2009

[Jabaco] JaCOB and WScript.Network

I wonder about myself sometimes. Am I sane? For instance, I’ve taken recently to learning and developing with Jabaco. This would seem from one angle, good, and from another, daft. 

Jabaco is a simple programming language with a Visual Basic like syntax. Jabaco enables you to create powerful software for all Java supported operating systems.

So there you have it: a VB6-alike (more or less) syntax targeting the JVM. In my experience, people bagging VB syntax are in the majority. People bagging Java aren’t infrequent either. And I know Java about as well as I know Babylonia Cuneiform.

That said, it’s been a bit of fun figuring things out in Jabaco, and discovering just how effectively one can hook into the Java subsystem.

The code below demonstrates The JACOB Project: A JAva-COM Bridge talking to WScript.Network. Comments are a bit sparse, but VB folk should be able to figure it out quick enough.

Public Sub main(ByJava args() As String)
   Dim myArgs() As String
   myArgs = args
   Dim oShell As ActiveXComponent
'ActiveXComponent is exported by JACOB and referenced in the IDE
'(yes, there’s an IDE, and it’s pretty good too) Set oShell = New ActiveXComponent("WScript.Network") Dim a As String Dim b As String Dim c As String Dim dShell As Dispatch
'Dispatch also part of JACOB. Nice that it uses the standard jargon Set dShell = oShell.getObject() a = Dispatch.call(dShell,"UserDomain") b = Dispatch.call(dShell,"ComputerName") c = Dispatch.call(dShell,"UserName") MsgBox (a & ", " & b & ", " & c) comthread.Release() End Sub

The IDE makes possible ‘compiling’ to an EXE. The code above compiles to about 400K. Of course, the presence of a JRE is implied. And it runs, nicely.

I think I’ll be spending more time with Jabaco. It’s got a lot of promise, especially for the VB-deranged like me.

© Copyright Bruce M. Axtens, 2009.

Thursday, July 02, 2009

[VB6] Using VBScript's Escape and UnEscape

Something again from an answer I gave on StackOverflow. This one demonstrates how to use MSScriptControl.ScriptControl to make VBScript's Escape and Unescape functions available to the VB6 programmer.

The best thing would be add MSSCRIPT.OCX to the project, but for the sake of demonstration, I'll use CreateObject instead.


Using the code is easy.


It's that simple. And the technique can be used to get at other VBScript functionality. In fact, .Language can have values other than "VBScript", making it possible to interface to any language with a Windows Scripting Host presence.

© Copyright Bruce M. Axtens, 2009

Tuesday, May 19, 2009

[VB6] MAXDOUBLE, MINDOUBLE, +INFINITY, -INFINITY and NaN

VB6 doesn't seem to be able to specify that a Double contains MAXDOUBLE, MINDOUBLE, +INFINITY, -INFINITY or NaN. (I could be wrong on that as there may be some undocumented feature hiding in there somewhere.)

Anyway, I came up with a way of storing these values into Doubles so that they could be used in a Complex Numbers library I've been writing.

Essentially, I create an 8 byte array, load it up with the relevant values, and then, using API calls and the VarPtr function, store the contents of the array into the storage used by the Double.

Here's the code. First the declarations.


Then the routine that does the work.


Finally, a slice out of the Complex Numbers project demonstrating the use of some of these declarations.


Enjoy!


© Copyright Bruce M. Axtens, 2009

Tuesday, June 10, 2008

[Visual BASIC 6] BOOST (v3)

BOOST got another look-in recently. I had been using it exclusively to do "realtime" and "kill". This particular time I tried to do an "abovenormal" -- instead it did a "kill".

In the process of finding and fixing that bug, I replaced the Collection mechanism with the augmented Collection supplied by the "Class Builder" add-in.

The source code is essentially the same as before but with a few changes. First the SysVars and Var classes. (The comments are predominantly those supplied by the add-in.)

First, SysVars.cls:


Next, Vars.cls (it's tiny but it could hold so much more):


Now the main module. The first thing that is different here, is that nLevel is now a Long. Integer doesn't handle the ABOVENORMAL_PRIORITY value without complaint.

Next change is removing the reference to Collection, changing it to SysVars and setting up the values.

The benefit of using SysVars and that approach to the Collection object is that we now have an .Exists method, so we now have a tidier way of setting nLevel and trapping invalid settings thereof.



All the other routines are as before.

I haven't gotten around to extending this tool to do stuff to remote computers. It's not hard; killing a process on another machine means another value for sComputer in KillProcess, and, of course, the relevant privilege level.

© Copyright Bruce M. Axtens, 2008

Thursday, February 21, 2008

[Perl/PDK/PerlCtrl] Returning an array of arrays for VB6/VBScript

For the last few weeks I've been trying to get used to working with the Perl Development Kit (PDK 7.1) from ActiveState, particularly PerlCtrl.

One of the challenges I've been facing lately is how to return an array of arrays. That is, returning something that in VB6 or VBScript might be represented as Array( Item, Array( Item, Item ), Item ).

Kudos to
Perl Monks, who have provided a function which converts a Perl array to a VB array. Interestingly, it also copes with said converted array being embedded in another array and with that array being converted as well. (Bodes well for other projects.)

So what we have below is some Perl code, a PerlCtrl wrapper around a WMI call to list System Services. The wrapper returns a two element array, the first element being an array of all the services which are running, and the second an array of all the services which have been stopped.

Note that without 'use Win32::OLE qw(in);' you can't access the elements of the $colItems collection, as it enables the use of 'in' in the foreach. Similarly, you need Win32::OLE::Variant for all the Variant() calls.

Please don't ask me to explain what's going on in the convertArrayToVBArray sub, because I have no idea. Anyone know?



The second bit of code is a VBScript testing the COM DLL. It's a assumed that you've run the above code through PerlCtrl, generated a DLL and registered it with RegSvr32.



I'm having a lot of fun with Perl. I've been able to take a lot of Perl functionality (Tree::Nary, Data::Trie, Lingua::EN::Inflect, Algorithm::LCSS, Algorithm::Knapsack, Algorithm::BinPack, Algorithm::Bucketizer, Algorithm::Permute, Algorithm::SetCovering, String::LCSS and Statistics::Benford) and turn it into something that VB6 and VBScript can use. I'm impressed. Would that every scripting language had this kind of power.

© Copyright Bruce M. Axtens, 2008

Monday, December 24, 2007

[FORTRAN] Making a DLL for VB6

As a break from trying to figure out how to get a pattern matching library DLLified (and UTF-32 sensitive), my boss gave me something else to do, something on the one hand similar (wrap something in a DLL) and on the other quite different (wrap two knapsack algorithms done in Fortran 90).

The knapsack code is derived from
Algorithm 4.9, Donald Kreher and Douglas Stinson, Combinatorial Algorithms, CRC Press, 1999, page 125.
and was downloaded from FORTRAN Source Codes, specifically, the COMBO library. Many thanks to John Burkardt for making this resources available.

The files used from the COMBO library were knapsack_01.f90, knapsack_rational.f90, knapsack_reorder.f90, rvec_backtrack.f90 and r_swap.f90, and were compiled using Compaq Visual Fortran.

Seeing as CVF is no longer available (except perhaps on eBay) you might get away with the
Intel® Fortran Compiler for Windows, SilverFrost's FTN95 or g95.

Don't try it with
Watcom Fortran, however, because that only does Fortran 77. Mind you, if the algorithms you're wrapping are F77 ones from NIST's 'Dictionary of Algorithms and Data Structures' or Steven Skiena's The Stony Brook Algorithm Repository you may do okay with the Watcom offering or even GNU's g77.

I really like Fortran. I remember writing some VAX-11 FORTRAN back in 1983 (along with a bit more VAX-11 COBOL) and found it quite powerful and expressive. I know that there's been a fairly widespread pooh-pooh-ing of Fortran in recent years, but I try not to be influenced by peer-group pressure, preferring to actually see for myself whether a language is any good or not.

Okay, off the soap box and down to the code. I'm not going to bother posting all the COMBO code, just what I wrote to wrap it.

These are the three exposed routines and have essentially the same framework: a subroutine header, two declarations controlling symbol names and method of access, the call to the COMBO routine, the return and the end of subroutine. Really, really, simple. Gobsmackingly simple, compared to some other languages I could mention. Rather embarrasing really ... so little to show.

If you're wondering why I explicitly state that the parameters are being passed by reference (!dec$ attributes reference ...), it's because a side effect of specifying "stdcall" is that it forces the passing mechanism into pass-by-value, and in this case, quite a few of the parameters are being modified by the subroutine.



The other routines are linked in during the compile and I end up with a nice little .DLL.

Okay, so maybe there's something more to show on the VB6 side.

First off, the declares. VB6 defaults to pass-by-reference, thus the missing "ByRef" ahead of each parameter.



Next the test code which is derived from TEST094 and TEST095 found inside COMBO's combo_prb.f90.

Do note that the floating point variables are defined here as Single. This seems to be the best match to an undecorated Fortran REAL. Maybe if I'd tweaked the COMBO code to REAL*8, I could have used Double on the VB6 side.



Hmm ... nothing much amazing there either. But that's how it was with wrapping the Fortran. Simple and powerful at the same time. So far as I'm concerned, if we ever have to rewrite Protium, Fortran's going to be very near the top of the list.

© Copyright Bruce M. Axtens., 2007

Thursday, December 20, 2007

[Visual BASIC 6] BOOST (v2)

I revisited BOOST this week. A friend had pointed out to me some time back that /P: and /L: were a bit of a pain when one wanted to speed something up quickly, or kill it quickly. So this version changes the command line handling.

This version also demonstrates how to kill a process. In fact it kills every process of the same name. I wish I'd had this a couple of years ago when I was doing stuff with Excel -- I'd end up with multiple Excel sessions which I would then have to kill off one-by-one through the Task Manager. (I eventually ended up installing Cygwin so that I could script the process.)

If you remember the original VBScript of BOOST and the first VB6 version, you'll see some similarities.

I've given up on using
setsubsys in favour of using VBAdvance (which is now freeware) to handle the compile-to-console-app process. I'm still compressing the app using UPX.

First a few constants and the declarations, found on
vb.mvps.org, for doing console I/O.



Next, a few supporting functions. First off the rank, is an invocation syntax display. Notice that priority can now be specified either by a number (0 to kill, 1-6 for low to realtime) or by name (kill, low, belownormal, normal, abovenormal, high, realtime).



Second, the command line parser. This splits on space and tab and is based on code found at
microsoft.public.vb.winapi



Third, the two routines which wrap the WMI functions to set priority and to kill process. The latter was found at
tek-tips. If I remember correctly, the places in each routine where it says
sComputer = "."
could, in fact, have some other computer's name. If you had sufficient privilege you could be tweaking or killing a process on someone else's machine.



Finally, the Main subroutine. Notice the additions to the priorities collection. Note also the change in the way the command line is handled.

Because there's no .Exists method on Collections, the .Item call is wrapped in an 'On Error' so that a non-existent item situation is caught appropriately.



What's next with this thing? I suppose one could explore the possibility of influencing the lives of applications on other computers:
BOOST Skype.exe kill /C:GUEST_01
There's also the fact that the commandline parser doesn't properly handle double-quoted names. If an .exe had a space it its name, you wouldn't be able use BOOST on it.

Finally, here's a short batch file (killall.bat) I use for killing applications en masse. It was originally designed to get around the 'typing the /P and /L' issue, but I'm so used to using it now ...



An example invocation:
killall excel.exe outlook.exe


© Copyright Bruce M. Axtens, 2007

Monday, December 03, 2007

[Ada (GNAT/GCC)] VARPTR, GNAT and data "transput"

Okay, have got it figured (soli Deo gloria!)

The following listings demonstrate the passing of a two part Type containing two Singles from VB6 into a GNAT/GCC DLL. Once inside, the elements of the Type are swapped and control returns to VB6. VB6 then displays the results.

As before, the code has been worked out in the context of the afore-posted api project. If you want to play with this, just take the earlier work and tweak it appropriately.

First up, the additions to api.ads:



Next, the additions to api.adb:



API.DEF must also be modified, adding swapsingles to the list of EXPORTS.

Next, some VB code to demonstrate ...



... and part of a sample output.



The project that all this was part of was done in both GNAT/GCC and ObjectAda 7.2.2. Interestingly, ObjectAda's Float is equivalent to VB6's Double, whereas GNAT/GCC's Float is equivalent to VB6's Single. Does anyone know why?

By the way, regarding "transput", Wikipedia's article on
Algol states:
ALGOL 60 as officially defined had no I/O facilities; implementations defined their own in ways that were rarely compatible with each other. In contrast, ALGOL 68 offered an extensive library of transput (ALGOL 68 parlance for Input/Output) facilities.
A extensive library of transput facilities is something you'll find in Protium, too.

© Copyright Bruce M. Axtens, 2007

Wednesday, November 28, 2007

[Ada (GNAT/GCC)] Passing data with VB6's VARPTR to Ada

In the current project, I've been trying to figure out how to pass data to Ada. Because I'll be passing Long, Double (Float), and two Types to emulate a LongLong and a DoubleDouble, I decided I'd use VB's VarPtr and have Ada sort the mess out on its side.

After the loss of a little hair, the reading of bits of Michael B. Feldman's
Software Construction and Data Structures with Ada 95, and with help from Martin Krischik and Simon Wright, I came up with the following.

The code was worked out in the context of the afore-posted api project. If you want to play with this, just take the earlier work and stuff this into it.

First up, the additions to api.ads:



Next, the additions to api.adb:



Next, some VB code to demonstrate ...



... and a sample output.



I'm in the process now of working out how to access the LongLong and DoubleDouble Types. Shall keep you posted.

By the way, the return value will eventually be a pointer as well. That'll keep the VB programmer on his toes.

© Copyright Bruce M. Axtens, 2007

Friday, November 23, 2007

[Ada (GNAT/GCC)] Creating DLLs for VB6

And finally, after much prayer and perspiration, the GNAT/GCC version. Again, thanks to Roger Pearse for The Noddy Guide to using ADA code with Visual Basic. I'm sure there was someone else [This space set aside for an attribution.]

First up, api.ads, which declares the functions and exports them.
  • function Factorial, which accepts a Win32.LONG and returns a Win32.LONG (I gave up on C.int as it was too small for a Factorial of 16)
  • function GetCount, which returns the value of the global Count (another Win32.LONG)
  • procedure Mangle, which accepts a Win32.LPWSTR and attempts the same dangerous stuff that Bob6 does in the ObjectAda version.
  • function Pattern, which returns a long pointer to some string data
  • procedures Initialize_API, and Finalize_API, which I understand are mandatory. (If someone knows better, please tell me.)


Next, api.adb, where the declarations are explained.

I'm still working through the issue of what to do with Mangle. Perhaps two routines are needed. The first to ask the DLL how much space is needed, with VB the allocating it, and the second then actually poking the data into the allocated space.

Pattern returns a pointer to the string data. I rediscovered the technique for reading it out and that's in the VB code below. It also seems that, for as long as the DLL is loaded, the data stored in foo is available for transfer.



api.def lists the symbols exported, and is needed by gnatdll.



Now, pulling it all together requires some handwaving on the commandline. From one of the demos I modified the make.bat to look like this:



The output from a run of these is as follows:



Now the VB code. Take note of a few things:
  • Exporting as DLL generates decorated names, but dlltool was able to resolve the decorated ones with the undecorated ones specified in api.def
  • For reasons I remember reading somewhere, initialize_api and finalize_api are mandatory, and must be the first and last calls to the DLL
  • Seeing as woof woof woof woof is longer than foo man chew, s ends up containing foo man chew f woof
  • As mentioned before, I rediscovered some code for getting data from a pointer and put that in. It's the PointerToString function at the bottom.


Finally, a sample run under VB6



Next? Well, there's the issue of how to export overloaded functions and procedures. And there's the question of how to pass in complex data types, like records and the like. I'll keep you posted.

(By the way, I'm not doing this merely for the fun of it, but also in pursuit of the fulfillment of my employer's expectations. So more will follow.)


© Copyright Bruce M. Axtens, 2007

Thursday, November 22, 2007

[Ada (GNAT/GPL)] Creating DLLs for VB6

As promised, the GNAT/GPL code. Based on Roger Pearse's The Noddy Guide to using ADA code with Visual Basic.

How I ended up with the names api.ads, api.adb and api.def is an artefact from something I was reading. [This space set aside for an attribution.]

First up, api.ads, which declares the functions and exports them.
  • function Factorial, which accepts a C.int and returns a C.int
  • procedure Mangle, which accepts a Win32.LPWSTR and attempts the same dangerous stuff that Bob6 does in the ObjectAda version. Still working on this one.
  • function Pattern, which returns a Win32.LPWSTR
  • procedures Initialize_API, and Finalize_API, which I understand are mandatory. (If someone knows better, please tell me.)


Next, api.adb, where the declarations are explained.

Mangle replaces the incoming string with something of its own. If the incoming string is larger or the same size then everything's more or less okay. (Less in the latter case, but at least it doesn't crash.) However, if the incoming string is shorter, VB truncates up to the length it knows about, and who knows what damage we've done with whatever gets overwritten by the rest of foo.

Pattern returns a pointer to the string data. Not knowing a lot about Ada, I don't know if the contents of foo will still be there by the time VB looks it and reads out.


api.def lists the symbols exported, and is needed by gnatdll.


Now, pulling it all together requires some handwaving on the commandline.


The output from a run of these is as follows:


Now the VB code. Take note of a few things:
  • Exporting as DLL generates decorated names, but gnatdll was able to resolve the decorated ones with the undecorated ones specified in api.def
  • For reasons I remember reading somewhere, initialize_api and finalize_api are mandatory, and must be the first and last calls to the DLL
  • Seeing as woof woof woof woof is longer than foo man chew, s ends up containing foo man chew woof


Next stop is to get this working with "GNAT/GCC MS-Windows MinGW 4.2.1 R0". I would prefer to be working under the GMGPL.


© Copyright Bruce M. Axtens., 2007

Tuesday, November 20, 2007

[Ada (ObjectAda 7.2.2)] Creating VB6 DLLs

Creating DLLs for use by VB6 seems to be somewhat of a dark art. From what Roger Pearse, in his The Noddy Guide to using ADA code with Visual Basic says, it doesn't appear to be something easily achieved.

So far I've achieved it, to a certain extent, with ObjectAda and GNAT/GPL. The code for the ObjectAda implementation is below. I'll post the GNAT/GPL stuff in the next couple of days, God willing.

BO_DLib.adb, BO_D.adb and BO_D.ads are all based, albeit rather loosely, on sample files in winapi\samples\petzold\ch19\edrlib\

First up, BO_DLib.adb



Next, the spec file, BO_D.ads

There are six routines:
  • procedure Bob1 calls dobox to show a messagebox.
  • function Bob2 returns a long value, 1.
  • function Bob3 receives a long and adds 21 to it.
  • procedure Bob4 receives a long pointer to a string and passes it to dobox for display.
    I can pass Unicode to it and have it rendered appropriately.
  • function Bob5 receives a long pointer to a string and returns a long being the length of the string.
  • function Bob6 receives a long pointer to a string and catenates a string to it.
    "Danger, Will Robinson!" ... this is not how to do it and VB complains bitterly.
Okay, there are seven routines, but dobox isn't exported. It takes a pointer to a wide string and passes it to Win32.Winuser.MessageBoxW. I got dobox from a sample file as well.



And, finally, BO_D.adb, where the definitions given in BO_D are fleshed out.



Now the VB code. Take note of a few things:
  • I declared the functions and procedures as DLL_Stdcall in BO_D.ads so the routines come in decorated, thus the use of Alias in the Declare line. I used Microsoft's Dependency Walker to discover to what extent the names had been mangled.
  • A reference to SCRRUN.DLL was added to the project, making possible the use of the FileSystemObject.
  • Bob6 barges past the limit set for the string by VB, so things get a bit unstable. I'm still working through how to pass strings back.


By the way, if you think my code sucks, say so. I'm a total newb when it comes to Ada. Critiques are always welcome. King Solomon said, "The wise person accepts instructions." and also "Better is open rebuke than hidden love."


© Copyright Bruce M. Axtens., 2007