воскресенье, 21 декабря 2014 г.

C#: получить снимки с цифровой камеры

Понадобилось тут поработать с цифровой камерой: скопировать с неё снимки и запихнуть их в базу данных. Если вторая часть задачи решилась хоть и не изящно, но достаточно просто, то с реализацией первой возникли определенные проблемы.

Дело в том, что потребовалось получать и обрабатывать несколько снимков за раз. Я думал обойтись штатным OpenFileDialog. Ведь цифровая камера, будучи подключенной по USB к компьютеру, притворяется обычной папкой. Но, оказалось, "папка" эта не совсем обычная. В частности, например, при попытке доступа к изображению система создает его копию во временных файлах, и позволяет работать уже с этой копией. Если же выбрать несколько изображений, то OpenFileDialog вернёт вместо путей что-то маловразумительное типа "004\Root\IMG_1111" "004\Root\IMG_1112" "004\Root\IMG_1113".

Поэтому возникла мысль использовать обходной путь. В Windows есть нечто под названием Windows Image Acquisition. Статус этой технологии мне остался до конца неясным - то ли она уже устарела, то ли еще нет - но и в XP, и в 7 эта штука присутствует, хоть и двух разных версий.

Сначала попытался использовать WIA первой версии. Думал, обратная совместимость вывезет. Работаю я на WinXP, там WIA 1.0, что называется, "из коробки". Итак, добавляю в проект ссылку на Microsoft Windows Image Acquisition 1.01 Type Library (Add Reference, вкладка COM), получаю пространство имен WIALib и в нем кучку классов, с которыми уже можно работать:
using WIALib;
...

private void button1_Click(object sender, EventArgs e)
{
  object selectUsingUI = System.Reflection.Missing.Value;
  try
  {
    // создаем класс для работы с WIA
    WiaClass wiamanager = new WiaClass();

    // выводим диалог выбора цифровой камеры
    ItemClass wiaRoot = (ItemClass)wiamanager.Create(ref selectUsingUI);
    if (wiaRoot == null)
      return;

    // выводим диалог выбора изображений, хранящихся в камере
    CollectionClass wiaPics = (CollectionClass)wiaRoot.GetItemsFromUI(WiaFlag.UseCommonUI, WiaIntent.ImageTypeColor);

    // каждое выбранное изображение копируем в каталог с нашей программой
    if (wiaPics != null)
      foreach (object wiaObj in wiaPics)
      {
        ItemClass wiaItem = (ItemClass)Marshal.CreateWrapperOfType(wiaObj, typeof(ItemClass));
        string tmpFileName = Path.GetDirectoryName(Application.ExecutablePath) + "\\" + wiaItem.Name;
        wiaItem.Transfer(tmpFileName, false);
      }
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message);
  }
}
Всё работает чудесно. Но обнаружились два момента. Первое: без дополнительной обработки изображения получаются чудовищного размера (без всякого сжатия), и второе, самое главное: этот способ не сработал в Windows 7. А именно, при запуске своей программы на Windows 7 64bit я получил такое сообщение об ошибке: Сбой при получении производства объектов класса COM для компонента с CLSID {4EC4272E-2E6F-4EEB-91D0-EBC4D58E8DEE} в результате следующей ошибки: 80040154. В общем, похоже, в современных виндах WIA 1.0 нет.

Пришлось обратиться к WIA 2.0. Под Windows XP потребовалось её установить дополнительно. Я брал дистрибутив отсюда: http://vbnet.mvps.org/files/updates/wiaautsdk.zip. Дальше добавляю в проект ссылку на Microsoft Windows Image Acquisition Library v2.0 (всё на той же вкладке COM) и получаю пространство имен WIA. Использовал это примерно так:
using WIA;
...

