The DataCAD Developer Network (DDN) is an online resource for information and support for DCAL® (DataCAD Applications Language) developers as well as anyone interested in creating fonts, toolbars, hatch patterns, or linetypes for use in DataCAD.
#72007 by Jsosnowski
Fri Aug 04, 2017 10:30 am
PART THREE - A D4D PRIMER - p_LR & p_DR RECORD STRUCTURES
published: 8/4/17. Rev1: 9/11/17 ( setargs)
As discussed in the PART TWO posting, D4D uses specific arguments to pass menu loop status information as P_LR and data as P_DR.

Code: Select all function <name>_doMenu (act : action; p_LR, p_DR : Pointer) : wantType;

Both of these arguments are of the type pointer. Each argument references a record type that each macro must define based upon the specific needs of each <Name>_doMenu function used. This posting explains what they are and how to tailor them to your macro.

The sample D4D macros provided by Datacad associates the first argument pl pointing to a record structure t<Name>L and a local variable of this type as L. (I have no idea what letter 'L' abbreviates, bit guessed it as 'loop'). Similarly the second pointer argument is called parg pointing to a structure t<somename> data record type and a misc. single letter variable name such as 'd' . This primer renames the record structures for clarity as t<Name>LoopRecord and t<Name>DataRecord. The pointers are now called p_LR & p_DR. THe local variable pointers are now called lr & dr

The t<name>LoopRecord record and pointer

The p_LR pointer argument in the doMenu function points to a custom record type t<name>LoopRecord that contains the relevant information needed by D4D interface functions (listed in uinterfaces.pas) such as getesc, getdis, & etc. There are two critical fields that should always be contained in the record as shown:

Code: Select allType

t<name>LoopRecord = record
  //Add any other desired variables
  state: asint; {1}
  case byte of  {2}
    0; (getp: getpointArg); //and/or getesc as required
    1: (getd: getdisarg); // declared in UinterfacesRecords
    //include all interfaces required in the specific _doMenu function.
end;

{1} state - identifies the current status of the executing menu loop when control is passed back to the macro from Datacad. Its value will direct the focus through an iteration of the <Name>_doMenu function as will be shown in PART FOUR.
{2} case byte of - this case listing identifies the type of data that might be returned by any of the D4D interfaces used. In this example record two items are listed to include an instance of getpoint() and getdis() function calls that would be found in the <Name>_doMenu function being used.

The code sample above includes two case interface options. Only one may be needed for a specific <Name>_doMenu function in which case you need only retain the one. However, the list must include an entry for every interface function used anywhere in the <Name>_doMenu function. Note that the same could also be shared by more than one <Name>_doMenu function in which case all of the relevant interfaces must be included in the case byte of statement.

Examining case 0 above more closely, a variable of type getp is declared of the type getpointArg. The D4D unit uinterfacesRecords.pas declares that record type as follows:

Code: Select allgetpointArg = PACKED RECORD
      Result: crtstat;    { out }
      curs: point;      { out }
      key: aSInt;    { out - if result = escape, the key that was pressed }
      fkey: aSInt;    { out, the function key number }
      from3d: boolean;    { in, were we called from 3d }
      curs3d: boolean;    { in }
      snapped: boolean;
      dogesc: boolean;
      viewmode: aSInt;
      curs3: point;
      PickingSymbols: boolean;
   end;

while case 1 declaration getd is of type getdisarg.

Code: Select all getdisArg = PACKED RECORD
      pdis: paFloat;     { in out }
      msg: aSInt;    { in }
      len: aSInt;    { in }
      toabs: boolean;    { in }
   END;

Depending upon which interface get<Something> function is called you will want to access different record variables, but only one can be used in each menu loop iteration. (Remember that calling an interface function must be the last step in an iteration of the doMenu). The use of the case statement in the record structure allows the record to hold both types in the same memory area. In PART FOUR you will encounter a request by DataCad (act = alsize) to determine the size of your t<name>LoopRecord record. The value returned will be determined by the size of the listed variables in the record and the largest record size for any of the case options in that list. In that way adequate memory is allocated to contain the data regardless of which case is required in a specific loop.

In addition to declaring the t<name>LoopRecord record structure you must also declare a pointer to it so that you can use the <Name>_doMenu function p<name>LoopRecord parameter for use in the macro code.

Code: Select allp<name>LoopRecord =  ^t<name>LoopRecord;

The t<name>DataRecord record and pointer

The fourth argument in <Name>_doMenu functions, p<name>_DR is handled similarly to p<name>_LR. Once again you must declare a record structure and related pointer, this time to handle variables used by your macro that must be preserved between menu loop iterations between Datacad and your macro.

The structure of this record depends upon what your specific macro is doing. The fields in this record are examples, not requirements, so the declarations in your macro will basically be similar:
Code: Select allType
t<name>DataRecord = record   
  //list any persistent macro variables needed 
  Count : integer;
  s : array [1..5] of str255;
  //etc...
end;

p<name>DataRecord = ^t<name>DataRecord;

