Differences for Synergy .NET
This topic, along with Features that are not supported for Synergy .NET development, lists ways in which Synergy DBL support for .NET differs from traditional Synergy development. For information on additional differences for SDK-style project development, see Differences for SDK-style project development.
Runtime
Synergy .NET assemblies run under a .NET CLR rather than the Synergy Runtime, but some Synergy runtime functionality is required for Synergy .NET. This is supplied by runtime libraries that are required and are automatically referenced in Synergy .NET projects. See Synergy runtime and build libraries for more information.
Building (compiling)
With Synergy .NET, you use MSBuild (e.g., via Visual Studio build features) to create assemblies from Synergy projects. (Running the Synergy .NET compiler directly from the command line is not supported.) Note the following:
- All programs are compiled as if the following compiler options were set in traditional Synergy: -qalign, -qcheck, -qnet, -qnoargnopt, and -qreentrant. This means, for example, that all pseudo arrays are converted to real arrays, and subscript ranging and dimension access are checked to ensure they do not exceed the descriptor of the passed variable.
- There are several defines that enable you to conditionalize code for Synergy .NET in general, debugging, and more. See Compile-time defines.
- Synergy .NET can use only forward references for external entries in class libraries. All global symbols must be resolved at the time an assembly (class library or executable) is compiled. Unlike traditional Synergy, it is not possible for a global record in an .exe file to be referenced by an external record in a class library. See Preparing Existing Code for Synergy .NET for more information.
- For optimization, all records default to stack records. This means that large data divisions (e.g., 8000a2000) can cause stack overflow errors or compile errors on some web server platforms (such as IIS, which limits the stack to 256K). If your application does not use multi-threading, you can work around this by using STATIC in front of such records (including records in mainline code, which default to STACK) or by using a dynamic array. And note that unlike traditional Synergy, stack records do support initial values in Synergy .NET.
You can use the “Generate warning when stack size exceeds # bytes” option on the Compile page of Visual Studio project properties to detect if the total stack data used by a routine exceeds a given size. For more information, see Compile page of Visual Studio project properties.
Debugging
For Windows development, Synergy .NET uses the Visual Studio .NET debugger (rather than the Synergy debugger). See Debugging Synergy .NET Code for more information on limitations and special features for Synergy .NET.
Data types
Arrays
You cannot declare a real array of a .NET type (e.g., [10]D_ADDR or [10]Int) because with .NET, D_ADDR is shorthand for System.IntPtr, and Int is shorthand for System.Int32. Instead use either an array of i4 for D_ADDR on 32-bit, i8 for D_ADDR on 64-bit, or a dynamic array ([#]int or [#]D_ADDR).
When passing a Synergy real array, the number of ranks must match the argument definition in the called routine.
Passing a non-arrayed field to a pseudo array argument — i.e., an argument defined with (*) — passes a single dimension array of one element. With traditional Synergy, you can do this if -qcheck is not used, and you can subscript beyond the end of the field. This is not possible with Synergy .NET because of strong bounds checking (which operates as if -qcheck were specified with traditional Synergy). And with Synergy .NET, passing ^M(field, data_area) to a pseudo array or real array argument will result in an array of field whose dimension is determined by the number of these fields that will fit in the memory area.
Data type identifiers
Handles for ^M should be defined using D_HANDLE. D_ADDR is not supported for use with ^M or arguments to functions that take a handle.
Decimal
A decimal assignment to an integer or unsigned integer derivative will cause a BIGNUM error if the decimal value exceeds the maximum for a 64-bit int.
Covariant return types
When a parent class method is overridden, the signature for the derived class method must match the signature for the parent class method that’s being overridden, except in .NET Core and .NET 6 and higher where covariant types are supported. For information on covariant return types, see Overriding a method.
Integer
For optimization, integer fields (which are usually descriptor types) are in many cases converted to native .NET data types (value types):
- i1 becomes System.Sbyte
- i2 becomes System.Int16
- i4 and int (which are synonymous in traditional Synergy) become System.Int32
- i8 becomes System.Int64
Generally, these conversions are seamless; there’s no need to consider them as you code. They can, however, cause problems if you rely on automatic boxing or unboxing. For example, the following code (which works in traditional Synergy) won’t work with Synergy .NET because casting ivar as (object) results in an @int, which can’t be unboxed to an (@i4). (You can’t unbox one type to another.)
record num ,@object ivar ,i4 proc num=(object)ivar ;Results in an @int ivar=(i4)num ;Attempts to unbox the @int to an (@i4)
To prevent this, force the data type as you box/unbox to ensure you use the same type. For example, the above would work for both traditional Synergy and Synergy .NET if ivar was explicitly boxed using (@i4):
num=(@i4)ivar
or unboxed using (int):
ivar=(int)num
Additionally, note the following:
- Some arithmetic BIGNUM errors don’t occur on integer types because the intermediate result is generally an Int64.
- Functions that FRETURN an i8 on a 32-bit platform return the i8.
- Functions that return ^VAL on 32-bit don’t generate a BIGNUM error if an i8 whose value is too large to fit in an i4 is returned.
Literals
Types for literals (and literals cast as object types or passed to parameters of object types) are changed from Synergy literal types to corresponding .NET literal types. For example, “abc” is type string, and 10 is int or @int. If you want Synergy literal types, cast the literal as the desired Synergy type (@a or @i).
Objects and value types
Objects and certain .NET value types (such as IntPtr) in named entities (structures and records) are automatically aligned on native boundaries for .NET CLR requirements. This causes an automatic align warning to notify of the implicit alignment of such fields. Use .ALIGN to suppress this warning. Additionally, structures that contain alignable types are padded to a multiple of the highest alignment size for use in arrays. A warning is reported when this occurs (WALIGN, “Align warning: structure padded because of alignment”). To avoid this, add a filler.
Overloading
Overloading by using a BYVAL parameter and a BYREF parameter of the same type is not supported.
Arguments cannot be overloaded, so passing a d. value to a d argument results in the d argument accessing a rounded whole value. Use ^D to correctly cast such variables.
Parameters
Note the following when passing parameters:
- Only descriptor types can be optional.
- Passing a decimal or implied-decimal type into a MISMATCH alpha parameter that is not marked as IN causes a PASSUR warning. To avoid this warning, either change the parameter to IN (recommended) or cast the call as ^A(), but make sure your routine can never create an invalid decimal variable.
- In traditional Synergy, an alpha parameter passed to a MISMATCH n parameter is typed decimal, whereas in Synergy .NET, the passed alpha parameter remains an alpha type. This can cause subtle behavioral differences. To avoid unexpected results, use MISMATCH n only
- for routines that pass the parameter as an argument to another routine marked MISMATCH n.
- when you explicitly use ^DATATYPE and cast with ^A of the argument.
If you do not explicitly use ^DATATYPE and ^A and want to pass an alpha to a routine n argument, change the call to use ^D() instead of making the routine MISMATCH n.
String
A new String() cannot take an alpha argument in Synergy .NET. Instead use stringvar = “abc”.
Structfields
Numeric types cannot be assigned to structfields. Attempting to do so results in a NETALLOW error during compilation.
Destructors
Destructors are nondeterministic on .NET. Order and timing are at the discretion of garbage collection, and they may even execute after a STOP statement.
Directives
Some directives are not supported for Synergy .NET. See Directives.
.INCLUDE
Repository field names with prefixes (created by the PREFIX qualifier) are not truncated. In traditional Synergy, a repository field name is truncated if it is longer than 30 characters.
Boxing and unboxing
If you have System.Object=@d, you can unbox the object only to a d, and you must unbox it explicitly. The object cannot be automatically unboxed because the compiler cannot detect its type.
Boxed types are automatically unboxed only when a boxed type argument is passed to an unboxed type parameter or to a boxed type assigned to an unboxed type. The types must match or must both be integer or numeric, and the boxed variable must be explicitly typed. In all other cases, you must explicitly cast the variable to unbox it. (With traditional Synergy, several circumstances result in automatic unboxing.) For more information, see Boxing.
Exception handling
If an exception is thrown by a method called by XSUBR, and the exception is caught in a TRY-CATCH block in the calling method, the caught method will not have the same type as the original exception thrown in the called method. Instead, it will have the type System.Reflection.TargetInvocationException. This type includes the original exception as the InnerException property and is necessary to preserve stack trace information. (ONERROR processing is different: with ONERROR, the error number is preserved.)
Note the following:
- ONERROR (and exception handling in general) is slow in .NET. Use I/O error lists instead of ONERROR or TRY-CATCH.
- CALL-RETURN is not allowed inside a TRY block.
- When a STOP statement is executed in a CATCH block, the FINALLY block will not be executed. Refer to Microsoft’s .NET documentation for further information about how and when FINALLY blocks are executed.
Memory
Unlike traditional Synergy, Synergy .NET uses garbage collection for nondeterministic destruction of objects. For information on emulating deterministic destruction of objects, which may be necessary with resource-intensive objects (e.g., large Synergy arrays and Select objects), see Microsoft’s documentation on implementing the Dispose pattern.
Structures, records, and fields
If your application uses global commons, global data sections, or public class fields that are accessed across assemblies, whenever one of those elements changes, you must recompile all projects that reference the assembly containing the element. We recommend that you use assembly versioning on your dependent projects as well. (Traditional Synergy does not have this problem because names are resolved at runtime, not at build time.)
Structures
Synergy .NET supports both Synergy structures and .NET structures. (Synergy structures are defined using STRUCTURE statements, and .NET structures are defined using CLS STRUCTURE statements.) Unlike Synergy structures, .NET structures are compatible with C# and other .NET languages within the public namespace. However, .NET structures cannot contain descriptor types or have overlays, and they cannot be used with ^M, passed as alpha arguments, or declared as real arrays (only dynamic arrays).
You cannot use a local structure to define a structfield in a global data section. With Synergy .NET, global data sections and commons are true global entities, and only global structures can be used to define structfields.
Records and fields
Note the following for records and fields:
- Records and fields are not restricted to 65,534 bytes on 32-bit platforms.
- Stack records are initialized on routine entry and initial values are allowed.
- Differing record definitions for a global data area are not supported. With Synergy .NET, every record in a global data declaration that does not include ,INIT must match a record in the declaration that does include ,INIT. (The ,INIT declaration may have additional records, but every record in the non-INIT declaration must be in the ,INIT declaration.) A record must match in name, number of fields, field names, field types and sizes, etc. If you have a record whose non-INIT and ,INIT declarations do not match, create an overlay in the ,INIT declaration to match the non-INIT record.
- An external common field cannot differ in size or data type from the corresponding global common field. With Synergy .NET, external and global common declarations for a field must match in every respect. If you have a field whose declarations differ, create an overlay in the global common declaration to match the field in the external common declaration.
- A record cannot overlay a common in Synergy .NET. Attempting this will cause an OLYBD error.
Statements
TRY-CATCH and ONERROR
An exception from XCALL EXITE or a runtime-signaled error can be caught by TRY-CATCH in the current or previous routine, or by ONERROR in any prior routine. (With traditional Synergy, an XCALL EXITE always transfers control to a prior routine and cannot cause a program to stop with a fatal error.)
ACCEPT, GETS, and READS
Terminal channel (TT:) functionality for ACCEPT, GETS, and READS is supported only for console applications in Synergy .NET. Additionally, these routines do not accept characters from applications that use the Synergy Windowing API; you must instead use WD_ACCEPT, WD_GETS, and WD_READS.
FOREACH
Note the following when using FOREACH for .NET development:
- .NET does not allow the current collection to be modified in a FOREACH statement from within the FOREACH. Attempting this will result in the error “System.InvalidOperationException: Collection was modified; enumeration operation may not execute.”
- The loop variable type for a FOREACH statement must match the type for each element in the statement’s collection or a runtime cast exception will occur. For example, a FOREACH statement that uses a collection of structfields for a structure made up of alpha fields can have a loop variable whose type is a (alpha) if you are using traditional Synergy. But this will not work with Synergy .NET unless you use the [AS type] extension to the FOREACH syntax:
FOREACH loop_var in collection [AS type]
- Executing a FOREACH statement on a collection where the test condition is == ^NULL will cause a null reference exception on the GetEnumerator call used in the implementation. (With traditional Synergy, this would result in no operation.)
- The enumerator created implicitly by a FOREACH statement is disposed at the end of the loop. If the collection implements the dispose pattern and is a new instantiation, the temporary object created by the new instantiation has its Dispose method called automatically at the end of the loop.
READ
An optional subroutine argument that is omitted cannot subsequently be used as the key_spec argument to a READ statement.
RETURN
A call to RETURN behaves as a call to XRETURN if there are no more items on the call stack, regardless of whether a CALL has occurred. (In traditional Synergy, a RETURN behaves as an XRETURN if at least one CALL has occurred, and causes a NOCALL runtime error if there has been no prior CALL.)
STOP chaining
With .NET, when chaining to a program with a STOP statement there is a delay when the program is started, and any on-screen data is cleared and re-created, which may cause flicker. This is a .NET limitation. We do not recommend using STOP to chain with Synergy .NET.
USING and CASE
USING and CASE operate as if the -qnoargnopt option were specified in traditional Synergy: numeric types are honored, string control variables in a USING statement cause string comparisons, and match labels for USING ranges are not rounded to whole numbers.
Subroutines and functions
With Synergy .NET, subroutines cannot be called as functions. To work around this, convert the subroutine to a ^VAL function.
^ARG* routines
We strongly recommend against using the ^ARG* routines with declared arguments because of the high overhead they incur.
^D and ^I
Using ^D or ^I on null alpha literals or intermediate results returned from %ATRIM correctly generates a NULARG error because these types cannot have a length of 0.
%ERLIN, ERRMOD, %ERROR, and MODNAME
%ERLIN, ERRMOD, %ERROR, and MODNAME have limited line number support, and the file_number argument for MODNAME is always returned as 0 with Synergy .NET.
%NUMARGS
%NUMARGS returns the number of the last passed argument, which can be different than the return value in traditional Synergy if there are optional arguments. For example, if a subroutine called mysub has three optional arguments, %NUMARGS will return 2 for both traditional Synergy and Synergy .NET for this example:
xcall mysub(arg1, arg2)
But for the following, it will return 3 for traditional Synergy and 2 for Synergy .NET:
xcall mysub(arg1, arg2, )
And for the following, if mysub has one optional argument, %NUMARGS will return 1 for traditional Synergy but 0 for Synergy .NET:
xcall mysub()
In traditional Synergy, ELBs linked to an executable (.dbr) are automatically loaded when the executable is run, and ELBs linked to a loaded ELB are automatically loaded. This behavior enables %XADDR and XSUBR to work. With .NET, however, referencing an assembly does not cause the assembly to be loaded. And the .NET method Assembly.Load does not always work with %XADDR or XSUBR. To load an assembly, use either OPENELB or add method calls to the referenced assembly. Note the following:
- Only public routines (those not marked INTERNAL) can be used with %XADDR or XSUBR.
- %XADDR and XSUBR do not perform well in Synergy .NET because they use reflection. Use direct method calls in critical code paths.
- OPENELB and %XADDR do not use the .elb extension in Synergy .NET. They open .NET assemblies according to the rules for loading assemblies in the .NET. Assemblies can be loaded using a partial name (e.g., Synergex.SynergyDE.synxml), a fully qualified name (e.g., Synergex.SynergyDE.synxml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=114C5DBB1312A8BC), or a full path (e.g., C:\Windows\assembly\GAC_MSIL\Synergex.SynergyDE.synxml\2.0.0.0__114c5dbb1312a8bc\Synergex.SynergyDE.synxml.dll). Microsoft’s recommended practice when loading assemblies is to use the fully qualified name when possible, to prevent versioning issues. Additionally, the elb argument for %XADDR is supported only when OPENELB is supported.
%TNMBR
%TNMBR (which is deprecated) always returns either the environment variable TNMBR or -1.
XSTAT
XSTAT is for use only with SHELL and SPAWN.
APIs
Synergy DLL API
We recommend you use the .NET DllImport attribute instead of %DLL_NETCALL or %DLL_CALL.
Synergy XML API
To use the XML API for .NET Framework development, you must add a reference to Synergex.SynergyDE.synxml.dll. For SDK-style projects, use the Synergex.SynergyDE.synxml NuGet package.
Synergy windowing API
The UNIX-compatible (non-mouse) functionality of the Synergy windowing API is fully supported for the .NET Framework on Windows. You can set the SYN_RESIZE_SCALE environment variable to 1 to make the application window resizable and maximizable.
Synergy socket API and HTTP document transport API
You should explicitly close channels and sockets used by these APIs and free global memory handles. Do not assume that shutting down the program or AppDomain will do this.
Repository subroutine library
To use the Repository subroutine library (the DD_ routines) for .NET Framework development, you must add a reference to Synergex.SynergyDE.ddlib.dll. For SDK-style projects, use the Synergex.SynergyDE.ddlib NuGet package.
Environment variables and initialization files
Initialization files and some environment variables can be used for .NET development, and in some cases they can be used for runtime settings. See Environment variables and Visual Studio development for more information, and see Environment variables for a list of unsupported environment variables.