private void button1_Click(object sender, EventArgs e)
{

  // создаем класс для работы с камерой
  WIA.CommonDialogClass dlg = null;
  try
  {
    dlg = new WIA.CommonDialogClass();
  }
  catch(Exception ex)
  {
    MessageBox.Show("Невозможно подключиться к интерфейсу работы с камерой:\n" + ex.Message, "Ошибка",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
  }
  if (dlg == null)
    return;

  // выбираем камеру
  WIA.DeviceClass d = null;
  try
  {
    d = (WIA.DeviceClass)dlg.ShowSelectDevice(WIA.WiaDeviceType.CameraDeviceType, true, false);
  }
  catch (Exception ex)
  {
    MessageBox.Show("Невозможно подключиться к камере:\n" + ex.Message, "Ошибка",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
  }
  if (d == null)
    return;

  // выбираем изображения
  WIA.Items items = dlg.ShowSelectItems(d, WIA.WiaImageIntent.ColorIntent,
      WIA.WiaImageBias.MinimizeSize, false, true, false);

  if (items != null)
    foreach (WIA.Item item in items)
    {
      WIA.ImageFile img = (WIA.ImageFile)item.Transfer(WIA.FormatID.wiaFormatJPEG);

      // выясняем имя и расширение выбранного изображения
      string
        tmpFileName = "",
        tmpFileExt = "";
      foreach (WIA.Property prop in item.Properties)
      {
        if (prop.Name == "Item Name")
          tmpFileName = prop.get_Value().ToString();
        if (prop.Name == "Filename extension")
          tmpFileExt = prop.get_Value().ToString();
      }
      if (tmpFileExt == "")
        tmpFileExt = img.FileExtension;
      if (Path.HasExtension(tmpFileName))
        tmpFileExt = "";

      // копируем выбранное изображение в каталог с нашей программой
      string
        tmpFullName = Path.GetDirectoryName(Application.ExecutablePath) + "\\"
            + tmpFileName + ((tmpFileExt != "") ? "." : "") + tmpFileExt;

      img.SaveFile(tmpFullName);
    }
}
Код получился немного длинее, но это из-за проверок. Основной плюс - помимо того, что эта конструкция сработала и в Windows 7 - возможность сохранять изображения в формате JPEG.

Кстати, во втором примере в свойствах изображения есть много интересного. Я себе на память сделал скриншот:

воскресенье, 14 декабря 2014 г.

Outlook: преобразовать OST в PST

"Семь раз отмерь..." - эта пословица, как и инструкция по технике безопасности, писана кровью. Давеча переносил в MS Outlook 2007 учетную запись Exchange с одного сервера на другой. Ну как переносил - одну запись грохнул, вторую создал. Предварительно сохранив - как я наивно думал - банк сообщений outlook.ost удаляемой учетки в укромное место. Потом подменил этот файл в новой учетной записи - и получил ошибку, что с этим файлом новая учетная запись работать отказывается. И только наступив на грабли полез разбираться.

Оказывается, так делать нельзя. OST - это Offline Storage Table, в некотором роде "зеркало" банка сообщений конкретной учетной записи сервера Exchange. Соответственно, другая учетная запись с этим зеркалом работать не будет. Поэтому перед удалением старой учетной записи следует переместить нужные письма и контакты из неё в какой-нибудь файл PST (Personal Storage Table).

Если же на руках только OST, и восстановить старую учетную запись, хотя бы временно, не представляется возможным, то придется воспользоваться какой-нибудь сторонней утилитой. Мне помогла вот эта: Coolutils OST to PST Converter. Она, правда, shareware, но один раз отработала в полном объеме.

среда, 10 декабря 2014 г.

C#: Превышена максимальная длина запроса

Помогла вот эта статья: Загрузка файла большого размера.

Вкратце - в web.config в узел <system.web> надо добавить опцию:
<httpRuntime maxRequestLength="16384" />

вторник, 9 декабря 2014 г.

КриптоПро

Постоянно при работе с КриптоПро возникают проблемы. Должен сказать, что не по его вине, просто банальное непонимание с моей стороны, как всё функционирует.

Вот например, давеча не увидело оно воткнутый в usb ключевой носитель eToken. Виноват в этом оказался, как обычно, я. На всякий случай оставлю себе заметочку, что нужно было делать вместо того, чтобы ужасаться и панически кричать "всё пропало, ничего не работает".

Для работы с eToken Pro Java необходимо удостовериться что:
1. eToken распознаётся в драйвере etoken pki client.
2. В КриптоПро CSP установлены "Все считыватели смарт-карт" (КриптоПро CSP - Оборудование - Настроить считыватели).
3. В КриптоПро CSP установлены ключевые носители eToken Java 1.0\1.0b
4. На ключе eToken присутствует закрытый ключ.

Для диагностики можно создать закрытый ключ на eToken.
"c:\Program Files\Crypto Pro\CSP\csptest.exe" -keyset -newkeyset -cont testcont
После проверить через КриптоПро CSP (сервис - протестировать контейнер).