Importing ActiveX Controls  
 

In Visual C++ 6.0 and later versions, using the #import compiler directive is an alternative to adding the control to a project through the IDE. Similar to how header files are included in an application, this directive incorporates information from a type library, automatically creating wrapper classes for its interfaces. These classes use smart pointers which handle things like reference counting automatically, and make actually using the control's interface much simpler.

To use this method of referencing a control, the first thing that needs to be done is to import the control into the module where it will be used. This is done using the #import directive which can be placed in an appropriate header file. For example, to import the File Transfer Protocol control, you would use:

#import "csftpx10.ocx" no_namespace named_guids

The no_namespace attribute specifies that the interface classes should not be defined in a namespace. Normally, a namespace is created which is based on the name of the control. The named_guids attribute tells the compiler to initialize the GUID variables using the standard naming convention.

The next step is to declare a variable that is used to reference an instance of the control. For example, the following could be included in the definition of a class:

IFtpClientPtr m_pIFtpClient;

Note that the member variable is declared as type IFtpClientPtr, which is a specialization of the smart pointer _com_ptr_t template class. If errors are encountered when compiling the application indicating that the compiler cannot instantiate an abstract class (because the class contains pure virtual functions) then most likely the member variable was declared as type IFtpClient, which is incorrect. Don't forget the "Ptr" on the end of the name.

To use the control, an instance of the control must be created using the CreateInstance function. However, before that can be done, the COM subsystem must be initialized by the application. For MFC based applications, this is accomplished by calling the function AfxOleInit which is essentially a wrapper around CoInitializeEx. This should be done fairly early in the application, typically in the InitInstance function of the CWinApp derived application class. Next, the control's CreateInstance member function must be called before it is used:

HRESULT hr;
 
hr = m_pIFtpClient.CreateInstance(CLSID_FtpClient);
if (FAILED(hr))
{
    AfxMessageBox(_T("Control creation failed"), MB_ICONEXCLAMATION);
    return;
}

The HRESULT return value should be 0, which indicates that an instance of the control was created successfully. If an error is returned, this typically means that AfxOleInit (or CoInitializeEx) was not called first, or the control has not been registered on the system.

Unlike the previous examples where the initialization of the control was performed automatically or by calling the Create function, this instance of the control should be explicitly initialized by calling the Initialize method:

_variant_t varLicKey;
_variant_t varError;
USES_CONVERSION;

// Create the runtime license key defined in csrtkey10.h
varLicKey = SysAllocString(T2OLE(CSTOOLS10_LICENSE_KEY));

// Initialize the control
varError = m_pIFtpClient->Initialize(varLicKey);
if (V_I4(&varError) != 0)
{
    AfxMessageBox(_T("Control initialization failed"), MB_ICONEXCLAMATION);
    return;
}

Just as in the previous example using the Create method, the runtime license key is created by converting it to Unicode and then calling SysAllocString to create a BSTR string. Because the control methods use variants, this key is assigned to a variant. Note that the _variant_t type is used, which is a COM support class which encapsulates a variant. The Initialize method returns a long integer variant which specifies an error code. A value of zero indicates that the control was successfully initialized, while a non-zero value is an error code.

Once the control has been created and initialized, it can be used in a fashion similar to how the previous examples were written:

_variant_t varServerName(m_strServerName);
_variant_t varServerPort(m_nServerPort);
_variant_t varUserName(m_strUserName);
_variant_t varPassword(m_strPassword);
_variant_t varAccount(m_strAccount);
_variant_t varTimeout(m_nTimeout);
_variant_t varLocalFile(m_strLocalFile);
_variant_t varRemoteFile(m_strRemoteFile);
_variant_t varOptions;
_variant_t varError;

varError = m_pIFtpClient->Connect(varServerName,
                                  varServerPort,
                                  varUserName,
                                  varPassword,
                                  varAccount,
                                  varTimeout,
                                  varOptions);

if (V_I4(&varError) != 0)
{
    CString strError;
    USES_CONVERSION;

    strError.Format(_T("Unable to connect to %s\n%s"),
                    m_strServerName,
                    OLE2T(m_pIFtpClient->GetLastErrorString()));

    AfxMessageBox(strError, MB_ICONEXCLAMATION, 0);
}
else
{
    CString strMessage;
    _variant_t varRestartOffset;
    LONG nBytes ;
    
    varError = m_pIFtpClient->GetFile(varLocalFile, 
                                      varRemoteFile, 
                                      varRestartOffset);
    if (V_I4(&varError) != 0)
    {
        CString strError;
        USES_CONVERSION;

        strError.Format(_T("Unable to get %s\n%s"),
                    m_strRemoteFile,
                    OLE2T(m_pIFtpClient->GetLastErrorString()));

        AfxMessageBox(strError, MB_ICONEXCLAMATION, 0);
    }
    else
    {
        nBytes = m_pIFtpClient->TransferBytes;    
        strMessage.Format(_T("Transferred %ld bytesof %s"), 
                          nBytes, m_strRemoteFile);
        AfxMessageBox(strMessage, MB_ICONINFORMATION, 0);
    }
    m_pIFtpClient->Disconnect();
}

There are two significant differences between the previous examples which use the control as a CWnd derived class, and this class which is based on COM smart pointers. The first is that methods are accessed through the m_pIFtpClient object as a pointer to the interface, so the -> operator is used. The second is that the control's properties, such as TransferBytes, can be accessed as if they are member variables of the class rather than using accessor functions like GetTransferBytes. This is a bit of slight-of-hand being performed by the interface class using the __declspec(property) extension. For example, the TransferBytes member is declared as:

__declspec(property(get=GetTransferBytes)) long TransferBytes;

This tells the compiler whenever the TransferBytes member is read, it should call the GetTransferBytes function to return the value. So, in effect the above code is changed by the compiler into:

nBytes = m_pIFtpClient->GetTransferBytes();

Either method may be used, so it is generally up to the personal preferences of the developer as to which is used.