IT:Embedding C code in DCS

From Stiki
Jump to: navigation, search
This page contains sample code to be used as part of a complete program. You may need to adapt this code to make it suitable for your requirements. Please be aware of the disclaimer regarding the use of any code posted on this site.

DCS includes a wide range of functionality for producing model point files for Prophet from various types of raw or formatted data sources. There are occasions however when you may wish to include a certain piece of functionality within a DCS program that cannot be constructed using the existing set of functions. For this reason, there is the option to embed raw C code within a DCS script between the INSERT_C_CODE and END_C_CODE commands. By including the appropriate libraries, it is then theoretically possible to enable your DCS program to perform any required action.

Sadly the help files and manual for DCS do not include any useful examples of how to use this functionality, and the help desk seem reticent to discuss this as an option since their role is not to support C programmers, but to support users of DCS. Hopefully this page will provide enough information to allow other people to successfully use C code within DCS.

Passing variable values

The variable values used in the DCS code can be referred to within the embedded C code as well (including for assigning the variable a value). Within the context of the C code, any DCS variable can be referred to by prefixing the variable name with DCS_ (so the DCS variable VAR1 would be referred to as DCS_VAR1 within the embedded C code).

Text variables require a little more complexity since these are not considered basic variable types by C. DCS stores text as variables of type DCSString for which I need to be converted into the appropriate data type. You can do this using the c_str() function which is already included. So a DCS text variable called TEXT1 can be referred to in the embedded C code as DCS_TEXT1.c_str().

The fragments of code below give further examples that refer to DCS variable values within the C code.

Examples

The key tasks that DCS cannot perform but that might be useful in the context of producing Prophet model point files are:

  • Running external programs. DCS allows for a fair bit of flexibility in automating the process of producing model point files, since a compiled DCS program can be run by another application as an executable file (with parameters passed to it as required). But it lacks the ability to call an external program itself.
  • Manipulating files. Although DCS can read data from and write output to files (creating new files if necessary), it lacks the functionality to check for the existence of a file (other than raising an error message) or to move or copy a file.

The example below give full solutions to these problems.


Running an external executable file

The following style of code can be used to run external programs. It works using functionality from the library process.h which is already included by DCS. The function system() takes an argument that is interpreted as a command line as it would be typed in DOS.


INSERT_C_CODE
system("C:\\Temp\\Progra~1.exe");
END_C_CODE

Notes

  • The command line interpreter is based on the 8.3 filename format, so directory and file names that are longer than that must be written in their truncated form. Typically this is as the first six characters of the name (excluding spaces) followed by a tilde and the number 1. If there are multiple files or directories with the same initial characters then they will be enumerated in sequence according to the order in which they were created in that location (as per the file allocation table). So of the form, filena~1 to filena~9, then filen~10 and so on. Some experimentation should reveal the appropriate short form name, or alternatively you can rename the files and directories to be in 8.3 format.
  • The backslash is used as an escape character within strings by C. In order to include a single backslash with a string, you must escape it. This effectively means using double backslashes as in the example above.
  • Note that the execution of the DCS code will pause until the external process has completed.
  • There are other options using commands like exec() or spawn(). This would allow for more control over the external process, including being able to pass information between processes. But using system() is the simplest solution to the problem. Some documentation on a more structured approach within the Windows environment using CreateProcess() from the windows.h library is available from: http://msdn.microsoft.com/en-us/library/ms682425.aspx.
  • For a more robust solution, you may wish to parameterise the file path (and also file name) of the target executable. The following code gives an example of this:

EXPLICIT_DECLARATIONS
FILE_PATH, FILE_NAME, FULL_FILE_PATH AS TEXT
 
; Further parameterise these variables as required:
FILE_PATH = "C:\\Temp\\"
FILE_NAME = "Progra~1.exe"
FULL_FILE_PATH = FILE_PATH + FILE_NAME
 
INSERT_C_CODE
system(DCS_FULL_FILE_PATH.c_str());
END_C_CODE

Notes

  • String concatenation is possible in C (using strcat() for example) but it will generally be easier for you to perform such variable manipulation within the pure DCS code, where you don't have to worry about including libraries.


Preventing an existing output file from being overwritten

This code tests for the existence of a DCS output file, and if it already exists (i.e. using fopen() does not return a null value) then it copies the existing file to a new location to save it. It reads in one file (designated with the pointer i_file) and writes the entire contents to a second file (pointer o_file).


EXPLICIT_DECLARATIONS
FILE_PATH, FILE_NAME1, FILE_NAME2, FILE1, FILE2 AS TEXT
CONTINUE AS INTEGER
 
; Further parameterise these variables as required:
FILE_PATH = "C:\\Temp\\"
FILE_NAME1 = "testfile1.txt"
FILE_NAME2 = "testfile2.txt"
FILE1 = FILE_PATH + FILE_NAME1
FILE2 = FILE_PATH + FILE_NAME2
 
IF FIRST_RECORD THEN
    CONTINUE = 0
    INSERT_C_CODE
    #include <stdio.h> /* For file handling functionality */
    FILE *i_file, *o_file; /* File pointers for referencing the input and output files. */
    int c; /* Used to hold the value of a character byte whilst copying data between files. */
    i_file = fopen (DCS_FILE1.c_str(), "r");
    if( i_file == NULL )
        DCS_CONTINUE = 1;
    else {
        END_C_CODE MESSAGE("Output file already exists!") INSERT_C_CODE
        o_file = fopen (DCS_FILE2.c_str(), "w");
        if( o_file == NULL ) {
            END_C_CODE MESSAGE("Failed to copy output file to " + FILE_NAME2 + ". Program terminated.") INSERT_C_CODE
        }
        else {
            while( (c = getc( i_file)) != EOF )
                putc (c, o_file);
            END_C_CODE MESSAGE("Old output file saved as " + FILE_NAME2 + ".") INSERT_C_CODE
            DCS_CONTINUE = 1;
            fclose (o_file);
        }
        fclose (i_file);
    }
    END_C_CODE
ENDIF
 
IF CONTINUE = 1 THEN
    ; Insert the rest of your DCS program here.
ENDIF

Notes

  • The code for manipulating the files is run only once (and so is conditioned on FIRST_RECORD) and sets the variable CONTINUE to 1 if the files are in a suitable state to proceed (and 0 otherwise). The remainder of the program's code is then conditional on CONTINUE=1, ensuring that it is only run if everything is okay.
  • This style of code can be adapted to a variety of different purposes. Copying a file is useful to preserve a file's contents before DCS changes it, as this example does. Or it may be useful to copy an input file from its original location into a staging location where all DCS runs are performed.
  • The function fclose() does not like being passed a null value. If the fopen() statement fails, you do not need to use fclose().
  • The conditioning here on whether certain commands are successful has been used to send messages to the runlog. You can use this conditional branching for various other purposes instead.