Отправка SMS из торгового советника через Skype

Alexey Koshevoy | 20 июня, 2007

Введение

Skype – телекоммуникационная программа, которая, кроме обычных чатов, позволяет вести телефонные разговоры по Интернет. Одним из наиболее значимых преимуществ Skype перед другими программами подобного плана является шлюз на реальных операторов мобильной связи. Соответственно можно звонить с компьютера на реальный мобильный телефон, отсылать SMS и так далее. Так же существует версия Skype для мобильных телефонов, можно экономить на SMS, потому что отправка обычных сообщений внутри программы совершенно бесплатна. Правда, мобильный телефон должен работать под управлением операционной системы. В общем, при желании, можно быть полностью мобильным, чем в последнее время и пользуется все большее количество людей.


Кому и зачем это надо?

Трейдер не всегда может и должен сидеть за торговым терминалом, наблюдать за тем, как идет торговля. Однако, было бы неплохо иметь возможность иногда получать сигналы о действиях эксперта или о состоянии рынка в определенные моменты времени. Почему бы не получать данную информацию на мобильный телефон?


Какую информацию было бы полезно получать?

Информацию, которую можно получать посредством сообщений на телефон можно разделить на два класса:

  1. текущая, которая ни на что не влияет и практически дублируют логфайл;
  2. полезная, которая будет реально полезна для трейдера именно в момент поступления.

Рассмотрим примеры текущей информации:

Полезная информация:


Как это работает в Skype?


Как это сделать из эксперта?

Я нашел два способа и оба используют DLL:

  1. Предварительно подготавливаем макрофайл, то есть файл, при запуске которого будет перехвачено управление клавиатурой и мышью компьютера. Таким образом, с помощью последовательности действий нам нужно активировать Skype, найти в меню пункт отправки SMS, затем в появившемся окне набрать номер абонента и вставить из буфера обмена текстовое сообщение, которое заранее было туда вставлено из эксперта. Потренироваться и отточить действия можно заранее. Таким образом, мы подготовили файл, который является ассоциированным документом и может быть запущен как обычное приложение. Кстати, приложений делающих запись и воспроизведение макрофайлов существует большое множество, поэтому в статье конкретные экземпляры рассматриваться не будут.

    Дальше нам нужно разработать DLL, работа которой будет заключаться в двух небольших действиях. Первое, – это поместить переданный из эксперта текст в буфер обмена и, второе, – запустить заранее заданный макрофайл. Если все заранее хорошо настроено и все окна и кнопки буду появляться на своих местах, то проблем быть не должно, сообщение отправится.

    Однако, это жутковатый способ. Интуиция мне подсказывала, что если мозги начинают выдумывать что-то подобное, значит надо искать более элегантное решение или отказаться от этой идеи вообще. И тут проскользнула мысль – а может быть Skype имеет API? И точно, на сайте есть и голый API и ActiveX интерфейс. Супер! Рассмотрим второй способ работы со Skype из эксперта.

  2. Смысл тот же. Из эксперта в DLL передается номер абонента и текст, который должен быть отправлен, а DLL уже выполняет отправку посредством COM объекта Skype.


Реализация второго способа.

Начнем с DLL. Основную часть работы будет занимать подготовка DLL для взаимодействия с экспертом. Во-первых, напишем библиотеку, которая будет работать при обращении к ней из нескольких экспертов. К сожалению, будет недостаточно просто написать функцию и вызывать ее. Мы используем ActiveX, поэтому желательно создать для него отдельный поток и проводить всю работу в нем. Стандартное средство распараллеливания работы функций Mutex не поможет. Будут наблюдаться креши, причем не отслеживаемые. Реализуем последовательность обращений через систему пользовательских сообщений.


Исходный текст библиотеки DLL

#include "stdafx.h"
 
#pragma once
// Allow use of features specific to Windows XP or later. 
#ifndef WINVER
// Change this to the appropriate value to target other versions of Windows.
#define WINVER 0x0501      
#endif
// Exclude rarely-used stuff from Windows headers 
#define WIN32_LEAN_AND_MEAN
 
// Подключаем библиотеку Skype4COM.dll, предварительно выкачанную 
// с сайта разработчиков для Skype – http://developers.skype.com/
#import "Skype4COM.dll" rename("CreateEvent","CreatePluginEvent"), 
                        rename("SendMessage","SendChatMessage")
 
 
