Chapters

  1. Introduction
  2. Structure
  3. Packaging
  4. Logging
  5. Configuration
  6. Debugging EXEs
  7. Handling errors
  8. Testing
  9. Documentation
  10. Make
  11. Providing help
  12. Scheduled Tasks
  13. Windows Services
  14. Windows Event Log
  15. Windows Registry
  16. Creating SetUp.exe
  17. Regular Expressions
  18. Acre
  19. GUI
  20. Git

Appendices

  1. Windows environment vars
  2. User commands
  3. aplcores & WS integrity
  4. Development environment
  5. Special characters

Misc

Imagine the following situation: MyApp is started with a double-click on the DYAPP and, when tested, everything works just fine. Then you create a stand-alone EXE from the DYAPP and execute it with some appropriate parameter, but it does not create the CSV files.

In this situation, obviously you need to debug the EXE. In this chapter we’ll discuss how to achieve that. In addition we will make MyApp.exe return an exit code.

For debugging we are going to use Ride. (See the Dyalog manuals for information about Ride.) If enabled, you can use Ride to hook into a running interpreter, interrupt any running code, investigate, and even change that code.

We introduce a [RIDE] section into the INI file:

[Ride]
Active      = 1
Port        = 4599
Wait        = 1

By setting Active to 1 and defining a Port number for the communication between Ride and the EXE you can tell MyApp that you want ‘to take it for a ride’. Setting Wait to 1 lets the application wait for a ride. That simply means it enters an endless loop.

That’s not always appropriate of course, because it allows anybody to read your code.

If that's something you need to avoid, you have to find other ways to make the EXE communicate with Ride, perhaps by making temporary changes to the code.

The approach would be the same in both cases. In MyApp we keep things simple and allow the INI file to rule whether the user may ride into the application or not.

Copy Z:\code\v05 to Z:\code\v06 and then run the DYAPP to recreate the MyApp workspace.

Information

Note that 4502 is Ride’s default port, and that we’ve settled for a different port, and for good reasons. Using the default port leaves room for mistakes.

Using a dedicated port rather than using the default minimises the risk of connecting to the wrong application.

If you exported the EXE with the Console application checkbox ticked there is a problem. You can connect to the EXE with Ride, but all output goes into the console window.

That means you can enter statements in Ride but any response from the interpreter goes to the console window rather than Ride.

For debugging we therefore recommend creating the EXE with the check box cleared.

We want to make the ride configurable. That means we cannot do it earlier than after having instantiated the INI file. But not long after either, so we change Initial:

∇ (Config MyLogger)←Initial dummy
⍝ Prepares the application.
  Config←CreateConfig ⍬
  CheckForRide Config.(Ride WaitForRide)
  MyLogger←OpenLogFile Config.LogFolder
  MyLogger.Log'Started MyApp in ',F.PWD
  MyLogger.Log #.GetCommandLine
  MyLogger.Log↓⎕FMT Config.∆List
∇

We have to ensure Ride makes it into Config, so we establish a default 0 (no Ride) and overwrite with INI settings.

