tutorial_1.cpp

C++ Tutorial Step 1: Subclassing and Login

Let's start with the most basic Skype functions: login and logout.

In this exercise, we'll examine a program that

Code Walkthrough

First, our program will need to instantiate two objects:

skype = new Skype();
getKeyPair();
skype->init(keyBuf, inetAddr, portNum, "streamlog.txt");
skype->start();

Note that this code assumes the SkypeKit Runtime is ALREADY running. It doesn't launch the runtime automatically.

The getKeyPair() function will load a valid key pair from keypair.pem file into keyBuf array. The loader function comes from the keypair.h (cpp/tutorials/keypair) and assumes the pem file is downloaded to the same directory. In production applications, you will need to replace this loading mechanism with some alternative - as you do not want your developer key to be distributed as a plain text file together with your application. Essentially, you will need to embed and obfuscate the keypair string inside your application.

skype->start() will start the skype event loop in its own thread. (Note to SkyHost developers: This means that, unlike with SkyHost, you no longer need to check for events in your own UI thread.)

Now our skype object is connected to the SkypeKit Runtime. Next, we'll need an account. For purposes of this example, we'll skip account creation and assume that the username and password we received as command-line arguments are valid for an existing account.

MyAccount::Ref account;
skype->GetAccount(accountName, account)

MyAccount is a class derived from Account. (More about this later.) MyAccount::Ref is a typedef for smart pointers to an Account-derived object. Basically, these two statements:

MyAccount * account;
MyAccount::Ref account;

are sort of the same thing. The benefit of using smart pointers is in enabling the wrapper to do decent memory management. The wrapper objects are often generated in a rather unpredictable manner - think of incoming chat messages for example. Keeping track of all the instances of all the classes and then deleting them as they become obsolete would be quite a chore. Smart pointers relieve us from that.

However, there is a small cost to using smart pointers. Namely, we can only have one level of inheritance from the base Account class. That is, we can derive MyAccount from Account, but not MyMyAccount from MyAccount. We can still derive MyAccountTypeA and MyAccountTypeB, as long as they are both derived from Account.

Usage-wise the smart pointer is exactly the same as s normal pointer. We can access all the methods inherited from Account, as usual:

MyAccount::Ref account;
account->LoginWithPassword();

However, to use methods added in your own custom class (in this case MyAccount), we will need to add this line to the public section of your class definition:

class MyAccount : public Account
{
public:
  typedef DRef<MyAccount, Account> Ref;
};

Otherwise, if your MyAccount class has a custom method MyCustomLogin

Account::Ref account;
account->MyCustomLogin();

..would not work. This, however works just fine:

MyAccount::Ref account;
account->MyCustomLogin();

Now, it is perhaps not the case with Account class in particular, but with most other classes it is also good to have a strongly typed lists. For this, there is another typedef: typedef DRefs<MyAccount, Account> Refs

which enables you to access class methods and properties directly, via index:

MyAccount::Refs accountList;
accountList.append(account);
accountList[0]->Login();

So, for future reference, all derived SkypeKit classes, except your derived Skype class, should start with these two typedefs, like this:

class MyAccount : public Account
{
public:
  typedef DRef<MyAccount, Account> Ref;
  typedef DRefs<MyAccount, Account> Refs;
  ...
};

Now back to our login sequence:

The GetAccount() returns a bool value, indicating whether the account object retrieval was successful. If so, the second argument will return with a reference to a valid account object.

To log in, we call the Account::LoginWithPassword(MyPassword), like this:

SEString accountPsw = "yourpasswordhere";
account->LoginWithPassword(accountPsw, false, false);

The first of the two boolean arguments specifies whether the supplied password will be saved in the local database (so that for subsequent logins we can use Login instead of LoginWithPassword). Passing false to the second argument prevents all updates to the local database, which makes sense for preliminary testing purposes.