#define MT4_EXPFUNC __declspec(dllexport)
 
// Объявляем коды сообщений для наших функций.
#define WM_PROC_SENDSKYPESMS WM_USER + 01
#define WM_PROC_SENDSKYPEMESSAGE WM_USER + 02
 
// Переменные для потока, которые мы будем использовать для 
// отправки сообщений
HANDLE hUserThread;
DWORD ThreadId;
 
// Структуры для хранения параметров функций
// SendSkypeSMS
struct fcpSendSkypeSMS
  {
    int ExitCode;
    char * UserNum;
    char * Message;
  };
 
// SendSkypeMessage
struct fcpSendSkypeMessage
  {
    int ExitCode;
    char * UserName;
    char * Message;
  };
//+------------------------------------------------------------------+
//| Thread function                                                  |
//+------------------------------------------------------------------+
DWORD WINAPI ThreadProc(LPVOID lpParameter)
  {
    MSG msg;
    HANDLE hEvent;
  
    while(true)
      {
        if(PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0))
            break;
      };
    // Инициализируем COM
    CoInitialize(NULL);
    while(GetMessage(&msg, 0, 0, 0))
      {
        if(msg.message == WM_QUIT)
          {
            break;
          }
        // Обработчик сообщения WM_PROC_SENDSKYPESMS
        else 
            if(msg.message == WM_PROC_SENDSKYPESMS)
              {
                fcpSendSkypeSMS* fcp = (fcpSendSkypeSMS*)msg.wParam;
                hEvent = (HANDLE)msg.lParam;
                try
                  {
                    // Инициализируем Skype 
                    SKYPE4COMLib::ISkypePtr pSkype(__uuidof(SKYPE4COMLib::Skype));
                    // Соединяемся со Skype. 6 – это версия протокола
                    HRESULT hr=pSkype->Attach(6,VARIANT_TRUE);
                    // Если все нормально, то начинаем отправку сообщения
                    if(!FAILED(hr))
                      {    
                        try
                          {
                            fcp->ExitCode = 1;
                            // Пытаемся отослать SMS
                            pSkype->SendSms(fcp->UserNum,fcp->Message,"");
                          }
                        catch(...)
                          {
                            fcp->ExitCode=-1;
                          }
                      }
                    // Деинициализируем Skype
                    pSkype = NULL;
                  }
                catch(...)
                  {
                    //Ошибку обрабатываем здесь
                  }
                // Отпускаем событие
                SetEvent(hEvent);
              }
            // Обработчик сообщения WM_PROC_SENDSKYPEMESSAGE
            else 
                if(msg.message == WM_PROC_SENDSKYPEMESSAGE)
                  {
                    fcpSendSkypeMessage* fcp = 
                                   (fcpSendSkypeMessage*)msg.wParam;
                    hEvent = (HANDLE)msg.lParam;
        
                    try
                      {
                        // Инициализируем Skype 
                        SKYPE4COMLib::ISkypePtr pSkype(__uuidof
                                              (SKYPE4COMLib::Skype));
                        // Соединяемся со Skype. 6 – это версия протокола
                        HRESULT hr=pSkype->Attach(6,VARIANT_TRUE);
                        // Если все нормально, то начинаем отправку сообщения
                        if(!FAILED(hr))
                          {
                            try
                              {
                                fcp->ExitCode = 1;
                                // Пытаемся отослать сообщение
                                pSkype->SendChatMessage(fcp->UserName,
                                                        fcp->Message);
                              }
                            catch(...)
                              {
                                fcp->ExitCode=-1;
                                MessageBeep(0);
                              }
                          }
                        // Деинициализируем Skype
                        pSkype = NULL;
                      }
                    catch(...)
                      {
                        //Ошибку обрабатываем здесь
                      }
  
                    // Отпускаем событие
                    SetEvent(hEvent);
                  }
              };
            // Деинициализируем COM
            CoUninitialize();
            return 0;
          }
 
