Automotive Entrepreneur Travels Successful Road with Synergy
April 3, 2024Introducing SDI Build 2024.05.1447 – Now Available for Download
May 3, 2024What It Is
Microsoft .NET (previously called .NET Core) is a multi-platform framework. In terms of Synergy .NET, this means that an application targeting .NET 6 or .NET 8 can run on either Windows or Linux. With Synergy/DE version 12.2.1.1007 and higher, we’ve increased the capabilities of the Synergy .NET Linux runtime and included support for the Synergy windowing API and UI Toolkit. This article gives an overview of building and running Synergy .NET programs for Linux.
What It Can Do
With Synergy .NET, you can build terminal-based applications that will run on Windows or Linux. These applications will have similar behavior to equivalent traditional Synergy programs running on Linux, including cell-based UI for applications that use Toolkit or the windowing API. While this may be useful in the development of new applications, the primary use case is to provide a migration path for existing applications currently running on Linux (or other Unix-based operating systems). These programs can now be rebuilt for Synergy .NET with few or no changes to UI code. This will allow programs to take advantage of the capabilities on .NET and can provide the basis for additional future development. This may also be useful as a migration path from Unix-based systems to Windows, as terminal-based Synergy .NET applications will run with similar behavior on both Linux and Windows.
What It Can’t Do
Synergy .NET does not allow for Windows-like graphical user interfaces for applications running on Linux. Applications must be terminal-based, and any windowing capabilities will be implemented using a cell-based display and keyboard-driven control, as with traditional Synergy applications on Unix. Consequently, existing Synergy applications on Windows (whether traditional Synergy or Synergy .NET) might not be good targets for conversion.
If a Windows application only uses console I/O, you may be able to build it for .NET and run it on Linux with no changes. But if a Windows program uses the windowing API or UI Toolkit, it may need significant modification in order to work on Linux. The Terminal-based .NET implementation of the windowing API and Toolkit is based on the Unix implementation for traditional Synergy, so Windows-specific behavior will no longer apply. For instance, the default application height is 24 rows to match the Unix implementation, rather than 25. Windows-specific APIs such as the Windows printing API or ActiveX API will be unavailable.
Likewise, apps that use .NET-specific GUI technologies (such as Windows Forms or WPF) will not work on Linux without significant redesign to avoid those technologies.
Example 1: Getting Started
In this section, I’ll go over the basics of how to build a Synergy program for .NET and run it on Linux.
Prerequisites
To get started, you’ll need a Windows system with the following:
- Synergy/DE (32-bit and 64-bit) 12.2.1.1007 or higher
- SDI 2023.07.1357 or higher
- Visual Studio 2022 version 17.4 or higher
- Windows 10 version 22H2 or higher
- Windows Terminal (optional)
You’ll also need a Linux system with Synergy installed and the license manager running, since Synergy .NET programs on Linux use runtime licenses just like traditional Synergy applications. For simplicity of testing, I’ll assume that you have Windows Subsystem for Linux (WSL) configured, so that you can test Linux programs on the same system where you build them. The Windows Terminal app makes it easy to interact with WSL and can make Synergy .NET apps look better, but it isn’t required.
If you’d like to use WSL for testing, see the steps below for a basic explanation of how to set it up (details may vary). If you already have a Linux system with Synergy installed, you can use that for testing instead—just transfer all the files from the “publish” directory to the Linux system and run the executable from there.
To set up WSL:
- From a command prompt or PowerShell window, type “wsl –install” (which may require Administrator privileges).
- After installation is finished, reboot the Windows system.
- After the reboot, a window titled “Ubuntu” should pop up to finish installation. Wait until this is finished, following prompts as directed.
- Open a new command prompt and run “wsl –list –verbose”. You should see a default WSL instance listed with a version of 2. For example:
NAME STATE VERSION * Ubuntu Stopped 2
- Start a WSL instance (by running “wsl” from the command prompt, by opening the WSL instance in the Terminal app, or by starting the WSL or Ubuntu app).
- Transfer a 64-bit Linux SDE installer (e.g., 428SDE1221-1010.a) to a Linux filesystem location in the WSL instance.
Note: If you have a command prompt or File Explorer window open to a directory containing the installer, typing “wsl” will open a new WSL instance in the mounted equivalent to that directory, e.g., /mnt/c/Users/username/Downloads. This can make it easy to access the file in WSL, but do not try to install Synergy in this location. Installing Synergy on Linux involves setting certain file attributes that aren’t available on mounted Windows filesystems. Instead, you can copy the installer to a location using a native Linux filesystem (e.g., /home/username) and install it there.
- Follow the instructions to extract the installation files from the archive and run the installer. For example:
cpio -ivBdum < 428SDE1221-1010.a and sudo ./install.sde
- Follow the prompts to install Synergy/DE. When prompted whether to use licenses from an existing Windows server, you can select “y” and enter the IP address of your Windows system (not the hostname, as that may not work correctly from WSL). Alternatively, you can select “n” and set up local licensing. Then, if you want to configure license forwarding after installation, you can run “lmu -nf address”.
- Source the setsde script from the installation location to set up the environment for Synergy, and then run lmu to ensure the license manager is running or to start it up if it isn’t. You’ll need to do this each time you reboot the host system or start up WSL from a stopped state, so that licenses will be available for Synergy applications. (Alternatively, you can automate synd startup.)
Building the app
Open Visual Studio and create a new project. Filter projects by the “Synergy DBL” language. As of this writing, the following project templates should work with Synergy .NET on Linux (although not all of them have been tested):
- Class Library (.NET Standard)
- Console App (.NET)
- Class Library (.NET)
- Synergy/DE Repository
- Synergy Script
- Unit Test (.NET)
Additionally, .NET or .NET Standard class libraries should be usable in Synergy .NET applications on Linux, and vice versa. Projects targeting .NET Framework are not compatible and only work on Windows.
Of these project types, only Console App (.NET) produces an executable program, while the other project types can be used together with a Console App project to create a full application. For now, just select the Console App project template and create the project.
The initial code in the project should look something like this:
import System main proc Console.WriteLine("Hello World") endmain
You can modify the code if you’d like, but this should be sufficient for a basic test. Visual Studio will let you build and debug the program on Windows, but to test on Linux you’ll need to switch to a command line.
But before you leave Visual Studio, right-click on the project and click on Properties. In the Application tab, you should see a Target framework of .NET 6.0 or .NET 8.0 (as of this writing). Either one should work fine. But make sure it isn’t targeting “.NET 6.0 Windows” or “.NET 8.0 Windows”. These options are Windows-only: they allow access to some Windows-specific APIs and an implementation of UI Toolkit and the windowing API similar to what’s available in .NET Framework, but the resulting application won’t work on Linux. It’s also possible that an unsupported option like .NET Core 3.0 will show up in the list if you have an older version of SDI. In any case, make sure that a supported target is selected, and then close Visual Studio.
Open a command prompt and navigate to the directory containing the solution you created. Run a command like this:
dotnet publish projectname -c Debug -r linux-x64 --self-contained
That will build the project and copy all the files needed to run it on Linux to a “publish” directory (e.g., projectname\bin\Debug\net6.0\linux-x64\publish\).
Testing the app
To run the program, you can transfer the files from the “publish” directory to your Linux system and run the executable there. But if you have WSL installed, you can run the executable in-place. For example, navigate to the directory in the command prompt and type “wsl” to start up WSL. Then type “./projectname” to run the executable. (The file should automatically have the execute attribute set.)
If all goes well, you should see the expected output:
Hello World
Congratulations on building a Synergy .NET program that runs on Linux! But, naturally, this is a trivial example. And while it’s possible to build an application from scratch like this, the expected use case is migrating an existing application from traditional Synergy for Unix to Synergy .NET for Linux. We’ll go over an example of that in the next section.
Example 2: Converting an Existing Application
Prerequisites
For this example, I’ll assume you have the same setup as in the previous example. I’ll also assume you have a traditional Synergy application that’s built in Visual Studio for deployment on Linux or Unix. My example solution is simple and not fully functional, but it demonstrates several different components that you may expect to find in a UI Toolkit application:
- contactlist.synproj – Traditional Application (DBR)
- References:
- executablelibrary.synproj
- repository.synproj
- tklib.elb
- Program.dbl
- References:
- executablelibrary.synproj – Executable Library (ELB)
- References:
- repository.synproj
- Routine.dbl
- References:
- repository.synproj – Synergy/DE Repository
- repository.scm
- script.synproj – Synergy Script
- script.wsc
See the end of the article for the source code for these projects.
Conversion to .NET
We start with the Visual Studio solution described above, which builds a Toolkit application that can run on Unix. We want to convert this to a Synergy .NET application that can run on Windows or Linux. The repository and script projects should be usable as they are, but we’ll need new .NET projects to take the place of the DBR and ELB projects. So:
- Add a new Console App (.NET) project named “contactlistnet”. Set it as the start-up project for debugging.
- Add a new Class Library (.NET) project named “executablelibrarynet”.
- Open the properties of both projects and set the Target framework to .NET 8.0 if it isn’t already.
- Remove the initial Program.dbl file from the contactlistnet project and replace it with a link to Program.dbl from the contactlist project. Alternatively, you could just copy the code from the contactlist project to contactlistnet, but then you would need to update both files separately when making changes, instead of using the same code for both the traditional and .NET projects.
- Remove the initial Class.dbl from the executablelibrarynet project and replace it with a link to Routine.DBL from executablelibrary (or copy and paste the code).
If you try building now, you’ll encounter a number of errors because the new projects aren’t aware of UI Toolkit. To fix this, manage the NuGet packages for the Console App project and install the Synergex.SynergyDE.tklib package. Do the same for the Class Library project.
Then modify the references in the contactlistnet project and add references to the repository and executablelibrarynet projects. Also modify executablelibrarynet to reference the repository project.
At this point, the solution should build, but attempting to debug the program will result in a failure to locate script.ism. This is because the output directory for the Console App project doesn’t match the path for the other projects, so the script isn’t automatically available when the program runs. There are various ways to fix this, but for now you can just right-click on the contactlistnet project, select Add > Reference Existing Item, and add script.ism from the original output directory (e.g., C:\temp\contactlist\Debug\x64). Do the same for script.is1 and then modify the properties of both items to set “Copy to Output Directory” to “Copy Always”. To be safe, you can also add a Project Dependency on “script” to the Console App project, so that the script project will always build first and the ISAM files will be available.
If you build and debug the solution now, it should successfully run. The interface should look similar to that of the traditional Synergy version of the program running on Unix, with the program running in a terminal and a menu driven by the keyboard instead of the mouse. Window elements will likely be drawn with ASCII characters (e.g., “-“ and “|”) instead of actual lines, although this behavior can be changed by setting the TERM environment variable to a known value like vt100 and running the program in Windows Terminal or another environment that can correctly display the escape sequences.
But the program is still running on Windows. To test on Linux, we’ll have to follow the same publish steps as in the previous example.
Testing with Linux
Leave Visual Studio and open a command prompt in the location of the solution. Run a publish command. For example:
dotnet publish contactlistnet -c Debug -r linux-x64 --self-contained
The final line of output from that command should include the publish directory. To test on Linux, you can copy the files from that directory to the Linux system. To test on WSL, simply navigate to that location in a WSL instance, set up the environment, and run the program. For example:
$ cd /mnt/c/Temp/contactlist/contactlistnet/bin/Debug/net8.0/linux-x64/publish/ $ . /home/username/synergyde/setsde $ ./contactlistnet
A few things to note:
- Unlike the previous example, in this case it’s necessary to source the setsde script before running the program. This is because UI Toolkit relies on some Synergy environment variables to work correctly.
- The Synergy License Manager daemon (synd) must be running on the Linux system in order for the program to run. If it isn’t running (or you aren’t sure), you can run lmu to check the license status, and synd should start automatically.
- Terminal-based Toolkit programs look better when run in the new Windows Terminal app than in a regular command prompt. You may want to use that app for the WSL instance in this case.
- Alternatively, you could set the TERM environment variable to “dumb” to use standard ASCII characters to approximate drawing window elements, rather than using escape sequences to actually draw them. This is the default behavior for .NET terminal applications running on Windows. Note, however, that bash and other Linux programs also use TERM and may misbehave if it’s changed from its default value.
Conclusion
This article has provided an overview of Synergy .NET on Linux. We’ve seen some advantages to this feature, as well as some limitations. We’ve seen how a multiplatform program can be built from scratch, and how an existing traditional Synergy program can be converted to build in Synergy .NET and run on Linux. Converting an actual application may be more complicated, especially because Synergy .NET is stricter about things like bounds checking, but the basic concepts are the same.
I hope this article has given you some helpful ideas about Synergy .NET and how you can use it on Windows or Linux.
Source Code
Program.dbl
main .include "WND:windows.def" .include "WND:tools.def" .include "contact_structure" repository, structure record control1 ,a1000 col1 ,i4 WIN1_H ,i4 ;window handle 1 WIN2_H ,i4 ;window handle 2 scrchn ,i4 contact_data ,contact_structure endrecord proc xcall U_START xcall w_proc(WP_CREATE, WIN1_H, "Window 1", 15, 50) xcall w_proc(WP_PLACE, WIN1_H, 1, 1) xcall u_open(scrchn,, "script") xcall i_ldinp(WIN2_H, scrchn, "i_entry", D_NOPLC) xcall mb_column(control1,"m_MAIN","Main",,,,,'m') xcall mb_entry(control1,"ADD","ADD",,,'a') xcall mb_entry(control1,"EXIT","EXIT",,,'x') xcall mb_end(control1,col1) xcall add_text_to_window(WIN1_H,contact_data) menu_loop, xcall t_view(WIN1_H) if (g_select) begin case g_entnam of begincase "ADD": call add_contact "EXIT": goto done endcase end goto menu_loop done, xcall w_proc(WP_REMOVE, WIN1_H) xcall w_proc(WP_REDRAW) xcall w_updt xcall U_FINISH stop add_contact, ;xcall e_enter clear contact_data xcall i_init(WIN2_H,,contact_data) xcall u_window(D_PLACE,WIN2_H) xcall i_input(WIN2_H,,contact_data) ;if (g_entnam == "I_OK") xcall add_text_to_window(WIN1_H,contact_data) xcall u_window(D_REMOVE,WIN2_H) ;xcall e_exit return endmain
Routine.dbl
subroutine add_text_to_window
WIN1_H ,i4
group contact_data
.include "contact_structure" repository, norecord
endgroup
endparams
.include "WND:windows.def"
.include "WND:tools.def"
static record localdata
textarray ,[20]a64
idx ,i4, 0
endrecord
proc
xcall t_setup(WIN1_H, D_INIT,2,2,2,2,,,,15,50)
if (idx == 0) then
begin
textarray[1] = "Open menu to add entries (Ctrl+P). "
textarray[3] = "John Smith 1234567890"
textarray[4] = "Jane Doe 2468013579"
textarray[5] = "Fred Carlson 1029384756"
idx = 5
end
else
begin
idx += 1
textarray[idx] = %atrim(contact_data.first_name) + " " +
& %atrim(contact_data.last_name) + " " +
& %string(contact_data.phone_number)
end
xcall w_disp(WIN1_H, WD_CLEAR)
xcall t_puttxt(WIN1_H, textarray, idx, D_WRITES, D_WRAP)
xcall w_updt
xreturn
endsubroutine
repository.scm
Structure contact_structure DBL ISAM Description "Contact information" Field FIRST_NAME Type ALPHA Size 30 Field LAST_NAME Type ALPHA Size 30 Field PHONE_NUMBER Type DECIMAL Size 10 ;End Structure
script.wsc
.script
input i_entry, 4, 57
.placement 4, 12
.title "Contact Entry", highlight
.field first_name, a30, pos(1,2), prompt("First name"), fpos(1,17)
.field last_name, a30, pos(2,2), prompt("Last name"), fpos(2,17)
.field phone_number, d10, pos(3,2), prompt("Phone number"), fpos(3,17)
.structure i_entry, first_name, last_name, phone_number
.button I_OK, text(OK)
.button_set bottom(1)
.end