📄 ch05.htm
字号:
CTracker::CTracker <BR>
( <BR>
IUnknown *pUnkOuter <BR>
) <BR>
: CAutomationObject(pUnkOuter, OBJECT_TYPE_OBJTRACKER, (void *)this) <BR>
{ <BR>
<BR>
// setup our timer resolution <BR>
m_lTimeBegin = timeBeginPeriod(1); <BR>
m_lHiResTime = m_lLastHiResTime = timeGetTime(); <BR>
<BR>
SYSTEMTIME sTime; <BR>
// get the current date and time <BR>
::GetLocalTime(&sTime); <BR>
<BR>
TCHAR tstrFileName[18]; <BR>
// format the file name based on the current date <BR>
sprintf(&tstrFileName[0], "%04d%02d%02d.tracklog", <BR>
sTime.wYear, sTime.wMonth, sTime.wDay); <BR>
<BR>
// open a file <BR>
m_fileLog = fopen(&tstrFileName[0], "a"); <BR>
<BR>
// if we have a file handle <BR>
if(m_fileLog) <BR>
{ <BR>
// output some starting information <BR>
fprintf(m_fileLog, "************************\n"); <BR>
fprintf(m_fileLog, "Start %02d/%02d/%04d - %02d:%02d:%02d\n", <BR>
sTime.wMonth, sTime.wDay, sTime.wYear, <BR>
sTime.wHour, sTime.wMinute, sTime.wSecond); <BR>
fprintf(m_fileLog, "\n"); <BR>
} <BR>
} <BR>
#pragma warning(default:4355) // using `this' in constructor <BR>
<BR>
//=--------------------------------------------------------------------------= <BR>
// CTracker::CTracker <BR>
//=--------------------------------------------------------------------------= <BR>
// "We all labour against our own cure, for death is the cure of all diseases"
<BR>
// - Sir Thomas Browne (1605 - 82) <BR>
// <BR>
// Notes: <BR>
// <BR>
CTracker::~CTracker () <BR>
{ <BR>
// if we have a file handle <BR>
if(m_fileLog) <BR>
{ <BR>
SYSTEMTIME sTime; <BR>
// get the current date and time <BR>
::GetLocalTime(&sTime); <BR>
<BR>
// output some closing information <BR>
fprintf(m_fileLog, "\n"); <BR>
fprintf(m_fileLog, "End %02d/%02d/%04d - %02d:%02d:%02d\n", <BR>
sTime.wMonth, sTime.wDay, sTime.wYear, <BR>
sTime.wHour, sTime.wMinute, sTime.wSecond); <BR>
fprintf(m_fileLog, "************************\n"); <BR>
<BR>
// close the file <BR>
fclose(m_fileLog); <BR>
} </TT></FONT></P>
<P><FONT COLOR="#0066FF"><TT><BR>
} </TT></FONT></P>
<P>The last thing you need to do is update the build settings for the project. Since
the sample implementation is using some timer functions defined in mmsystem.h, you
need to link with the appropriate library file that contains their implementation.
Under the <U>P</U>roject menu, select the <U>S</U>ettings menu item. In the Project
Settings dialog select the Link tab, and add the file winmm.lib to the Object/<U>l</U>ibrary
modules edit field. Click OK to close the dialog.</P>
<P>The basic support code needed for the sample implementation has been added. The
server will open a file in its constructor and leave the file open during its entire
lifetime. When the server is destroyed, the destructor will be called, and the file
will be closed.</P>
<P>The next step is to make the sample more meaningful by adding methods and properties,
which are used to output data to the open file.
<H2><A NAME="Heading8"></A>Adding Methods</H2>
<P>An automation method consists of zero to <I>n</I> parameters and may or may not
have a return value. The term method is synonymous with function or subroutine, depending
on the particular language you are familiar with. Since your server is <TT>IDispatch</TT>-based,
you are limited to a specific set of data types. Only those data types that are valid
<TT>VARIANT</TT> data types can be passed or returned via a method.</P>
<P>The rules for declaring parameters and how they are used is very much like C++
and VB. Methods can pass parameters by value or by reference and may also declare
them as optional, meaning that the parameter does not have to be supplied.</P>
<P>When passing a parameter by value, a copy of the data is sent to the method. When
passing by reference, the address of the parameter is passed, allowing the method
to change the data.</P>
<P>Optional parameters are handled a little differently than C++, however, because
you can't specify a default value in the traditional C++ sense. Optional parameters
must be passed as <TT>VARIANT</TT> data types and not the actual data type they represent.</P>
<P>For developers using VB to access a method with optional parameters, VB will supply
the parameter for you if one has not been provided. With C++, you are still required
to supply a <TT>VARIANT</TT> parameter, even though it may not contain any data.</P>
<P>As we stated at the beginning of the chapter, the sample automation server will
be used to log strings of data to a file. The server will define the method <TT>OutputLines</TT>,
which is used by the user of the server to supply the string data that is written
to the file. The method will accept an array of strings and an optional indentation
parameter and will output the strings to the file. The indentation parameter is used
to offset the strings by <I>n</I> number of tab characters to provide simple, yet
effective, formatting to the data as it is output to the file.</P>
<P>All work involving methods and properties in BaseCtl (and also ATL) starts with
the ODL (or IDL) file. Methods and properties are declared here, first, and then
implemented in the server.</P>
<P>BaseCtl Automation Servers are dual-interface by default, so you must declare
all of the methods and properties so as to conform to standard dual-interface rules.
Refer to <A HREF="ch03.htm">Chapters 3</A> and <A HREF="ch04.htm">4</A> for more
information on dual-interface.</P>
<P>Listing 5.4 shows the declaration of the <TT>OutputLines</TT> method.</P>
<P><TT>OutputLines</TT> is defined as having two parameters: <TT>varOutputArray</TT>,
as a <TT>VARIANT</TT> passed by reference that will contain a string array of data
to output to the file, and <TT>varIndent</TT>, as a <TT>VARIANT</TT> passed by value,
which is also an optional parameter indicating the amount of indentation when writing
the string data to the file. The third parameter is actually the return type of the
method and is defined as a <TT>Boolean</TT>.</P>
<P>See <A HREF="ch03.htm">Chapter 3</A> regarding the use of Boolean data types and
the differences between VB and VC++.</P>
<P>Due to data type restrictions imposed by OLE Automation, you cannot pass arrays
as parameters of methods. You can, however, pass <TT>VARIANT</TT> data types that
can contain arrays, thus the reason for defining <TT>varOutputArray</TT> as a Variant.
You are also required to pass <TT>varOutputArray</TT> by reference because the array
stored in the VARIANT does not get copied over when it is passed by value.</P>
<P>Optional parameters must fall at the end of the parameter list and must be of
type <TT>VARIANT</TT>. <TT>varIndent</TT> is an optional parameter that indents your
text output as an added formatting feature (see Listing 5.4).
<H3><A NAME="Heading9"></A>Listing 5.4 BCFSERVER.ODL--OutputLines Method ODL Declaration</H3>
<P><FONT COLOR="#0066FF"><TT>. . . <BR>
[ <BR>
uuid(5ea29be0-5a82-11d0-bcbc-0020afd6738c), <BR>
helpstring("Tracker Object"), <BR>
hidden, <BR>
dual, <BR>
odl <BR>
] <BR>
interface ITracker : IDispatch { <BR>
<BR>
// properties <BR>
// <BR>
<BR>
// methods <BR>
[id(2)] HRESULT OutputLines([in] VARIANT * varOutputArray, <BR>
[in, optional] VARIANT varIndent, [out, retval] boolean * RetVal); <BR>
}; <BR>
<BR>
// coclass for CTracker objects <BR>
// <BR>
</TT></FONT></P>
<P><FONT COLOR="#0066FF"><TT><BR>
. . . </TT></FONT></P>
<P>When the ODL file is compiled into a type library, the compiler will create a
header file that contains all of the interface and <TT>CLSID</TT> declarations in
C++ format. It is from this file that you will copy all of the function prototypes
that are needed in your server implementation for defining the methods and properties
that it contains. From the file BCFServerInterfaces.h, copy the <TT>OutputLines</TT>
function prototype, and paste it into the class definition of the server (see Listing
5.5). Remember to remove the <TT>PURE</TT> keyword from the function prototype.</P>
<P>For the purposes of the sample implementation, you also need to add the <TT>m_lIndent</TT>
member variable, which is used in the <TT>OutputLines</TT> method implementation,
and later as a property of the server.
<H3><A NAME="Heading10"></A>Listing 5.5 TRACKER.H--OutputLines Function Prototype
Added to the Class Definition</H3>
<P><FONT COLOR="#0066FF"><TT><BR>
. . . <BR>
<BR>
CTracker(IUnknown *); <BR>
virtual ~CTracker(); <BR>
<BR>
// ITracker methods <BR>
STDMETHOD(OutputLines)(VARIANT FAR* varOutputArray, VARIANT varIndent, <BR>
VARIANT_BOOL FAR* RetVal); <BR>
<BR>
// creation method <BR>
// <BR>
static IUnknown *Create(IUnknown *); <BR>
<BR>
protected: <BR>
virtual HRESULT InternalQueryInterface(REFIID riid, void **ppvObjOut); <BR>
<BR>
private: <BR>
// member variables that nobody else gets to look at. <BR>
// TODO: add your member variables and private functions here. <BR>
<BR>
protected: <BR>
FILE * m_fileLog; <BR>
long m_lTimeBegin; <BR>
long m_lHiResTime; <BR>
long m_lLastHiResTime; <BR>
long m_lIndent; <BR>
}; <BR>
</TT></FONT></P>
<P><FONT COLOR="#0066FF"><TT><BR>
. . .</TT></FONT></P>
<PRE><FONT COLOR="#0066FF"><TT></TT></FONT></PRE>
<P>Before adding the <TT>OutputLines</TT> implementation, it is necessary to update
the constructor to initialize the <TT>m_lIndent</TT> member variable to a valid state
(see Listing 5.6).
<H3><A NAME="Heading11"></A>Listing 5.6 TRACKER.CPP--Member Initialization in the
Constructor</H3>
<P><FONT COLOR="#0066FF"><TT><BR>
. . . <BR>
<BR>
// output some starting information <BR>
fprintf(m_fileLog, "************************\n"); <BR>
fprintf(m_fileLog, "Start %02d/%02d/%04d - %02d:%02d:%02d\n", <BR>
sTime.wMonth, sTime.wDay, sTime.wYear, <BR>
sTime.wHour, sTime.wMinute, sTime.wSecond); <BR>
fprintf(m_fileLog, "\n"); <BR>
} <BR>
<BR>
m_lIndent = 0; <BR>
} </TT></FONT></P>
<P><FONT COLOR="#0066FF"><TT><BR>
#pragma warning(default:4355) // using `this' in constructor </TT></FONT></P>
<P>Next you add the <TT>OutputLines</TT> implementation to the source file as in
Listing 5.7. The implementation varies very little from the ATL sample.</P>
<P>As with the MFC and ATL implementations, the BaseCtl version checks the array
parameter to ensure its validity and, if so, outputs the data to the file, indenting
the text if appropriate. See <A HREF="ch03.htm">Chapter 3 </A>for more information
regarding the other implementation details.</P>
<P>For now, the implementation returns <TT>VARIANT_FALSE</TT> in the cases where
an error has occurred. Later in this chapter, you will learn how to create rich error
information.
<H3><A NAME="Heading12"></A>Listing 5.7 TRACKER.CPP--OutputLines Function Implementation
Added to the Source File Tracker.cpp</H3>
<P><FONT COLOR="#0066FF"><TT><BR>
STDMETHODIMP CTracker::OutputLines(VARIANT FAR* varOutputArray, VARIANT varIndent,
VARIANT_BOOL FAR* RetVal) <BR>
{ <BR>
HRESULT hResult = S_OK; <BR>
<BR>
*RetVal = VARIANT_TRUE; <BR>
<BR>
// if we have a file and if the variant contains a string array <BR>
if(m_fileLog && varOutputArray->vt == (VT_ARRAY | VT_BSTR)) <BR>
{ <BR>
// lock the array so we can use it <BR>
if(::SafeArrayLock(varOutputArray->parray) == S_OK) <BR>
{ <BR>
LONG lLBound; <BR>
<BR>
// get the lower bound of the array <BR>
if(::SafeArrayGetLBound(varOutputArray->parray, 1, &lLBound) == S_OK) <BR>
{ <BR>
LONG lUBound; <BR>
<BR>
// get the number of elements in the array <BR>
if(::SafeArrayGetUBound(varOutputArray->parray, 1, &lUBound) == S_OK) <BR>
{ <BR>
SYSTEMTIME sTime; <BR>
BSTR bstrTemp; <BR>
<BR>
// if we have an indent parameter <BR>
if(varIndent.vt != VT_I4) <BR>
{ <BR>
// get a variant that we can use for conversion purposes <BR>
VARIANT varConvertedValue; <BR>
<BR>
// initialize the variant <BR>
::VariantInit(&varConvertedValue); <BR>
<BR>
// see if we can convert the data type to something useful <BR>
// - VariantChangeTypeEx() could also be used <BR>
if(S_OK == ::VariantChangeType(&varConvertedValue, <BR>
(VARIANT *) &varIndent, 0, VT_I4)) <BR>
// assign the value to our member variable <BR>
m_lIndent = varConvertedValue.lVal; <BR>
} <BR>
else <BR>
// assign the value to our member variable <BR>
m_lIndent = varIndent.lVal; <BR>
<BR>
// for each of the elements in the array <BR>
for(long lArrayCount = lLBound; lArrayCount < <BR>
(lUBound + lLBound); lArrayCount++) <BR>
{ <BR>
// get the current date and time <BR>
::GetLocalTime(&sTime); <BR>
m_lHiResTime = timeGetTime(); <BR>
<BR>
// get the data from the array <BR>
if(::SafeArrayGetElement(varOutputArray->parray, <BR>
&lArrayCount, &bstrTemp) == S_OK) <BR>
{ <BR>
// output the data <BR>
fprintf(m_fileLog, "%02d:%02d:%02d(%10ld)-", <BR>
sTime.wHour, sTime.wMinute, sTime.wSecond, m_lHiResTime - m_lLastHiResTime); <BR>
<BR>
// if we have to indent the text <BR>
for(long lIndentCount = 0; lIndentCount < m_lIndent; <BR>
lIndentCount++) <BR>
// add a tab to the string <BR>
fprintf(m_fileLog, "\t"); <BR>
<BR>
// output the data <BR>
fprintf(m_fileLog, "%ls\n", bstrTemp); <BR>
<BR>
// store the last timer value <BR>
m_lLastHiResTime = m_lHiResTime; <BR>
<BR>
// free the bstr <BR>
::SysFreeString(bstrTemp); <BR>
} <BR>
} <BR>
} <BR>
else <BR>
*RetVal = VARIANT_FALSE; <BR>
} <BR>
else <BR>
*RetVal = VARIANT_FALSE; <BR>
<BR>
// unlock the array we don't need it anymore <BR>
::SafeArrayUnlock(varOutputArray->parray); </TT></FONT></P>
<P><FONT COLOR="#0066FF"><TT><BR>
} <BR>
else </TT></FONT></P>
<P><FONT COLOR="#0066FF"><TT><BR>
*RetVal = VARIANT_FALSE; <BR>
} <BR>
else <BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -