Methods: A function by any other name means just the same

The next program displays the names of the OCX methods in a list box within a dialog. All we do in this program is sequentially access each element of the link-list that stores the method details. We pick up the name of the method, it's return type, the names and data types of the parameters. We then use suitable string concatenation to display the resultant string in the list box.
The link-list is our way of doing things. We do not know how Microsoft did it.

Program 7

 BOOL MDialog::OnInitDialog()
 {
 CDialog::OnInitDialog(); 
 if(z_nID==IDD_DIALOG1)
 {
  
  // Retain all previously existing code 
 
 }      
 else
 {            
  CListBox* z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
  FData *z_pFDNext;
  
  z_pFDNext = z_pFDFirst;
  while(z_pFDNext)
  {
  char *z_sTemp;
  
  z_sTemp = (char*) malloc(50);
  sprintf(z_sTemp,"%s    %s(",z_VarType[z_pFDNext-> z_nRType],z_pFDNext-> z_sFName);
  if(z_pFDNext-> z_nNumParams)
    for(int z_nParams = 0; z_nParams < z_pFDNext-> z_nNumParams; z_nParams++)
    {
     if(z_nParams)
      strcat(z_sTemp,",");
     strcat(z_sTemp,z_VarType[z_pFDNext-> z_pPTypes[z_nParams]]);
     strcat(z_sTemp," ");
     strcat(z_sTemp,z_pFDNext-> z_sPName[z_nParams]);
    }
  strcat(z_sTemp,")");
  z_pListBox-> AddString(z_sTemp);
  free(z_sTemp);
  z_pFDNext = z_pFDNext-> z_pNext;
  }     
  z_pListBox-> SetCurSel(0);
  return TRUE;
 }             
 return TRUE;
 }       
 
 void MFrameWnd::Methods()
 {
 MDialog z_Dialog(IDD_DIALOG3);
 
 z_Dialog.DoModal();
 }
Select an OCX for display. Choose the Methods... sub-option under Display. This will bring up a dialog box with all the methods of the OCX listed in it. In OnInitDialog(), we read the relevant details about the OCX's methods from the link-list. Using suitable string concatenation, we populate a list box in the dialog box.

Executing a function: At the touch of a button

Executing the OCX methods is no simple task. The conventional practice whereby a function is executed by using it's name along with parameters is no longer applicable. This is because the methods are a part of the OCX and we are calling them from the container. Moreover, these are not functions that are a part of an interface. So the OLE way of QueryInterface() cannot be used either.
Microsoft has defined an interface, IDispatch, to handle this situation. As mentioned before every method, property and event of the OCX has an ID. These IDs are used to access them. If we want to execute a certain method, primarily we have to pass it's ID to the OCX. In addition, we have to pass the parameters of the method to the OCX and obtain the return value, if any.
All this is accomplished by calling the function Invoke() of the IDispatch interface of the OCX. It is the responsibility of this function to execute the OCX method indicated by the ID. Talking about the semantics does not help. We have to see it work. The following code will display a dialog in which the methods of the OCX are displayed. The user of the OCX can select the method to be executed and enter parameters that are to be passed to it. The user can then click on a button to execute the method.

