hack.connect
 
[ru.scene.community]
HackConnect.E-zine issue #2
// Использование BITS
Введение.
BITS — Background Intelligent Transfer Service — это системный сервис в Win2k, WinXP, Win2k3, используемый для проведения транзакций файлов по сети. Изначально создавался специально для Windows Update, но позже MS решила документировать его подробно для общего пользования.
Вообще говоря, этот ход MS не понятен — создавать сервис такого рода и разрешать его использование произвольными программами, так как, по сути, это был (а на многих системах — есть) тривиальный способ произвести обмен данными по сети через доверенный процесс [svchost.exe].
Моя статья направлена на то, чтобы показать вам, как работать с этим сервисом, добавлять, удалять и просматривать задания.
Задания определяются как некая абстракция, базовый юнит службы BITS, содержащий в себе набор свойств файлов, предназначенных для передачи, и некоторые внутренние свойства задания, например, приоритет и прочие. Приоритет может быть низким, нормальным, высоким — при фоновой загрузке, и обычный, когда приложение использует для своих нужд все имеющиеся ресурсы сети. При фоновом же режиме, другие приложения могут остановить передачу, либо отобрать часть, а то и весь канал.
BITS является Unicode компонентом, таким как, например, GDI+, соответственно, все строки свойств должны быть в этом формате. Собственно, по этой причине сервис не доступен в Win9x и в NT 4.0 (в 9x, для начала, вообще нет служб в том виде, в котором они в NT системах). Поэтому все приложения с использованием BITS должны быть скомпилированы в формате Unicode (смотрим директивы препроцессора).
Итого, BITS — сервис, предназначенный для обмена данными по протоколу HTTP[S] в фоновом или обычном режиме.

Объекты.
Как и многие базовые компоненты COM, BITS создает главный объект, через который доступны остальные. Я перечислю здесь основные.
IBackgroundCopyManagerОсновной объект, создает и перечисляет задания.
IEnumBackgroundCopyJobsОбъект перечисления, используемый для получения свойств имеющихся заданий.
IBackgroundCopyJob
IBackgroundCopyJob2
IBackgroundCopyJob3
Объект задания. Почему три — каждой версии свой объект. Через такие объекты добавляются и удаляются файлы/списки файлов, изменяются свойства.
IEnumBackgroundCopyFilesЭтот объект содержит в себе список файлов для передачи.
IBackgroundCopyFile
IBackgroundCopyFile2
Объект файла.
IBackgroundCopyErrorОбъект ошибок, используемый для получения описаний возникающих ошибок соответственно.
IBackgroundCopyCallbackОбъект событий, должен быть определен и зарегистрирован в задании, если необходима большая интерактивность приложения.