So we're all logged in now, right? Wrong! As you probably know from using standard Skype clients, account login is not instantaneous. Account::LoginWithPassword only initiates the login process. It is asynchronous -- that is, the call returns after immediately making the request. So, how will we know the login was successful?

We do this by catching the account's status property change event. To do that, we derive our own account class, like this:

class MyAccount : public Account
{
public:
  typedef DRef<MyAccount, Account> Ref;
  typedef DRefs<MyAccount, Account> Refs;
  bool loggedIn;
  bool loggedOut;
  MyAccount(unsigned int oid, SERootObject* root);
  void OnChange(int prop);
};

MyAccount::MyAccount(unsigned int oid, SERootObject* root) : Account(oid, root) 
{
  loggedIn = false;
  loggedOut = false;
};

void MyAccount::OnChange(int prop)
{
  if (prop == Account::P_STATUS)
  {
    Account::STATUS loginStatus;
    this->GetPropStatus(loginStatus);
    if (loginStatus == Account::LOGGED_IN)
    {
      printf("Login complete.\n");
      loggedIn = true;
    }
    else if (loginStatus == Account::LOGGED_OUT)
    {
      Account::LOGOUTREASON whyLogout;
      this->GetPropLogoutreason(whyLogout);
      printf("%s\n", (const char*)tostring(whyLogout));

      loggedOut = true;
      printf("Logout complete.\n");
    };
  };
};

Note that there are no separate events for different account properties. This callback will fire for all property changes. loggedIn and loggedOut are declared in global scope so that we can quickly access them in our main loop, without requesting the property values from wrapper cache each iteration.

Unfortunately, overriding our own Account class is not quite enough. The reason is that the wrapper objects are normally created somewhat spontaneously. For example, in case of an incoming chat message, a corresponding object is created automatically. If we had our own class for chat messages, how would Skype know which class to instantiate? For this, the skype class has a set of overridable newXXX(int oid) methods. In this case, we override the newAccount method so that it returns MyAccount instead of Account.

class MySkype : public Skype
{
public:
  Account* newAccount(int oid) { return new MyAccount(oid, this);}
};

After all this, our "main loop" looks simple:

MyAccount::Ref account;
if (skype->GetAccount(accountName, account))
{
  printf("Logging in..\n");
  account->LoginWithPassword(accountPsw, false, false);
  while ( (!account->loggedIn) && (!account->loggedOut) ) { Delay(1); }; 

  if (account->loggedIn)
  {
    printf("Press ENTER to log out..\n");
    int ch = getchar();
  };

  printf("Logging out..\n");
  account->Logout(false);
  while (!account->loggedOut) { Delay(1); }; // Loop until LoggedOut
}
else
{
  printf("Account does not exist\n");
};

All we need to do now is the clean-up sequence:

    skype->stop();
    delete skype;

Full code of this example

/****************************************************************************

Getting Started With SkypeKit. Tutorial Application, Step 1.

In this exercise, we'll examine a program that
 1. Takes a username and password as command-line arguments
 2. Initiates login with that username
 3. Waits until the login process is complete
 4. Waits for user to press Enter and initiates logout
 5. Waits until logout is complete
 6. Cleans up and exits
**/

#include "skype-embedded_2.h"
#include "keypair.h"

#ifdef _WIN32
#include "Windows.h"
#else
#include <unistd.h>
#endif

using namespace Sid;

// Parameters for the transport.
SEString  inetAddr  = "127.0.0.1";
uint      portNum   = 8963;

void Delay(int Sec)
{
  #ifdef _WIN32
    Sleep(Sec * 1000);
  #else
    sleep(Sec);
  #endif
};


//----------------------------------------------------------------------------
// Interface section

class MyAccount : public Account
{
public:
  // The purpose of these typedef's is to enable us to delclare reference variables as:
  // MyAccount::Ref someAccount
  // MyAccount::Refs someAccountList
  typedef DRef<MyAccount, Account> Ref;
  typedef DRefs<MyAccount, Account> Refs;