Program 8

 BOOL MDialog::OnInitDialog()
 {
 CDialog::OnInitDialog(); 
 if(z_nID==IDD_DIALOG1)
 {
  
  // Retain all previously existing code 
 
 }      
 else
 {            
  CListBox* z_pListBox;
  FData *z_pFDNext;                   
  
  z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
  z_pFDNext = z_pFDFirst;
  while(z_pFDNext)
  {
  char *z_sTemp = (char*) malloc(50);
 
  sprintf(z_sTemp,"%s    %s(",z_VarType[z_pFDNext-> z_nRType],z_pFDNext-> z_sFName);
  if(z_pFDNext-> z_nNumParams)
   for(int z_nParams = 0; z_nParams < z_pFDNext-> z_nNumParams; z_nParams++)
   {
   if(z_nParams)
    strcat(z_sTemp,",");
   strcat(z_sTemp,z_VarType[z_pFDNext-> z_pPTypes[z_nParams]]);
   strcat(z_sTemp," ");
   strcat(z_sTemp,z_pFDNext-> z_sPName[z_nParams]);
   }
   strcat(z_sTemp,")");
   z_pListBox-> AddString(z_sTemp);
   free(z_sTemp);
   z_pFDNext = z_pFDNext-> z_pNext;
  }     
  CEdit *z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
  z_pEdit-> ShowWindow(0);
  return TRUE;
 }                          
 return TRUE;
 }       
 
 void MFrameWnd::Single()
 {
 MDialog m_Dialog(IDD_DIALOG5);
 
 m_Dialog.DoModal();
 }
 
 void MDialog::Select()
 {          
 if(z_nID==IDD_DIALOG5)
 {
  CListBox* z_pListBox;
  CEdit *z_pEdit;
  int z_nIndex;
  CString z_sSel;               
  
  
  z_pListBox=(CListBox*)GetDlgItem(IDC_LIST1);
  z_nIndex = z_pListBox-> GetCurSel();
  z_pListBox-> GetText(z_nIndex,z_sSel);
  z_pFDSearch = z_pFDFirst;
  while(z_pFDSearch)
  {
  if(z_sSel.Find(z_pFDSearch-> z_sFName) == -1)
   z_pFDSearch = z_pFDSearch-> z_pNext; 
  else
   break;
  }
  z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
  if(z_pFDSearch-> z_nNumParams)
  {
  z_pEdit-> SetWindowText("");
  z_pEdit-> ShowWindow(1);
  } 
  else
  z_pEdit-> ShowWindow(0);
 }
 }
 
 void MDialog::SingleFunc()
 {
  DISPPARAMS z_DispParam;
  VARIANTARG *z_Varg;    
  VARIANT z_Variant;
  IDispatch *z_pDispatch;  
  CEdit *z_pEdit;
  
  _fmemset(& z_DispParam,0,sizeof(DISPPARAMS));
  z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
  if(z_pFDSearch-> z_nNumParams)
  {
  z_DispParam.cArgs = z_pFDSearch-> z_nNumParams;
  z_Varg = (VARIANTARG*) malloc (z_pFDSearch-> z_nNumParams * sizeof(VARIANTARG));
  for(int  z_nTemp  =  0;z_nTemp < z_pFDSearch-> z_nNumParams;z_nTemp++)
  {
   char *z_sData;
   int z_nCopied;
   
   z_sData = (char*)malloc(15);
   z_nCopied = z_pEdit-> GetLine(z_nTemp,z_sData,15); 
   *(z_sData + z_nCopied)='\0'; 
   Initialize(z_Varg,z_sData,z_nTemp);
   free(z_sData);
  }
  z_DispParam.rgvarg = z_Varg;
  } 
  z_pOleObject-> QueryInterface(IID_IDispatch,(void**)& z_pDispatch);
  z_pDispatch-> Invoke(z_pFDSearch-> z_lDispID,IID_NULL,1033,DISPATCH_METHOD,& z_DispParam,& z_Variant,0,0);   
  RetVal(& z_Variant);
  z_pDispatch-> Release(); 
  if(z_pFDSearch-> z_nNumParams)
  free(z_Varg);
 }
 
 void MDialog::Initialize(VARIANT *z_VargTemp,char *z_sParams,int z_nIndex)
 {
 z_VargTemp[z_pFDSearch-> z_nNumParams - z_nIndex - 1].vt = z_pFDSearch-> z_pPTypes[z_nIndex];
 if(!strcmp(z_VarType[z_pFDSearch-> z_pPTypes[z_nIndex]] ,"VT_I2"))
  z_VargTemp[z_pFDSearch-> z_nNumParams - z_nIndex  - 1].iVal=atoi(z_sParams);
 if(!strcmp(z_VarType[z_pFDSearch-> z_pPTypes[z_nIndex]] ,"VT_I4"))
  z_VargTemp[z_pFDSearch-> z_nNumParams - z_nIndex  - 1].lVal=atol(z_sParams);
 if(!strcmp(z_VarType[z_pFDSearch-> z_pPTypes[z_nIndex]] ,"VT_R4"))
  z_VargTemp[z_pFDSearch-> z_nNumParams - z_nIndex  - 1].fltVal=(float)atof(z_sParams);
 if(!strcmp(z_VarType[z_pFDSearch-> z_pPTypes[z_nIndex]] ,"VT_BSTR"))
 { 
  z_VargTemp[z_pFDSearch-> z_nNumParams - z_nIndex  - 1].bstrVal=(char*) malloc(strlen(z_sParams) + 1);
  strcpy(z_VargTemp[z_pFDSearch-> z_nNumParams - z_nIndex - 1].bstrVal,z_sParams);
 }
 }
 
 void MDialog::RetVal(VARIANT *z_pVariant)
 {  
 char z_sTemp[200];        
 VARTYPE z_Vt;
 z_Vt = z_pVariant-> vt;
 if(z_Vt > 13)                                 
  z_Vt = z_Vt - 2; 
 if(!strcmp(z_VarType[z_Vt],"VT_I2"))
  sprintf(z_sTemp,"Data  Type  =  %s\nValue  = %d",z_VarType[z_Vt],z_pVariant-> iVal);
 if(!strcmp(z_VarType[z_Vt],"VT_I4"))
  sprintf(z_sTemp,"Data  Type  =  %s\nValue  = %ld",z_VarType[z_Vt],z_pVariant-> lVal); 
 if(!strcmp(z_VarType[z_Vt],"VT_R4"))
  sprintf(z_sTemp,"Data  Type  =  %s\nValue  = %4.3f",z_VarType[z_Vt],z_pVariant-> fltVal);
 if(!strcmp(z_VarType[z_Vt],"VT_BSTR"))
  sprintf(z_sTemp,"Data  Type  =  %s\nValue  = %s",z_VarType[z_Vt],z_pVariant-> bstrVal);
 if(!strcmp(z_VarType[z_Vt],"VT_EMPTY"))
  strcpy(z_sTemp,"No return value");
 MessageBox(z_sTemp,"The return value details"); 
 } 