//Инициализация библиотеки.
//+------------------------------------------------------------------+
//| DLL entry                                                        |
//+------------------------------------------------------------------+
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call,
                      LPVOID lpReserved)
  {
    if(ul_reason_for_call == DLL_PROCESS_ATTACH)
      {
        // Создаем поток и привязываем к нему адрес процедуры обработчика
        hUserThread = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &ThreadId);
        if(!hUserThread)
          {
            // Обработка ошибки, если поток не будет создан
          };
      } 
    else 
        if(ul_reason_for_call == DLL_PROCESS_DETACH)
          {
            // Уничтожаем поток при выходе из библиотеки
            CloseHandle(hUserThread);
          }
    return(TRUE);
  }
 
MT4_EXPFUNC bool __stdcall SendSkypeSMS(int &ExCode,char* sUserNum, 
                                        char* sMessage)
  {
    //Объявляем структуру параметров функции
    fcpSendSkypeSMS* fcp;
    //Объявляем событие
    HANDLE hEvent;
    //Результат работы функции по умолчанию false
    bool Result = false;
 
    // Выделяем память под структуру и инициализируем ее
    fcp = new fcpSendSkypeSMS();
    memset(fcp, 0, sizeof(fcpSendSkypeSMS));
 
    // Заполняем структуру
    //По умолчанию код окончания работы функции – ошибка.
    fcp->ExitCode = -1;
    fcp->UserNum = sUserNum;
    fcp->Message = sMessage;
 
    // Создаем событие
    hEvent = CreateEvent(NULL,FALSE,FALSE, NULL);
    // Вызывает событие WM_PROC_SENDSKYPESMS, передаем в процедуру 
    // обработки адрес структуры с данными
    PostThreadMessage(ThreadId, WM_PROC_SENDSKYPESMS, (WPARAM)fcp,
                      (LPARAM)hEvent);
    if(WAIT_OBJECT_0 == WaitForSingleObject(hEvent,INFINITE))
      {
        
        Result = true;
      } 
    else
      {
        // Если была ошибка при отработке сообщения, то функция 
        // вернет значение false
        return(Result);
      };
    // Присваиваем переменной код отработки функции
    ExCode = fcp->ExitCode;
    if(ExCode == -1) 
        Result = false;
    // Освобождаем память и переменные и выходим
    delete fcp;
    CloseHandle(hEvent);
    return(Result);
  }
 
MT4_EXPFUNC bool __stdcall SendSkypeMessage(int &ExCode,char* sUserName, 
                                            char* sMessage)
  {
    //Объявляем структуру параметров функции
    fcpSendSkypeMessage* fcp;
    //Объявляем событие
    HANDLE hEvent;
    //Результат работы функции по умолчанию false
    bool Result = false;
 
    // Выделяем память под структуру и инициализируем ее
    fcp = new fcpSendSkypeMessage();
    memset(fcp, 0, sizeof(fcpSendSkypeMessage));
 
    // Заполняем структуру
    //По умолчанию код окончания работы функции – ошибка.
    fcp->ExitCode = -1;
    fcp->UserName = sUserName;
    fcp->Message = sMessage;
 
    // Создаем событие
    hEvent = CreateEvent(NULL, FALSE,FALSE, NULL);
    // Вызывает событие WM_PROC_SENDSKYPESMS, передаем в процедуру 
    // обработки адрес структуры с данными
    PostThreadMessage(ThreadId, WM_PROC_SENDSKYPEMESSAGE, (WPARAM)fcp,
                      (LPARAM)hEvent);
    if(WAIT_OBJECT_0 == WaitForSingleObject(hEvent, INFINITE))
      {
        Result = true;
      } 
    else
      {
        // Если была ошибка при отработке сообщения, то функция 
        // вернет значение false
        return(Result);
      };
    // Присваиваем переменной код отработки функции
    ExCode = fcp->ExitCode;
    if(ExCode == -1) 
        Result = false;
    // Освобождаем память и переменные и выходим
    delete fcp;
    CloseHandle(hEvent);
    return(Result);
  }


DEF файл

LIBRARY SkypeLib
 
EXPORTS SendSkypeSMS
        SendSkypeMessage


Торговый советник для тестирования

//+------------------------------------------------------------------+
//|                                              SkypeTestExpert.mq4 |
//|                               Copyright © 2007, Alexey Koshevoy. |
//+------------------------------------------------------------------+
// Import functions
#import "SkypeLib.dll"
   bool SendSkypeSMS(int &ExCode[], string Num,string Message);
   bool SendSkypeMessage(int &ExCode[], string User, string Message);
