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