Select the sub-option Invoke A Single Method... under the Edit menu option. This will bring up a dialog titled `Executing Single Methods' which has a list box populated with the names of the OCX methods, the names and data types of the parameters and the return type of the method.
There is a multi-line edit control in which we are to enter each of the parameters on a separate line and in the proper order. This edit control is displayed only when the user selects a method that is to be passed some parameters. In the case of methods where there are no parameters to be passed, the edit control is hidden from view.
We select the method to be executed from the listbox by highlighting it. When we have entered the parameters; if any; we are to click on the push button labelled 'Invoke'. This will cause the selected method to be executed.
The function Single() gets executed when we select the menu option Invoke A Single Method.... The function displays the appropriate dialog box. In OnInitDialog(), we use the details stored in the link-list holding the information about the methods and appropriate string concatenation to populate the listbox in the dialog.
The function Select() is responsible for the display of the edit control. This function is called every time the user selects a new method from the list box. This is accomplished by the use of the ON_LBN_SELCHANGE() macro. Select() uses the link-list to determine if the selected function is to be passed any parameters and appropriately displays or hides the edit control. When the user clicks on the `Invoke' button, the function SingleFunc() gets executed. As stated earlier, to execute an OCX method, we use the function Invoke() of the IDispatch interface of the OCX.
The parameters to the accessed OCX method are passed using VARIANTARG structures. The number of parameters vary from method to method and hence we create an array of VARIANTARG structures. The length of this array is equal to the number of parameters that a method requires.
The VARIANTARG structure contains a variable vt that is initialized to hold the data type of the parameter. We use the enumerators listed under VARENUM in assigning a value to vt. Since, the type of variable required to hold the data would change with the data type; the VARIANTARG structure is provided with a union.
The union consists of variables of different data types. The variable of the union selected for initialization depends on the data type of the parameter. The VARIANTARG structure will, thus, hold the data type and the actual value of the parameter.
There is a catch to the situation. The array of VARIANTARG structures has to be initialized in the reverse order. That is the last member of the array of VARIANTARG structure will hold details about the first parameter.
In Initialize(), we use the data type of the parameter read from the link-list to convert the data which is in the form of a string to the appropriate type. The data type determines the appropriate variable in the union of the VARIANTARG structure to be initialized. We have to pass the array of the VARIANTARG structures to the OCX. In addition, we have also to indicate the number of parameters. This information is packaged into a DISPPARAMS structure.
The DISPPARAMS structure holds the number of parameters of the method. It has an element that is to be initialized to point to the start of the array of VARIANTARG structures. Thus, the OCX will retrieve all information from the DISPPARAMS structure and execute the selected method.
Invoke() is to be passed a total of eight parameters.  The first is the ID of the method to be executed. This helps the OCX  identify  the  method  to  be  executed. The Invoke() function, besides being used to execute methods, can also be used to access properties of the OCX. We have to indicate the nature of the operation Invoke() is being used for. The fourth parameter to Invoke() indicates the nature of the accessed member of the OCX. In this case, it is set to DISPATCH_METHOD. The fifth parameter is used to pass the loaded DISPPARAMS structure to the OCX.
Merely executing a method is not enough. It is also essential that we are able to reap the benefits of what we sow. The return value from the method executed is just as important. This value is returned into a VARIANT structure. The address of the VARIANT structure is passed as the sixth parameter of the function Invoke(). It is upto the user of the function to extract the return value from this structure.
VARIANT is another name for a VARIANTARG structure. To extract the return values from this structure we first determine the data type of the return value. This is stored in the variable vt of the VARIANT structure. Just as with the VARIANTARG structure, the value in vt decides which element in the union within the structure will hold the actual value returned from the OCX method. For example, if vt is equal to VT_I2, the data is stored in the member iVal of the union within the VARIANT structure. If vt is VT_BSTR, the return value is stored in bstrVal.
In the above explanation, what we didn't tell you is that the return value is always stored in the same fashion in the union. How it is accessed differs depending on the data type. The function RetVal() is dedicated to handling return values. It extracts the data type from the VARIANT structure and uses it to convert the data into a string. This string is displayed in a messagebox.