Note that once again a pointer to the record type is declared.

Using the Record Structures

As described in PART TWO, when Datacad commences a menu loop it does so by calling the Main function and invoking one of the dcalstate options, calling in turn the associated <Name>_doMenu function. In doing so, it is handing off the p_LR argument to transfer data obtained from interface functions such as (getpoint, getesc...). When the 'alsize' act is invoked by Datacad, the size of the t<name>LoopRecord is determined, Datacad allocates memory to store the record's data, and the pointer to the actual location of the record is connected through the p_LR argument. The current <name>_doMenu function links to this argument in each loop through a local variable lr to interact with the record's fields. Different t<name>LoopRecords can be assigned for different <name>_doMenu functions by changing the linking code statement that connects p_LR to lr and changing the tLooprecord refernce in the 'alSize' case code (See PART FOUR).
Rev1 below
Setting up the p<name>_DR is handled differently. When your macro calls another <name>_doMenu function, the specific t<name>DataRecord may vary for every function called. It is the coder's responsibility to insure that Datacad knows the size of that data at the time the new p<name>_doMenu call is made. As discussed in PART TWO, the new menu is called by returning the <name>_doMenu function's result (wanttype) to the desired new dcalstate value in the Main case statement. Whenever this is done, the macro must also inform Datacad about the new t<name>DataRecord record to be used with the new <name>_doMenu call. This is accomplished by using the setargs() procedure described in Uinterfaces.pas.
Code: Select allsetargs (p<name>_DR);

The life cycle of p<name>_DR in a macro requires a closer examination. As discussed in previous posting PARTS, since multiple copies of the macro may run simultaneously, the data must be passed to the macro through the p<name>_DR argument. Insuring that the data is handled properly requires several steps.

1. As mentioned previously, all data that must persist in a macro must be declared in the tDataRecord structure. The user declares the record structure and associated pointer in the 'type' declaration in the '<name>.dll library unit or in the interface of of another unit included in the Delphi project.
2. An instance of the 'tdatarecord' is declared as a unit level variable.
3. The macro must declares a pointer variable to the tDataRecord structure to be passed as the p<name>_DR argument in subsequent menu loops. This is usually accomplished at the start of the main menu loop for the macro.
4. The pointer is connected to the address of the unit level data variable.

Code: Select alllibrary <name>
...
const
//dcalstate constants
   XMain = XDcalStateBegin; 
   Xlvl2Menu = XMain +1;   
...
t<name>DataRecord = record   {1}
  //list any persistent macro variables needed 
  Count : integer;
  s : array [1..5] of str255;
  //etc...
end;
p<name>DataRecord = ^t<name>DataRecord; {1}

var
  data :tdatarecord; {2}
 
function Main_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
var
   retval : wantType;
   lr : pLocalL; 
   dr : pDataRecord; {3}
begin
    //link function arguments to local pointers
  lr := pLocalL(p_LR); 
  dr := addr(data);  {4}
  ...

Now note that the p<name>_DR argument in the menu function declaration is a value variable. While it passes information into function, it does not pass that information back to Datacad through the argument. This means that each subsequent menu operation that may be called from within this menu must link to the data in another manner. The setargs(p:pointer) procedure is used to do so. To illustrate this in action lets assume that the menu function above calls a separate menu to operate on some of the data. To call the new menu we will create a procedure to call a new menu (call_Menu2) and invoke it in the appropriate case in the EXECUTE SECTION. In our example we will use the 'F2' key.

Code: Select allprocedure call_Menu2 (data: pDataRecord; VAR iwant: wanttype);
begin
  SetArgs(data);
  iwant := xLvl2Menu;
end;

function Main_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
...
begin
   ...
   aagain:
                                                     //ROUTER STEP       
      case lr.state of
        1 :
           begin  //Set case for menu keys used use either getp or gete lines
             case lr.gete.key of {10}
               f2 : lr.state := 2; 
    ...
                                                   //EXECUTE SECTION
   if act <> alsize then begin 
     case lr.state of   
   ...
     2: 
       begin
         call_Menu2 (dr, retval);
       end;

Now, when the 'F2' key is selected, the 'lr.state' is changed to 2 and the EXECUTE SECTION acts upon the function key call by implementing 'call_Menu2' passing the data and the return value (retval) to the procedure. 'Call_Menu2' in turns passes a pointer to the Data to Datacad using Setargs() which will then pass it back to Menu2 in the next itteration. The actual call to the menu is accomplished by assigning the constant value associated with the menu (in this case 'Xlvl2Menu' as declared in the const section of the unit) to be handled by the 'Main' function discussed in PART TWO.

Now, with the function arguments and record structures for the pointers established, it is time to put a <name>_doMenu function together (in PART FOUR).

Who is online

Users browsing this forum: No registered users and 16 guests

About DataCAD Forum

The DataCAD Forum is a FREE online community we provide to enhance your experience with DataCAD.

We hope you'll visit often to get answers, share ideas, and interact with other DataCAD users around the world.

DataCAD

Software for Architects Since 1984