∇ Config←CreateConfig dummy;myIni;iniFilename
  Config←⎕NS''
  Config.⎕FX'r←∆List' 'r←{0∊⍴⍵:0 2⍴'''' ⋄ ⍵,[1.5]⍎¨⍵}'' ''~¨⍨↓⎕NL 2'
  Config.Debug←A.IsDevelopment
  Config.Trap←1
  Config.Accents←'ÁÂÃÀÄÅÇÐÈÊËÉÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝ' 'AAAAAACDEEEEIIIINOOOOOOUUUUY'
  Config.LogFolder←'./Logs'
  Config.DumpFolder←'./Errors'
  Config.Ride←0        ⍝ If not 0 the app accepts a Ride & treats Config.Ride as port number.
  Config.WaitForRide←0 ⍝ If 1 `CheckForRide` will enter an endless loop.
  iniFilename←'expand'F.NormalizePath'MyApp.ini'
  :If F.Exists iniFilename
      myIni←⎕NEW ##.IniFiles(,⊂iniFilename)
      Config.Debug{¯1≡⍵:⍺ ⋄ ⍵}←myIni.Get'Config:debug'
      Config.Trap←⊃Config.Trap myIni.Get'Config:trap'
      Config.Accents←⊃Config.Accents myIni.Get'Config:Accents'
      Config.LogFolder←'expand'F.NormalizePath⊃Config.LogFolder myIni.Get'Folders:Logs'
      Config.DumpFolder←'expand'F.NormalizePath⊃Config.DumpFolder myIni.Get'Folders:Errors'
      :If myIni.Exist'Ride'
      :AndIf myIni.Get'Ride:Active'
          Config.Ride←⊃Config.Ride myIni.Get'Ride:Port'
          Config.WaitForRide←⊃Config.Ride myIni.Get'Ride:Wait'
      :EndIf
  :EndIf
  Config.LogFolder←'expand'F.NormalizePath Config.LogFolder
  Config.DumpFolder←'expand'F.NormalizePath Config.DumpFolder
∇

As a result Config.Ride will be 0 if the INI rules that no Ride is permitted, otherwise the port number to be used by Ride.

We add a function CheckForRide:

∇ {r}←CheckForRide (ridePort waitFlag);rc;init;msg
 ⍝ Depending on what's provided as right argument we prepare for a Ride
 ⍝ or we don't. In case `waitFlag` is 1 we enter an endless loop.
  r←1
  :If 0<ridePort
      {}3502⌶0                     ⍝ Switch Ride off
      init←'SERVE::',⍕ridePort     ⍝ Initialisation string
      rc←3502⌶ini                  ⍝ Specify INIT string
      :If 32=rc
          11⎕Signal⍨'Cannot Ride: Conga DLLs are missing'
      :ElseIf 64=rc
          11 ⎕Signal⍨'Cannot Ride; invalid initialisation string: ',ini
      :ElseIf 0≠rc
          msg←'Problem setting the Ride connecion string to SERVE::'
          msg,←,(⍕ridePort),', rc=',⍕rc
          11 ⎕SIGNAL⍨msg
      :EndIf
      rc←3502⌶1
      :If ~rc∊0 ¯1
          11 ⎕SIGNAL⍨'Switching on Ride failed, rc=',⍕rc
      :EndIf
      {}{_←⎕DL ⍵ ⋄ ∇ ⍵}⍣(⊃waitFlag)⊣1  ⍝ Endless loop for an early RIDE
  :EndIf
∇

Notes:

Finally we amend the Version function:

∇r←Version
   ⍝ * 1.3.0:
   ⍝   * MyApp gives a Ride now, INI settings permitted.
   ...
∇

Now you can start Ride, enter both 'localhost' and the port number as parameters, connect to the interpreter or stand-alone EXE etc., and then pick Strong interrupt from the Actions menu to interrupt the endless loop; you can then start debugging the application.

Note that this does not require the development EXE to be involved: it may well be a runtime EXE.

NB You need a development licence to be legally entitled to Ride into an application run by the RunTime EXE (DyalogRT.exe).

Prior to version 16.0 one had to copy these files :

or these:

as siblings of the EXE. From 16.0 onward you must copy the Conga DLLs instead.

Neglecting that will make 3502⌶1 fail. Note that 2.7 refers to the version of Conga, not Ride.

Prior to version 3.0 of Conga every application (interpreter, Ride, etc.) needed their own copy of the Conga DLLs, with a different name.

Since 3.0 Conga can serve several applications in parallel. We suggest you copy the 32-bit and the 64-bit DLLs as siblings of your EXE.

If you forgot to copy the DLLs you will see an error Can't find Conga DLL. This is because the OS does not bother to tell you about dependencies.

You need a tool like DependencyWalker for finding out exactly what’s missing. (We said OS because this is not a Windows-only problem.)

Restartable functions

Not only do we try to exit functions at the bottom, we also like them to be restartable.

What we mean by that is that we want if possible a function – and its variables – to survive →1. That is not possible for all functions: for example, a function that starts a thread and must not start a second one for the same task, or a file was tied etc. But most functions can be restartable.

That means that something like this should be avoided:

∇r←MyFns arg
r←⍬
:Repeat
    r,← DoSomethingSensible ⊃arg
:Until 0∊⍴arg←1↓arg

This function does not make much sense but the point is that the right argument is overwritten; so one cannot restart this function with →1. Don’t do overwrite an argument without a very good reason. In this example, a counter is a better way to iterate. (Faster, too.)