Executing multiple functions: The more the merrier

The above method of executing OCX functions is rarely followed in real-life. In true-to-life applications a user would write the name of the function and it's parameters as a part of the code. For example in the case of Visual Basic; calls to OCX methods will be written by the user as a part of the script. Visual Basic will determine if a function call refers  to  an OCX method. If yes, then it uses Invoke() to call the appropriate OCX method. This means that we could call one or more OCX methods as and when required. The following program will allow us to call more than one OCX method at a time.

Program 9

 
 void MFrameWnd::Multiple()
 {
  MDialog z_Dialog(IDD_DIALOG6);
 
  z_Dialog.DoModal();
 }
 
 void MDialog::Many()
 {
  CEdit *z_pEdit;
  int z_nCount;
  
  z_pEdit=(CEdit*)GetDlgItem(IDC_EDIT1);
  z_nCount = z_pEdit-> GetLineCount();
  for (int z_nTemp = 0; z_nTemp < z_nCount;z_nTemp++)
  {
   char *z_sSel;
   int z_nLen;
   
   z_sSel = (char*)malloc(30);
   z_nLen=z_pEdit-> GetLine(z_nTemp,z_sSel,30);                               
   
   if(!z_nLen)
      break;
   z_sSel[z_nLen] = '\0';
   FuncExec(z_sSel);    
   free(z_sSel);
  }                   
 } 
 
 void MDialog::FuncExec(char *z_sSel)
 {   
  DISPPARAMS z_DispParam;
  VARIANTARG *z_Varg;
  VARIANT z_Variant;                        
  IDispatch *z_pDispatch;
  int z_nPos;
  char *z_sFName;
 
  _fmemset(& z_DispParam,0,sizeof(DISPPARAMS));  
  z_pFDSearch = z_pFDFirst;
  z_nPos = strcspn(z_sSel,"(");
  z_sFName = (char*)malloc(strlen(z_sSel) + 1);
  strcpy(z_sFName,z_sSel);
  z_sFName[z_nPos] = '\0'; 
  while(z_pFDSearch)
  {                
   if(!strcmp(z_pFDSearch-> z_sFName,z_sFName))
    break;
   z_pFDSearch= z_pFDSearch-> z_pNext;
  }
  free(z_sFName);
  while(z_nPos > = 0)
  {
   z_sSel++;
   z_nPos--;
  }  
  if(z_pFDSearch-> z_nNumParams)
  {
   z_DispParam.cArgs=z_pFDSearch-> z_nNumParams;
   z_Varg = (VARIANTARG*) malloc (z_pFDSearch-> z_nNumParams * sizeof(VARIANTARG));
   for(int z_nTemp1 = 0; z_nTemp1 < z_pFDSearch-> z_nNumParams ;z_nTemp1++)
   {
        char *z_sParams = (char*)malloc(10);
       z_nPos = 0;
       while(( *z_sSel  != ' ') & & ( *z_sSel !=',') & & (*z_sSel != ')'  ))
       {
         *(z_sParams + z_nPos)=*z_sSel;
         z_nPos++;     
         z_sSel++;
       }                     
       *(z_sParams + z_nPos)= '\0';
       z_sSel++;
       Initialize(z_Varg,z_sParams,z_nTemp1); 
       free(z_sParams);
     }
     z_DispParam.rgvarg=z_Varg;
   } 
   z_pOleObject-> QueryInterface(IID_IDispatch,(void**)& z_pDispatch);
   z_pDispatch-> Invoke(z_pFDSearch- > z_lDispID,IID_NULL,1033,DISPATCH_METHOD,& z_DispParam,& z_Variant,0,0);                                           
   RetVal(& z_Variant);
   z_pDispatch-> Release();                                                        
 }

