External Commands (How to Create a Plug-In) Changed in Version 2015.0

www.CAD6.com

One of the main purposes of plug-ins is to add new, previously unavailable commands to the serving application. Such commands are called "External Commands". There are two types of external commands:

 

Direct Commands

The first, easy type of command is the "direct" command that does not require any point entry by the user. Such a command can immediately be executed after the user selected that command from the plug-in's menu. As a result, implementing such a command is very simple.

 

The most common "direct" command is the "About..." command that should be available in every plug-in's (sub)menu. Once the user selected this command, the plug-in's command processing procedure MKI_PlugInCommand is called with the corresponding command ID of the "About..." menu entry (let's call it MKI_MENU_ABOUT) passed in CommandID. Most plug-ins will react to that command by simply displaying a message box stating the plug-in's version and some copyright messages:

 

C++ Source Code

MKI_INTERFACE_EXPORT __int32

MKI_PlugInCommand(

 __int32 f_nCommandID,

 __int32 f_nExecMode,

 void* f_pData )

{

  bool          fResult = false;

  MKI_DUMMYSTRW szText1,

                szText2;

 

  switch( f_nExecMode )

  {

    case MKI_EXECMODE_HELP:

      switch( f_nCommandID )

      {

        case MKI_MENU_ABOUT:

          MKI_DialogHelpTopic( hGlobalWnd, L"SAMPLE", 0 );

          fResult = true;

          break;

      }

      break;

 

    case MKI_EXECMODE_USER:

    case MKI_EXECMODE_SYSTEM:

      switch( f_nCommandID )

      {

        case MKI_MENU_ABOUT:

          MKI_GetBuildDate( __DATE__, szText1 );

          MKI_CopyW( szText2,

                     L"Release 2022, ", szText1, L"\n\n"

                     L"Copyright 2022 Malz++Kassner\n\n"

                     L"Developer: Bugs Bunny" );

          MessageBox( hGlobalWnd,

                      szText2, L"Sample Plug-In", MB_OK );

          fResult = true;

          break;

      }

      break;

 

    case MKI_EXECMODE_GET_PROFILE:

      // Nothing to do here...

      break;

 

    case MKI_EXECMODE_SET_PROFILE:

      // Nothing to do here...

      break;

 

    case MKI_EXECMODE_MENU_INIT:

      // Nothing to do here...

      break;

 

    case MKI_EXECMODE_READY:

      // Nothing to do here...

      break;

  }

  return( fResult );

}

 

Of course, a "direct" command can do a lot more than simply displaying a simple message box. It can be used to edit parameters, to load or save files, to execute complete applications, etc. One thing is common for all "direct" commands: They have to be completely terminated at the time the plug-in exits the MKI_PlugInCommand procedure!

 

Indirect Commands

The second type of command is the "indirect" command. The implementation of an "indirect" command is more complex than a "direct" command. Nevertheless, you will find that it is rather simple once you've done it for the first time.

 

"Indirect" commands rely on point entry performed by the user. Any kind of point entry is very complex (think of all possibilities the user has to enter points within the serving application, like snapping, grids, orthogonal mode, direct coordinate entry, multiple windows with different views, etc.!), so this task is best observed by the serving application which has the knowledge to handle all these possibilities. As a result, an "indirect" command requires some communication between the serving application (observing and handling the basic point entry tasks) and the plug-in (reacting on the point entry). In order to explain this communication, lets try to implement a very simple command: a line entry. This is an "indirect" command expecting the user to enter two points (a start-point and an end-point). During the entry of the second point, a "rubber line" should be drawn to indicate how the resulting line will look like. After the point entry is done, the plug-in has to create a line based on the entered points which is then inserted to the current drawing.

 

First, let's build a simple textual flowchart of the command "Draw Line" (having the command identifier MKI_MENU_DRAW_LINE):

 

Step 1: The user selects the external command "Draw Line"

MKI_PlugInCommand( MKI_MENU_DRAW_LINE, MKI_EXECMODE_USER )

First, the plug-in is informed that the user has selected the command "Draw Line" from the plug-in's menu. If the plug-in returns TRUE, the serving application will start the command "Draw Line". This results in a sequence of calls to plug-in-owned callback procedures as described below. For each step the procedures are called in the order of their appearance. If the plug-in returns FALSE from this procedure, the serving application will not start the command. This allows the plug-in to check whether it makes sense to run the command right now. If, e.g., the plug-in displays an initial parameter editing dialog, and the user presses the "Cancel" button, the plug-in should return FALSE.

 

Step 2: The entry of the start-point begins

MKI_InputPointInitProc( MKI_MENU_DRAW_LINE, 0, Point )

The server tells the plug-in that the entry of the first point (PointIndex = 0) starts. The current cursor position is passed in Point. Usually, this is the time to prompt for any choices that might be made for the entering of the first point. In our example, no action is required as the first point does not require any choices to be made. If the plug-in supplies its own guide window texts, this is the place to call MKI_DialogUpdateGuide with appropriate parameters for the start-point.

MKI_InputPointMoveProc( MKI_MENU_DRAW_LINE, 0, Point )

The server tells the plug-in that the current position of the first point (PointIndex = 0) is Point. Usually, this is the time to calculate (but not display!) all data that is required to display the current input status ("rubber lines"). In our example, no action is required as the first point does not require any input status to be drawn.

MKI_InputDisplayProc( MKI_MENU_DRAW_LINE, 0, Data )

The server tells the plug-in that the input status of the first point (PointIndex = 0) is to be drawn. This is the time to redraw the complete input status. In our example, no action is required as the first point does not require any input status to be drawn.

 

Step 3: Each time the user moves the cursor during the entry of the start-point

MKI_InputPointMoveProc( MKI_MENU_DRAW_LINE, 0, Point )

The server tells the plug-in that the current position of the first point (PointIndex = 0) is Point. Usually, this is the time to calculate (but not display!) all data that is required to display the current input status ("rubber lines"). In our example, no action is required as the first point does not require any input status to be drawn.

MKI_InputDisplayProc( MKI_MENU_DRAW_LINE, 0, Data )

The server tells the plug-in that the input status of the first point (PointIndex = 0) is to be drawn in. This is the time to redraw the complete input status. In our example, no action is required as the first point does not require any input status to be drawn.

 

Step 4: The user finally enters the start-point

MKI_InputPointMoveProc( MKI_MENU_DRAW_LINE, 0, Point )

The server tells the plug-in that the current position of the first point (PointIndex = 0) is Point. Usually, this is the time to calculate (but not display!) all data that is required to display the current input status ("rubber lines"). In our example, no action is required as the first point does not require any input status to be drawn.

MKI_InputPointExitProc( MKI_MENU_DRAW_LINE, 0, Point )

The server tells the plug-in that the entry of the first point (PointIndex = 0) has ended. The final position of the first point is passed in Point. Usually, this is the time to save the point coordinates and to check whether the point entered makes sense (especially if the point is used for object identification). In our example, the point coordinates are saved without any further check as the first point may be placed anywhere.

 

Step 5: The entry of the end-point begins

MKI_InputPointInitProc( MKI_MENU_DRAW_LINE, 1, Point )

The server tells the plug-in that the entry of the second point (PointIndex = 1) starts. The current cursor position is passed in Point. Usually, this is the time to prompt for any choices that might be made for the entering of the second point. In our example, no action is required as the second point does not require any choices to be made. If the plug-in supplies its own guide window texts, this is the place to call MKI_DialogUpdateGuide with appropriate parameters for the start-point.

MKI_InputPointMoveProcProc( MKI_MENU_DRAW_LINE, 1, Point )

The server tells the plug-in that the current position of the second point (PointIndex = 1) is Point. Usually, this is the time to calculate (but not display!) all data that is required to display the current input status ("rubber lines"). In our example, only the current position has to be saved as no further calculation is required.

MKI_InputDisplayProc( MKI_MENU_DRAW_LINE, 1, Data )

The server tells the plug-in that the input status of the second point (PointIndex = 1) is to be drawn. This is the time to redraw the complete input status. In our example, the plug-in has to draw a line connecting the previously entered start-point with the current cursor position.

 

Step 6: Each time the user moves the cursor during the entry of the end-point

MKI_InputPointMoveProc( MKI_MENU_DRAW_LINE, 1, Point )

The server tells the plug-in that the current position of the second point (PointIndex = 1) is Point. Usually, this is the time to calculate (but not display!) all data that is required to display the current input status ("rubber lines"). In our example, only the current position has to be saved as no further calculation is required.

MKI_InputDisplayProc( MKI_MENU_DRAW_LINE, 1, Data )

The server tells the plug-in that the input status of the second point (PointIndex = 1) is to be drawn. This is the time to redraw the complete input status. In our example, the plug-in has to draw a line connecting the previously entered start-point with the current cursor position.

 

Step 7: The user finally enters the end-point

MKI_InputPointMoveProc( MKI_MENU_DRAW_LINE, 1, Point )

The server tells the plug-in that the current position of the second point (PointIndex = 1) is Point. Usually, this is the time to calculate (but not display!) all data that is required to display the current input status ("rubber lines"). In our example, only the current position has to be saved as no further calculation is required.

MKI_InputPointExitProc( MKI_MENU_DRAW_LINE, 1, Point )

The server tells the plug-in that the entry of the second point (PointIndex = 1) has ended. The final position of the second point is passed in Point. Usually, this is the time to save the point coordinates and to check whether the point entered makes sense (especially if the point is used for object identification). In our example, the point coordinates are saved without any further check as the second point may be placed anywhere.

 

Step 8: The entry was completed, so the command has to be finished

MKI_InputFinishProc( MKI_MENU_DRAW_LINE, 2 )

The server tells the plug-in that the command was completed, the user entered two points (PointCount = 2). Usually, this is the time to create or modify objects depending on the user entry. In our example, a line with the entered start-point and end-point is to be created, inserted to the current drawing and displayed.

 

For some commands, there is a need of one or two other callback procedures to allow the user to edit parameters of the command during its execution and to determine how many points to enter:

 

Step 9: During point entry, the user selects the "Edit Parameters" command

MKI_InputParameterProc( MKI_MENU_DRAW_LINE )

The server tells the plug-in that the user wants to edit the parameters of the current command. Usually, this is the time to display a dialog window that allows viewing and editing of the current command's parameters. If this procedure returns true, the process will return to step 1, allowing the user to start again with the new parameters. In our example, no parameters are available, so there is no action to be performed here.

 

If an external command offers parameters, these parameters will often also be offered once for editing at the time the command is selected from the menu (i.e. inside the MKI_PlugInCommand procedure prior to returning TRUE).

 

Step 10: During point entry, the user presses the right mouse button to cancel the command

MKI_InputCancelProc( MKI_MENU_DRAW_LINE, 1 )

The server tells the plug-in that the user has pressed the right mouse button. This procedure is only called if the current command requires a variable number of points to be entered (like the command Polyline), and if at least one point has already been entered. Usually, this is the time to display a dialog window that asks whether to continue entering more points or to terminate the command (either with or without creating the currently entered object). In our example, the number of points is fixed, so there is no action to be performed here.

 

If an external command requires a variable number of points, it will often not display any dialog window at the time the user presses the right mouse button, but immediately create the object based on all previously entered points. If the user does not want that object, he can simply undo the last operation. For the implementation of a complex command requiring a variable number of points, have a look at the commands Draw > Curve and Draw > Surface.

 

Now, let's build a graphical flowchart based on the previous textual one. This flowchart is more common, i.e. it will not only be valid for the command "Draw Line":

 

 

Notes

The steps described above are not a complete list of all possible steps, only the most common ones. There are some situations where the callback procedures may be called in unexpected orders:

 

If the user presses the ESC key to re-enter the previous point, the plug-in will not be notified explicitly! Instead, the callback procedures will simply be called with a decremented value in the PointIndex parameter. In any case, the MKI_InputPointInitProc will first be called with the decremented PointIndex parameter to allow any pre-calculation required.
So assure that this sudden fallback will not cause data loss! Many problems occur by multiple use of a single global variable. Let's assume that the value of a global variable is calculated at PointIndex = 0, used at PointIndex = 1 and re-calculated at PointIndex == 2. If now a fallback from PointIndex = 2 to PointIndex = 1 occurs, the content of that global variable will be invalid (because it now contains the value calculated at PointIndex = 2)!

 

If the user has created a macro (using Windows' macro recorder or any similar tool) to enter a sequence of points via direct coordinate input, those points might be passed to the plug-in without intermediate calls to the MKI_InputDisplayProc callback procedure.
So assure that you do not rely on MKI_InputDisplayProc being called for each point! Place any vital calculation either inside the MKI_InputPointInitProc, inside the MKI_InputPointMoveProc or inside the MKI_InputPointExitProc.

 

Regardless of the exceptions just stated, some assumptions may be made to allow efficient coding. They will always be guaranteed by the serving application, independent of the plug-in's behavior:

 

Prior to the first call of MKI_InputDisplayProc with any given PointIndex, both the MKI_InputPointInitProc and the MKI_InputPointMoveProc have been called with the same PointIndex, i.e. you should calculate any data required for the display either in the MKI_InputPointInitProc (if it does not depend on mouse movement) or in the MKI_InputPointMoveProc(if it does depend on mouse movement).

 

Prior to the call to the MKI_InputPointExitProc with any given PointIndex, the MKI_InputPointMoveProc has been called with the same PointIndex and exactly the same coordinates passed in XPos and YPos, i.e. you do not have to re-calculate data that has already been calculated inside the MKI_InputPointMoveProc.

 

If you follow all these rules, you will find that in many cases, both the MKI_InputPointInitProc and the MKI_InputPointExitProc will have no duty, i.e. they will not have to be implemented at all!

 

Following a possible implementation of the eight callback procedures for the command "Draw Line". Normally, "empty" callback procedures that do only return the "default" value do not have to be supplied (instead, the corresponding address in the PlugInProc entry of the MKI_COMMAND_DATA structure is set to nullptr). But for this example, all eight callback procedures have been implemented to provide a complete overview!

 

C++ Source Code

//----------------------------------------------------------------------

// Static command description data block

static MKI_COMMAND_ENTRY g_acInfo =

{

  MKI_COMMAND_FIXED,

  2,

  { MKI_POINT_START, MKI_POINT_END },

  { -1, 0 },

  { nullptr, nullptr }

};

 

// Static coordinate array used to store the point coordinates

static MKI_POINT g_acPoint[2];

 

//----------------------------------------------------------------------

// Implementation of MKI_InputPointInitProc:

// Server tells plug-in that entry of point X starts

__int32

MKI_InputPointInitProc(

 __int32 f_nCommandID,

 __int32 f_nPointIndex,

 MKI_CONST_POINT_REF f_rPoint )

{

  return( MKI_INPUT_OK );

}

 

//----------------------------------------------------------------------

// Implementation of MKI_InputPointMoveProc:

// Server informs plug-in of new coordinates

bool

MKI_InputPointMoveProc(

 __int32 f_nCommandID,

 __int32 f_nPointIndex,

 MKI_CONST_POINT_REF f_rPoint )

{

  g_acPoint[PointIndex] = f_rPoint;

 

  return( true );

}

 

//----------------------------------------------------------------------

// Implementation of MKI_InputPointExitProc:

// Server tells plug-in that entry of point X has finished

__int32

MKI_InputPointExitProc(

 __int32 f_nCommandID,

 __int32 f_nPointIndex,

 MKI_CONST_POINT_REF f_rPoint )

{

  return( MKI_INPUT_OK );

}

 

//----------------------------------------------------------------------

// Implementation of MKI_InputDisplayProc:

// Server tells plug-in to draw/erase all

// required "rubber lines" for point X

void

MKI_InputDisplayProc(

 __int32 f_nCommandID,

 __int32 f_nPointIndex,

 MKI_DISPLAYDATA_PTR f_pData )

{

  if( f_nPointIndex == 1 )

  {

    MKI_InputDrawLine( g_acPoint[0], g_acPoint[1] );

  }

}

 

//----------------------------------------------------------------------

// Implementation of MKI_InputParameterProc:

// Server tells plug-in to display parameter window

// (no action required for "Draw Line")

bool

MKI_InputParameterProc( __int32 f_nCommandID )

{

  return( false );

}

 

//----------------------------------------------------------------------

// Implementation of MKI_InputCancelProc:

// Server tells plug-in that user pressed right mouse button

// (no action required for "Draw Line")

__int32

MKI_InputCancelProc(

 __int32 f_nCommandID,

 __int32 f_nPointCount )

{

  return( MKI_INPUT_FINISH );

}

 

//----------------------------------------------------------------------

// Implementation of MKI_InputFinishProc:

// Server tells plug-in to create the line

void

MKI_InputFinishProc(

 __int32 f_nCommandID,

 __int32 f_nPointCount )

{

  MKI_UndoInitProcess();

 

  MKI_ObjectOpen( MKI_OBJ_LINE );

  MKI_ObjectAddPoint( MKI_DB_POINT_START, g_acPoint[0] );

  MKI_ObjectAddPoint( MKI_DB_POINT_END,   g_acPoint[1] );

 

  if( MKI_ObjectFastInsert() )

  {

    MKI_UndoFinishProcess();

    MKI_DrawNewObjects();

  }

  else

    MKI_UndoCancelProcess();

}

 

//----------------------------------------------------------------------

// Implementation of MKI_PlugInCommand:

// Plug-in either executes a command directly or

// just asks the user for parameters before

// initializing an external command to start

MKI_INTERFACE_EXPORT __int32

MKI_PlugInCommand(

 __int32 f_nCommandID,

 __int32 f_nExecMode,

 void* f_pData )

{

  bool fResult = false;

 

  switch( f_nExecMode )

  {

    case MKI_EXECMODE_HELP:

      switch( f_nCommandID )

      {

        case MKI_MENU_DRAW_LINE:

          MKI_DialogHelpTopic( g_hGlobalWnd, L"SAMPLE", f_nCommandID );

          fResult = true;

          break;

      }

      break;

 

    case MKI_EXECMODE_USER:

    case MKI_EXECMODE_SYSTEM:

      switch( f_nCommandID )

      {

        case MKI_MENU_DRAW_LINE:

          fResult = true;

          break;

      }

      break;

 

    case MKI_EXECMODE_GET_PROFILE:

      // Nothing to do here...

      break;

 

    case MKI_EXECMODE_SET_PROFILE:

      // Nothing to do here...

      break;

 

    case MKI_EXECMODE_MENU_INIT:

      // Nothing to do here...

      break;

 

    case MKI_EXECMODE_READY:

      // Nothing to do here...

      break;

  }

  return( fResult );

}

 

//----------------------------------------------------------------------

// Extension of MKI_PlugInInit:

// Initialization of the indirect command "Line"

// based on the structure gInfo.

MKI_INTERFACE_EXPORT __int32

MKI_PlugInInit(

 const LPCSTR f_pszSerialNumber,

 HINSTANCE f_hMainInst,

 HWND f_hMainWnd,

 __int32 f_nInterfaceVersion,

 MKI_PLUGIN_ID_REF f_rID )

{

 

  // ...

 

  g_acCmds[0].m_cInputData = gInfo;

  g_acCmds[0].m_cMenuData.m_pszMenuEntry   = L"&1  Line";

  g_acCmds[0].m_cMenuData.m_pszDescription = L"Sample Plug-In >Line";

  g_acCmds[0].m_nIconMode = -1;

  g_acCmds[0].m_nType = MKI_MENUMODE_ENTRY;

 

  // ...

 

}

 

//----------------------------------------------------------------------

 

The names of the callback procedures may be freely chosen, as the address of those procedures is passed to the server in the MKI_PLUGIN_ID structure of the MKI_PlugInInit call. Anyway, it might be a good idea to always use the names stated above.

 

For further experience, you can find a more sophisticated example of external command implementation in the sources of SAMPLE_.DLL. This implementation includes additional code for language-dependent data libraries and the implementation of a dialog window for parameter editing.

 

CAD6interface 2024.2 - Copyright 2024 Malz++Kassner® GmbH