  // Simple globals for the main loop. We set those two bools from within
  // OnChange callback, checking for Account status property changes.
  bool loggedIn;
  bool loggedOut;

  MyAccount(unsigned int oid, SERootObject* root);
  void OnChange(int prop);
};

class MySkype : public Skype
{
public:
  // Every time an account object is created, we will return instance of MyAccount.
  // This basically "registers" our new MyAccount class as the new Account class.
  Account* newAccount(int oid) { return new MyAccount(oid, this);}
};

//----------------------------------------------------------------------------
// Implementation section


MyAccount::MyAccount(unsigned int oid, SERootObject* root) : Account(oid, root) 
{
  loggedIn = false;
  loggedOut = false;
};

// There are no separate events for different account properties -
// this callback will fire for all property changes.

void MyAccount::OnChange(int prop)
{
  // Here we are swicthing loggedIn and loggedOut bools to inform our 
  // "main loop" of the account status changes.

  if (prop == Account::P_STATUS)
  {
    // OnChange callback does not directly give you the new value
    // for the property that changed. You will need to explicitly
    // retrieve the new value.
    Account::STATUS loginStatus;
    this->GetPropStatus(loginStatus);
    if (loginStatus == Account::LOGGED_IN)
    {
      printf("Login complete.\n");
      loggedIn = true;
    }
    else if (loginStatus == Account::LOGGED_OUT)
    {
      // Account can proceed to LOGGED_OUT not only on because of intentional logout.
      // This state is also reached by failure conditions, such as invalid password, etc.
      // For this reason, it makes sense to also check the value of Account::P_LOGOUTREASON
      // property.
      Account::LOGOUTREASON whyLogout;
      this->GetPropLogoutreason(whyLogout);

      // The tostring function conveniently converts an SkypeKit enum value
      // to an SEString in following format: <Class>::<ENUMERATOR>.<ENUM>
      // For example: "Account::LOGOUTREASON.INCORRECT_PASSWORD"
      printf("%s\n", (const char*)tostring(whyLogout));

      loggedOut = true;
      printf("Logout complete.\n");
    };
  };
};


//----------------------------------------------------------------------------
// Main

MySkype* skype = 0;

int main(int argc, char * argv[])
{
  printf("*****************************************************************\n");
  printf(" SkypeKit Tutorial, Step 1 - Login with skypename and password.\n");
  printf("*****************************************************************\n");

  if (argc < 3)
  {
    printf("usage: tutorial_1 <skypename> <password>\n");
    return 0;
  };

  SEString accountName  = argv[1];
  SEString accountPsw   = argv[2];

  // At this point the SDK runtime should be already up and running.
  printf("Creating skype ..\n");
  skype = new MySkype();

  // Retrieving the keypair and starting up the Skype instance
  getKeyPair ();
  skype->init(keyBuf, inetAddr, portNum, "streamlog.txt");
  skype->start();

  printf("Getting account ..\n");

  MyAccount::Ref account;

  if (skype->GetAccount(accountName, account))
  {
    printf("Logging in..\n");
    account->LoginWithPassword(accountPsw, false, false);

    // Loop until LoggedIn or login failure
    while ( (!account->loggedIn) && (!account->loggedOut) ) { Delay(1); }; 

    if (account->loggedIn)
    {
      printf("Press ENTER to log out..\n");
      getchar();
    };

    printf("Logging out..\n");
    account->Logout(false);

    while (!account->loggedOut) { Delay(1); }; // Loop until LoggedOut
  }
  else
  {
    printf("Account does not exist\n");
  };

  printf("Cleaning up.\n");
  skype->stop();
  delete skype;
  printf("Done.\n");
  return 0;
};
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

(c) Skype Technologies S.A. Confidential/Proprietary

Last updated: Fri Jan 27 2012