Select  the  Invoke Multiple Methods... sub-option under Edit. This will bring up a dialog box in which the methods of the selected OCX are displayed in a list-box. In the edit control within the dialog, enter the methods to be executed in the format:
NameOfFunction(Param 1,Param 2...,Param n)
Each  method  is added on a separate line. Click on the button `Invoke' to see the methods executed one-by-one in the order that they have been entered.
OnInitDialog() is once again responsible for populating the listbox of the dialog. When the user clicks on 'Invoke', the function Many() gets executed. All this function does is it picks up each string from the multi-line edit control and passes it to FuncExec(). In FuncExec(), we use string parsing to determine the name of the method. The methods link-list is searched to determine the number and type of the parameters for the method. If the method has any parameters we create a suitable length array of VARIANTARG structures.
We extract each parameter value in turn from the string read from the edit control. The extracted parameter value is in the form of a string and is passed to Initialize(). This routine is repeated for each parameter of the method. The DISPPARAMS structure is appropriately initialized and then Invoke() is called. Thus, the method gets executed. RetVal() is called to display the return values if any. This entire process is repeated for every method entered in the edit control.
PowerBuilder and Visual Basic are examples of the kind of applications that would eventually act as OCX containers. Each of these has it's own programming language. We did not want to write our own programming language because it would add a lot of extraneous code, code that has more to do with UI than with OLE. So we execute methods the way we do. This is not only true of methods but also of properties and events.

Continue