Практика.
Для простых приложений/функций, производящих скачку/закачку 1 файла, нам понадобятся всего-лишь три объекта из вышеперечисленных. Напишем функцию, принимающую 2 аргумента - ссылку на удаленный файл и путь к локальному.
bool BitsDownload(LPCWSTR RemoteFile, LPCWSTR LocalFile)
{
  IBackgroundCopyManager* pCopyManager = NULL;  
  HRESULT hr;
  bool State = FALSE;

  // Инициализируем COM и указываем необходимую апартаментную модель
  hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  if (SUCCEEDED(hr))
  {
    hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL, CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager), (void**) &pCopyManager); // Создали объект для загрузки
    if (SUCCEEDED(hr))
    {
      GUID JobId;
      IBackgroundCopyJob* pJob = NULL;

      // Чтобы произвести закачку (не советую - много гемора), замените BG_JOB_TYPE_DOWNLOAD  
      // BG_JOB_TYPE_UPLOAD или BG_JOB_TYPE_UPLOAD_REPLY (второе для того чтобы битс получил еще ответ сервера после загрузки).
      hr = pCopyManager->CreateJob(L"DLJob", BG_JOB_TYPE_DOWNLOAD, &JobId, &pJob);
      if (SUCCEEDED(hr))
      {
        hr = pJob->AddFile(RemoteFile, LocalFile); // Добавляем наш 1-единственный файл для закачки
        if (SUCCEEDED(hr))
        {
          hr = pJob->Resume(); // Запускаем работу (в мсдн названо - перезапускаем)
          if (SUCCEEDED(hr))
          {
            BG_JOB_STATE pJobState; // Объект состояния
            while (true)
            {
              hr = pJob->GetState(&pJobState);
              if (SUCCEEDED(hr) && (pJobState != BG_JOB_STATE_TRANSFERRED)) // Если не передано - ждем
              {
                printf("."); // Точечка)))
                Sleep(100);
              }
              else 
                break; // Выходим из цикла по окончании соответственно
            }
            State = TRUE;
          }
          hr = pJob->Complete(); // Завершаем работу
          if(hr != S_OK)
          {
            printf("\r\nJob finishing error! But file maybe transfered ok.");
            State = FALSE;
          }
        }
        else
        {
          printf("\r\nFile adding to job error!");
          State = FALSE;
        }
        pJob->Release(); 
        pJob = NULL; // Жестоко убиваем указатель))
      }
      else
      {
        printf("\r\nJob creating error!");
        State = FALSE;
      }
    }
  }

  if (pCopyManager)
  {
    pCopyManager->Release();
    pCopyManager = NULL; // Аналогичное зверство
  }
  CoUninitialize();

  return State;
}
Внутри вызова CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) для потока нашего приложения было создано новое (скрытое) окно с именем «OleMainThreadWndName», класс которого «OleMainThreadWndClass». Если поток вызывает CoInitialize(NULL) или CoInitializeEx(NULL, COINIT_APARTMENTTHREADED), то создается так называемый Single-Threaded Apartment (однопоточный апартамент), или сокращенно STA. Если же поток вызывает CoInitializeEx(NULL, COINIT_MULTITHREADED), то он (поток) помещается в так называемый Multi-Threaded Apartment (многопоточный апартамент), или сокращенно MTA. В одном процессе может существовать сколько угодно STA и только один MTA. Для каждого апартамента (будь то MTA или STA COM создает скрытое окно) это окно (вернее его очередь сообщений) нужно для синхронизации вызовов производимых к апартаменту. Один поток физически не может вызвать другой. Выполнение любого кода в потоке (в том числе и вызовы функций) производится именно в этом потоке, независимо от того, в какой области памяти процесса код располагается, и кем и как он был загружен. Совершенно невозможно из одного потока вызвать функцию из другого потока. Апартаментная модель предполагает, что вызовы к объектам (то есть к их коду) должны производиться из одного из потоков, входящих в этот апартамент. Теперь нам необходимо проверить, запущен ли BITS, и, если нет - запустить его самим. Функция проверки:
bool CheckBITSStatus(SC_HANDLE hManager)
{
  SC_HANDLE hService = OpenService(hManager,L"BITS",SERVICE_QUERY_STATUS); // Открываем хэндл сервиса.
  LPSERVICE_STATUS rServStatus = ( LPSERVICE_STATUS ) GlobalAlloc( GPTR, sizeof( SERVICE_STATUS ) );
  bool Ret = QueryServiceStatus(hService,rServStatus); // Запрашиваем о текущем состоянии.
  if(Ret!=NULL)
  {
    if(rServStatus->dwCurrentState == SERVICE_RUNNING) // Запущен
    {
      CloseServiceHandle(hService);
      return TRUE;
    }
    else if(rServStatus->dwCurrentState == SERVICE_START_PENDING) // Запускается
    {
      printf("BITS is starting now. Waiting...\r\n");
      while(rServStatus->dwCurrentState != SERVICE_RUNNING) // Ждем
      {
        Sleep(250);
        Ret = QueryServiceStatus(hService,rServStatus);
      }
      CloseServiceHandle(hService);
      return TRUE;
    }
    else
    {
      CloseServiceHandle(hService); // Не запущен
      return FALSE;
    }
  }
  else
  {
    printf("Warning: Cannot get information about service status\r\n"); // Не смогли получить инфу - очень странно
    return TRUE;
  }
}
Отлично, теперь мы знаем запущен сервис или нет. Осталось написать основную функцию, соединяющую эти две и запускающую, в случае чего, BITS.
wmain(int argc, wchar_t *argv[]) // У нас ведь юникод милостью МС))
{
  printf("/*------- BITS Downloader Example by DeaDMonaX ----------\r\n| dm.xndcrew.org | Espessially for HackConnect.Ezine #2 |\r\n-------------------------------------------------------*/\r\n");
  if(argc < 3)
  {
    if(argc == 2 && wcscmp(argv[1],(wchar_t*)"show")==NULL)
    {
      PrintJobs();
      return 0;
    }
    else
    {
      printf("usage:\r\nDownload: bitsdl  \r\nShow and describe jobs: bitsdl show\r\n"); // Лень писать парсилку чтобы имя автоматом делал анализируя урл
      return -1;
    }
  }
  else
  {
    if(argc > 3)
      printf("\r\nToo many command-line arguments. Third and other after will be ignored.\r\n");

    SC_HANDLE hManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
    if(hManager==NULL)
    {
      printf("Warning: opening SCManager with full access failed.\r\n");
      hManager = OpenSCManager(NULL,NULL,SC_MANAGER_ENUMERATE_SERVICE);
      if(hManager!=NULL)
      {
        bool Ret = CheckBITSStatus(hManager);
        if(Ret == FALSE)
        {
          printf("BITS is stopped and cannot be started. Exiting...");
          return -1;
        }
      }
      else
      {
        printf("Mystic: opening SCManager with enumerate services access also failed.\r\nTrying download skipping check.\r\n");
      }
    }
    else
    {
      if(CheckBITSStatus(hManager) == FALSE)
      {
        printf("Warning: BITS is not started. Trying to run it.\r\n");
        SC_HANDLE hService = OpenService(hManager, L"BITS", SERVICE_START);
        if(StartService(hService,NULL,NULL))
        {
          printf("OK: BITS is running now\r\n");
          CloseServiceHandle(hService);
        }
        else
        {
          printf("Mystic: cannot start BITS with full access!\r\n");
          return -1;
        }
      }
      else
        printf("OK: BITS is running\r\n");
    }

    if(hManager!=NULL)
    {
      CloseServiceHandle(hManager);
    }
    Sleep(1000);

    printf("\r\nLocal file: %ls\r\nRemote file: %ls\r\n\r\nDownloading",argv[2],argv[1]);
    
    if(BitsDownload(argv[1],argv[2]))
      printf(" done!");
  }
  return 0;
}
Теперь напишем, которая перечисляет все задания и файлы в них.
void PrintJobs()
{
  IBackgroundCopyManager* pCopyManager = NULL; 
  IEnumBackgroundCopyJobs* pJobs = NULL;
  IBackgroundCopyJob* pJob = NULL;
  IEnumBackgroundCopyFiles* pFiles = NULL;
  IBackgroundCopyFile* pFile = NULL;
  BG_JOB_PRIORITY pPriority;
  LPWSTR pRemoteFileName;
  LPWSTR pLocalFileName;
  LPWSTR pJobName;
  ULONG cJobCount = 0;
  ULONG cFileCount = 0;
  ULONG idx = 0;
  HRESULT hr;

  hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // Та же тема с COM'ом
  if (SUCCEEDED(hr))
  {
    hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL, CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager), (void**) &pCopyManager); // Создали объект для загрузки
    if (SUCCEEDED(hr))
    {
      hr = pCopyManager->EnumJobs(BG_JOB_ENUM_ALL_USERS, &pJobs); // Чтобы перечислить задания только текущего пользователя, замените BG_JOB_ENUM_ALL_USERS нулем.
      if (SUCCEEDED(hr))
      {
        pJobs->GetCount(&cJobCount); // Получаем количество текущих работ. Не знаю почему, но у меня всегда пишет 2.
        
        printf("\r\nJobs total: %d\r\n",cJobCount);
        for (idx=0; idxNext(1, &pJob, NULL); // Перечисляем работы
          if (S_OK == hr)
          {
            printf("\r\nJob #%d\r\n",idx+1);
            pJob->GetPriority(&pPriority); // Приоритет работы, название
            pJob->GetDisplayName(&pJobName);
            printf("Name: %ls\r\nPriority: %ls\r\n",pJobName,GetStrPrVal(pPriority));

            hr = pJob->EnumFiles(&pFiles); // Перечисление файлов работы
            if (SUCCEEDED(hr))
            {
              pFiles->GetCount(&cFileCount); // Сколько там их?)
              printf("Total files: %d\r\n",cFileCount);
              printf("-------------------------");
              for (idx=0; idxNext(1, &pFile, NULL);
                printf("\r\nFile #%d\r\n",idx+1);
                if (S_OK == hr)
                {
                  hr = pFile->GetRemoteName(&pRemoteFileName); // кристально ясно
                  if(SUCCEEDED(hr))
                  {
                    printf("Remote Name: %ls\r\n",pRemoteFileName);
                  }
                  hr = pFile->GetLocalName(&pLocalFileName);
                  if(SUCCEEDED(hr))
                  {
                    printf("Local Name: %ls\r\n",pLocalFileName);
                  }
                }
                else
                {
                  printf("Warning: Could not retrieve information about %dd file  in job\r\n",idx);
                  break;
                }
              }

              pFiles->Release();
              pFiles = NULL;
            }
            printf("-------------------------\r\n");
            pJob->Release();
            pJob = NULL;
          }
          else
          {
            printf("No jobs.\r\n");
            break;
          }
        }

        pJobs->Release();
        pJobs = NULL;
      }
    }
  }

}
В тексте этой функции упоминалась неизвестная вам функция GetStrPrVal. Эта функция получает дескриптор приоритета задания и возвращает текстовое значение, ему соответствующее.
wchar_t *GetStrPrVal(BG_JOB_PRIORITY pPriority)
{
  if(BG_JOB_PRIORITY_FOREGROUND==pPriority)
    return &L"Foreground";
  else if(BG_JOB_PRIORITY_HIGH==pPriority)
    return &L"High";
  else if(BG_JOB_PRIORITY_NORMAL==pPriority)
    return &L"Normal";
  else
    return &L"Low";
}
Voila! Вот и все что я хотел рассказать вам о технологии BITS. Ищите в стаффе полные сорцы сего чуда. // dm
/* ----------------------------------------------------[contents]----------------------------------------------------- */