#import
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int init()
  {
   int ExCode[1];
   Alert("Отправляем сообщение...");
   Alert(SendSkypeMessage(ExCode, "skype.account.name", "Skype message test"));
   if(ExCode[0] == -1)
       Alert("Ошибка отправки сообщения");
   else 
       Alert("Сообщение отправлено");
   Alert("Отправляем SMS сообщение...");
   Alert(SendSkypeSMS(ExCode, "+1234567890", "Skype sms test"));
   if(ExCode[0] == -1)
       Alert("Ошибка отправки SMS сообщения");
   else
       Alert("SMS сообщение отправлено");
   return(0);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int start()
  {
   return(0);
  }
//+------------------------------------------------------------------+

Эксперт прост, его главная цель – отправить SMS сообщение и обычное сообщение через написанную нами библиотеку. Он проделывает все действия в функции инициализации, поэтому тестировать можно и в выходные.


Установка Skype

Скачать можно на http://www.skype.com/. Желательно установить последнюю версию программы, потому что в предыдущих версиях COM интерфейс не поддерживается, есть только API. Но, к сожалению API не поддерживает отправку SMS сообщений.

Итак, Skype установлен. Теперь нам нужно выкачать COM библиотеку. Находится она на сайте для разработчиков https://developer.skype.com/ в разделе Downloads. Проверяем наличие финансов на счету, которые будут использоваться для отправки SMS. Если таковых нет, то счет можно пополнить через Интернет из программы. При отсутствии денег на счету отправлять SMS не получится, однако, обычные сообщения без проблем.

Для того чтобы терминал имел доступ к Skype API, нужно его зарегистрировать. Проверить наличие разрешения для работы с API можно через меню Tools->Options->Privacy->Manage other programs access to Skype. Должно выглядеть примерно так:

Регистрируется терминал при первой попытке воспользоваться библиотекой. Вручную сделать этого нельзя. Поэтому при первой установке библиотеки следует дождаться отправки сообщения, чтобы подтвердить разрешение на пользование Skype API. Skype выдаст следующий диалог:

После подтверждения система начинает работать в автоматическом режиме.


Установка библиотеки SkypeLib

Для того чтобы установить библиотеку SkypeLib.dll, нужно скопировать ее в папку experts/libraries в каталоге терминала. Также туда нужно скопировать библиотеку Skype4COM.dll. Теперь нужно настроить терминал для работы с DLL. Для этого при установке эксперта в разделе Safety отмечаем галочкой пункт Allow DLL imports, как это показано ниже:

Теперь можно пользоваться библиотекой.


Некоторые важные моменты

После небольшого опыта тестирования и внедрения были замечены небольшие тонкости. Нужно учитывать, что если у вас на счету достаточно денег и вы отправили SMS сообщение на несуществующий номер, то ошибки не будет, функция отработает успешно, а статус сообщения будет установлен в состояние “sending...”. Поэтому нужно четко настраивать входные параметры функций. Следить нужно и за тем, чтобы версия Skype была не ниже 3.0.

Очень редко бывает, что COM объект не инициализирован, и сообщения отправляться не будут. Помогает только переустановка Skype. Интерфейс внешнего взаимодействия относительно новый, не без ошибок, поэтому случается такой неприятный момент. На моей памяти было только два таких случая. Будем надеяться, что в более поздние версии будут работать стабильнее.

Так же следует отметить, что для работы SkypeLib.dll могут потребоваться дополнительные библиотеки. Особенно остро вопрос стоит после выхода первого service pack к Visual Studio 2005. Лучшим выходом будет создание setup файла. Туда автоматически будут включены все необходимые библиотеки. Так же можно включить и файл Skype4COM.dll.


Прикрепленные к статье файлы


Достоинства и недостатки.

Недостатки использования Skype SMS

Достоинства данного метода:


Выводы

Мы научились отправлять SMS сообщения и обычные сообщения через Skype. Таким образом, мы получили, может быть, не самый удобный, но незаменимый интерфейс для оповещения о текущих событиях в терминале. Что дальше? А ведь в Skype можно не только отправлять сообщения, но и получать...