MCP

суббота, 5 октября 2013 г.

Тема из Windows 8 Release Preview в Windows 8.1

Данный пост является продолжением прошлогоднего: Смена темы в Windows 8 на тему из Release Preview. Как результат, контекст обсуждения и общие моменты по установке смотрите в прошлом посте.

Итак, совсем скоро Microsoft выпустит Windows 8.1, в которой будет 100500 улучшений, всё будет быстрее, лучше и красивее (по секрету скажу, что ничего стоящего не замечено).

Но Microsoft не был бы Microsoft'ом, если бы в новой версии что-нибудь бы не поломал. В частности, замечательная тема из Windows 8 RP, теперь выглядит гораздо хуже. Вот вам картинка для понимания процесса:



Если у кого плохо с монитором или внимательностью, то проблема в том, что вокруг заголовка появился прямоугольник слегка другого оттенка чем основной заголовок. Что не фатально, но раздражает.

После некоторого времени, потраченного на иследования я выяснил, что тут сложились два фактора:

  • Изменился рендеринг текста, стал ещё более простым (те. убрано свечение текста там, где оно не включено специально)
  • Тема от Windows 8 RP имеет в качестве цвета заголовка практически (но не полностью!) прозрачную рамку.
Т.е. получается, что из-за того что текст заголовка окна рисуется на фоне заданного в настройках персонализации цвета, а сам заголовок тем же цветом, но из-за неполной прозрачности чуть-чуть другого оттенка. И получается такая фигня.

Собственно, проблема есть, пишу решения:
Решение №1 использовать программу  Aero Glass for Windows 8, которая сама добавит свечение вокруг букв и решит проблему. Единственная проблема: заставить программу работать по-человечески, та ещё задача. 

Решение №2 поправить тему, заменив почти прозрачность на полную прозрачность, а так как мы сломаем оригинальную тему, то сломать дополнительно проверку подписи для тем.
Вторая проблема решается давно и неплохо (ибо люди любят кастомные темы).

Самый простой способ сломать проверку через UxStyle. Это небольшой сервис, котороый не меняет системные файлы, а проводит изменения в памяти. Как результат, можно подклаывать любую тему с deviantART. Единственная проблема, пока нельзя скачать программу для 8.1, ибо она была выпущена, но из-за какого-то критичного бага убрана. Так что ждём фикса бага и обновления.

Ну а саму тему из Win 8 RP, я уже поправил, и выкладываю здесь, чтобы можно было забрать и пользоваться:

А подробности по установке в прошлом посте.


суббота, 21 сентября 2013 г.

Маленький хинт для программистов

Иногда бывают ситуации, когда вам нужно что-то удалённо сделать с вашим домашним компьютером. Часто что-то стандартное, а под рукой только телефон.

С одной стороны, можно начать писать могучее приложение под телефон, которое будет общаться с компьютером (для надёжности через внешний сервис), а с другой, можно поступить по-программистски: написать самому абсолютно точное и простое решение:

  1. Поднять web-сайт на своём компьютере (IIS или node.js — не важно, что больше нравится)
  2. Выставить его в интернет (Dynamic DNS в помощь)
  3. Ограничить к нему доступ авторизацией или сертификатами
  4. Для стандартных задач сделать url'ы, выполняющие определённые команды (лучше внешние батники, чтобы их лего можно было менять)
  5. Если нужно что-то сложное, то можно уже сделать интерфейс для выбора или ввода команды вручную
  6. Открыть с телефона в браузере полученную ссылку
В общем-то и всё. Ключевое, конечно, в данном случае - ограничить доступ, иначе вы открываете огромную дыру себе в компьютер, остальное дело 10 минут. Код, выполяющий определённую команду на ASP.NET выглядит просто и банально:

var runCmd = ConfigurationManager.AppSettings["runCmd"];
var processStartInfo = new ProcessStartInfo
{
FileName = runCmd,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
process.WaitForExit();
lbOutput.Text = output + error;

При этом я уверен, что есть уже готовые внешние решения и всякие saas'ы, но ведь самому написать интереснее? 

Зачем я написал эту банальность? А дело в том, что практика показывает, что иногда простые и очевидные решения в голову приходят в самый последний момент (даже для программистов), и человек гораздо вероятнее будет гуглить computer management for android, когда у него всего-лишь одна задча: выполнить ping www.ru и поглядеть на получившийся результат. 

воскресенье, 25 августа 2013 г.

Деплой различных по стабильности версий продукта

Сейчас стало довольно модным использовать следующую модель версий продуктов (Google первый начал, остальные подхватили):
  • Developer — фактически транк, в котором тестируются новые фичи, стабильность не гарантируется и даже декларируется что её может не быть
  • Beta — фичи заморожены, идёт багфиксинг. Стабильность повышается со временем
  • Stable — полностью стабильная версия, обновления выходят только в случае обнаружения серьёзных проблем.
Кроме этого, обычно версионирование идёт по +1, т.е. Stable имеет версию n, Beta — n+1, Developer — n+2.
Когда Beta полностью оттестирована, она переходит в Stable и все версии щелкаются на единичку.

В общем, это всё всем понятно и известно. Но хочу поговорить о таком моменте: в любой момент времени свежесть версий должна идти в этом же порядке. Т.е. даже если сейчас идёт подготовка к релизу стабильной версии, в Beta и Developer должно быть по крайней мере тоже самое. Очевидно? Вроде бы да. Но я сейчас вижу перед глазами свежую Оперу, у которой на момент написания поста самая свежая версия это Beta (Next), а Developer, гораздо древнее. Т.е. явное нарушение концепции. Я не знаю точно, почему так происходит, но могу предположить, что у них одна команда, которая сегодня занимается исследованиями, а завтра в поте лица правит баги. Ну и чего они хотели добиться, внедряя технологию, которую сами же и не смогли осилить?

К чему я клоню? Нравится идея, выглядит интересной, а реализовать её в конкретных ваших условиях можно? Если нельзя, то и не стоит браться, а то всё начнёт превращаться в фарс. Как результат, ничего хорошего не происходит, а все начнут работать на концепцию, тратя драгоценные ресурсы. Или же, если тратить будет лень, сама концепция честно пойдёт лесом, и от неё останется только номинальное название. 

суббота, 17 августа 2013 г.

Методы и производительность динамического создания конструкторов

В .NET достаточно развитые средства рефлексии, и как результат этим активно пользуются. При этом вариантов и способов использования достаточно много. Эти способы отличаются в зависимости от того что надо: по типу действий (создать объект, вызвать его метод, получить/изменить значение поля), по информации об объекте (мы заранее знаем сигнатуру или не знаем), по типу доступа (публичные/приватные методы), по наличию параметров.

Я как-то делал доклад по этим технологиям на YaNA, но с тех пор прошло много времени, обнился Framework, и в моей жизни количество кода с рефлексией значительно возросло, поэтому я решил обновить знания, провести тесты и поделиться результатами.

Сегодня я рассмотрю достаточно простой способ: вызов конкструктора без параметров и с известными параметрами. Это достаточно часто встречающаяся задача, например, она может использоваться для создания плагинов и модулей. При этом мы знаем, что объект имеет определённый тип (отнаследован от интерфейса, например), так что в дальнейшем мы можем вызывать методы напрямую. Но тем не менее, мы не можем напрямую создать объект, т.к. у нас есть только информация о типе (Type), но изначально мы просто не можем написать new MyObject() — данные придут позднее.

Соответственно есть несколько путей решений:

Activator.CreateInstance

Самый простой и удобный метод создания объекта:
Activator.CreateInstance(myObjectType)

Также можно передать параметры для конструктора. Всё просто и очевидно.

ConstructorInfo.Invoke

"Классический рефлекшен". 
var constructorInfo = myObjectType.GetConstructor(new Type[0]);
constructorInfo.Invoke(new object[0]);

Т.е. мы получаем объект ConstructorInfo и через рефлексию создаём объект. В данном примере нет параметров, но их легко можно передать массивом объектов

Compiled Expression

Ещё один сравнительно новый вариант создания объекта, который заключается в построении Expression'а и дальнейшей компиляции его в делегат (анонимную функцию). Способ создания без параметров выглядит так:
var d = Expression.Lambda<Func<object>>(Expression.New(constructorInfo)).Compile();

В результате у нас будет делегат, возвращающий object (можно специфицировать точнее), который мы можем вызывать как обычный метод.

Если конструктор имеет параметры и нам точно известны, какие они должны быть, мы можем использовать подобный код:
var p1 = Expression.Parameter(typeof(int));
var p2 = Expression.Parameter(typeof(string));
var newExpression = Expression.New(constructorInfo, p1, p2);
var d = Expression.Lambda<Func<int, string, object>>(newExpression, true, p1, p2).Compile();

В данном случае, мы точно знаем, что у нас есть конкструктор с двумя параметрами типа int и string, поэтому мы генерируем конкретный код, вызывающий конкструктор с нужными параметрами. В отличие от предыдущих методов, данный слегка читерский, ибо тут мы точно указываем параметры, а мы их можем и не знать, соответственно у этого метода должна быть фора.

Compiled Expression with Any Parameters

Чтобы избавиться от этой форы и рассмотреть случаи, когда мы заранее не можем знать параметры, надо сгенерировать что-то общее. Логично будет рассмотреть случай, когда у нас есть делегат, принимающий массив параметров типа object, при этом данный делегат внутри сам проводит преобразования для корректного вызова. Код будет выглядеть примерно так:

var argsExpr = Expression.Parameter(typeof(object[]));
var constructorParams = constructorInfo.GetParameters()
.Select((p, i) => Expression.Convert(Expression.ArrayAccess(argsExpr, Expression.Constant(i)), p.ParameterType));
var newExpr = Expression.New(constructorInfo, constructorParams);
var d = Expression.Lambda<Func<object[], object>>(newExpr, true, argsExpr).Compile();

Т.е. мы передаём массив, в котором находятся аргументы, потом из этого массива достаём параметры для конструктора, конвертируем к нужному типу и вызываем конструктор.

Dynamic Method

Самый сложный метод, который заключается в использовании DynamicMethod и ручной генерации MSIL'а. Поскольку решение в отличие от остальных не совсем тривиальное, детальное описание реализации оставлю для следующей статьи (будет ссылка, когда статья будет написана). Мы его также как и Expression можем сделать специфичным, или обобщённым. У обобщённого основная идея та же, что и в предыдущем методе: мы генерируем делегат, принимающий массив object'ов, внутри приводим аргументы к нужному типу и вызываем конструктор. Только делаем всё это нативно, генерируя MSIL и компилируя его.

Результаты производительности

Мерять тут можно много чего. Но я остановился на следующих условиях:
  • Только публичные конкструкторы (приватные значительно дольше, а необходимость в их использовании весьма специфичная)
  • Время подготовки нужного объекта не замеряется. Подразумевается, что если уж есть цель оптимизации, то этим временем можно пренебречь. Хотя оно может и вносить ощутимые задержки на первый вызов. Если для вас это будет существенно, наверное стоит провести замеры на ваших условиях и железе.
  • Объекты очень простые но не совершенно пустые (сложность этих объектов может сильно повлиять на расстановку сил, нивелировав или максимально усилив различия между методами
  • Проверяется Debug и Release режим, чтобы выяснить ускорение, получаемое кодом от оптимизации
  • Конкретные цифры я не покажу, ибо это сильно зависит от железа. Только отношение
  • Также для сравнения добавлен прямой вызов конструктора 

Вызов конструктора без параметров

reflection constructor performance

По графику можно сделать простой вывод: если скорость не очень критична, используйте Activator, а если объектов нужно создавать очень много, то Expression. И да, Release режим в некоторых случаях рулит. Рефлекшен через ConstructorInfo самый медленный. Если у вас есть желание, можете использовать DynamicMethod, он самый быстрый.

Вызов конструктора с параметрами

Я взял конструктор с двумя параметрами int и string, чтобы было заметно влияние боксинга. Результаты получились следующие:
reflection constructor performance

Те же графики, но без активатора, чтобы был лучше виден масштаб:
reflection constructor performance


В данном случае Activator показал дичайшие тормоза, что очень удивительно, относительно предыдущего варианта. Обобщённый экспрешшен в 2 раза медленнее специализированного (хоть на графике это плохо видно) и в 4-6 раз медленнее прямого вызова (для сравнения скорости). Т.е. вывод тут такой — используйте скомпилированные экспрешшены, по-возможности с явными типами, если нет, то с обобщёнными, потери не очень сильные. При этом обобщённый DynamicMethod быстрее обобщённого Expresssion'а, но медленнее типизированного. Т.е. если хочется максимальной скорости, придётся относительно много писать, чтобы сделать максимально типизированный и производительный DynamicMethod.

суббота, 29 июня 2013 г.

Эксепшены в Task'ах

Сегодня расскажу о замечательных индусах из Microsoft, которые вначале делают, а потом думают.

Конкретно речь пойдёт про Таски. Не буду про них расписывать, ибо не это тема сегодняшнего поста. Вкратце, это такие треды на стероидах с вкусными и удобными фичам.

Одна из фич, это проброс эксепшенов наружу. Примерно так:

var t = new Task(() => { throw new Exception(); });
t.Start();
// do something
try
{
 t.Wait();
}
catch (AggregateException)
{
 Console.WriteLine("Exception occurred");
 throw;
}

Т.е. мы что-то выполняем в отдельном потоке, в это время занимаемся своими вещами, а потом убеждаемся, что таск закончился и ловим исключение, которое за нас обернули в AggregateException (настоящее скрыто внутри его).
Т.е. получается вполне прикольная фича, можно честно ловить исключения из других потоков в то время, когда мы знаем, как их обработать. И этим можно активно пользоваться (например, скрывая всю параллельную работу внутри метода, но пробрасывая исключения наружу).

Но есть маленький нюанс. Эксепшен ведь пробрасывается, когда от таска что-то ждут (результат, или просто завершение). В стандартной терминологии это называется observable. Т.е. таск должен быть наблюдаемым. А что если мы запустили и забыли (или забили)?

new Task(() => { throw new Exception(); }).Start();

Получается, что эксепшен куда-то потеряется? Как бы не так! Доблестные спецы из Microsoft подумали, и придумали бросать исключение в деструкторе. Оцените изящество этого решения: нарушает все вменяемые стандарты кодирования, приводит к падению приложения, и выдаёт ошибки в то время и в том месте, где их отловить невозможно вообще. Супер!

Наверное, они решили, что раз исключение в обычном треде вызывает падение приложения, то и тут надо. Только забыли про это маленькое отличие с исключениями: если мы хотим сделать безопасным обычный тред, нам достаточно написать try/catch и все станут счастливы (всё равно никто за нас ошибки не обработает). А случае с тасками, кидать исключение изнутри наружу можно и местами иногда полезно. Но получается, что за этими тасками приходится следить как за маленькими детьми, хотя нам вполне может быть глубоко наплевать в данном месте на результат конкретной операции.

Решений данной проблемы в принципе можно придумать несколько (различные врапперы, следить за использованием тасков), одно из достаточно красивых такое:

public static Task IgnoreErrors(this Task t)
{
 t.ContinueWith(x => { var e = x.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
 return t;
}

private static void DoTask()
{
 new Task(() => { throw new Exception(); }).IgnoreErrors().Start();
}

Т.е. мы создаём extension-метод, который прикрепляет к нашей таске продолжение (continuation), в котором мы берём значение свойства Exception (это обязательно надо сделать! именно так таска становится опять наблюдаемой (observable)), и на этом успокаиваем систему.

Задача решена, и надеюсь, что у вас она вызовет мата меньше чем у меня в своё время, когда я попался на такую проблему. А попался я на неё ещё по одной причине.

Бонус

Я вначале не зря писал, что в Microsoft вначале сделали, а потом начали думать. А подумав, они поняли, что поведение уж больно идиотское и убивать всё приложение никому не нужно. И в 4.5 изменили логику, так что для 4.5 уже мой пост неактуален.

Но без нюансов у Microsoft не обходится, ибо 4.5 по факту подменяет собой 4.0. Сюрприз! Т.е. установив 4.5, все приложения, написанные под 4.0 начинают по факту работать на 4.5, и слегка по-другому. Например, в данном случае, ошибка уже не появится (попадаются и некоторые другие различия в поведении, но это уж слишком выраженное). Вернуть старое поведение можно конфигом, но кто же заморачивается чтением всяких безумных и бесполезных настроек?

Т.е. вы как разработчик поставили все апдейты, написали приложение, протестировали в хвост и в гриву, а падает оно только у клиента. Ибо у клиента стоит Windows Server 2003, и ваше приложение должно с ним работать, и даже работает, только падает. Иногда. С непонятным стэктрейсом. И вы пытаетесь найти хвосты и поправить всё это безобразие.

Иногда просто хочется убиться головой об стену от таких гениальных поворотов сюжета.

вторник, 28 мая 2013 г.

Яндекс, ты пьян, иди домой!

Не силён в географии, и не факт что я ниже не займусь полным бредом, но Яндекс Карты взрывают мозг.
Вот кусок схемы дорог:

Это трасса М8, которая судя по схеме идёт через Лисавы, Тириброво и далее. Вроде всё в порядке.
Увеличиваем схему на один шаг.

Видно разницу? Если сразу не заметили, то распишу: через Лисавы и Тириброво уже идёт Ярославское шоссе, а трасса М8 неожиданно оказывается южнее.

WAT????
Т.е. на разных масштабах у Яндекса разные картографические данные. Как так?

При этом маршруты судя по всему, Яндекс строит исходя из первого варианта, в дальнейшем предлгая проехать через кусты и разделительную полосу.

Ну и как можно доверять яндесу в прокладке маршутов? При этом, что удивительно, но у Гугла всё гораздо лучше.

понедельник, 27 мая 2013 г.

Цифровая подпись больших файлов на .NET в потоковом режиме

Стандартный механизм в .NET по подписи через SignedCms, весьма симпатичный, но есть одна проблема: для подписи необходимо передать массив байт, т.е. если надо подписать большой файл, то вначале прочитать в память в .NET потом засунуть этот же объём системному API, что весьма затратно по памяти.

Конечно, в CryptoPro есть замечательный метод SignHash, который позволяет заранее посчитать хеш и его подписать (что идентично подписи самого файла), но это только у CryptoPro, стандартное API даже на низком уровне не позволяет провести такое.

Гугление выдало код, который может шифровать в потоковом режиме, но у него обнаружилось несколько проблем:

  • Почему-то используется FileStream, хотя достаточно просто Stream
  • Реально всё равно читается всё содержимое (!) а потом подписывается в потоковом режиме
  • Нет поддержки Detached подписи, хотя добавить её несложно
  • Внаглую из сертификата берётся приватный ключ, который опять же считается RSA (наивные! совершенно не знают что в России творится)
В общем, я поправил этот код, и выкладываю его здесь, ибо взял из блога исходники, надо их таким же образом вернуть, чтобы людям было приятно.

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

StreamCms.cs
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace NS1
{
 [ComVisible(false)]
 public class StreamCms
 {
  // File stream to use in callback function
  private Stream _callbackFile;

  // Streaming callback function for encoding
  private bool StreamOutputCallback(IntPtr pvArg, IntPtr pbData, int cbData, bool fFinal)
  {
   if (cbData == 0) return true;
   // Write all bytes to encoded file
   var bytes = new byte[cbData];
   Marshal.Copy(pbData, bytes, 0, cbData);
   _callbackFile.Write(bytes, 0, cbData);

   if (fFinal)
   {
    // This is the last piece. Close the file
    _callbackFile.Flush();
    _callbackFile.Close();
    _callbackFile = null;
   }

   return true;
  }

  // Encode StreamCms with streaming to support large data
  public void Encode(X509Certificate2 cert, Stream inFile, Stream outFile, bool isDetached)
  {
   // Variables

   IntPtr hProv = IntPtr.Zero;
   IntPtr SignerInfoPtr = IntPtr.Zero;
   IntPtr CertBlobsPtr = IntPtr.Zero;
   IntPtr hMsg = IntPtr.Zero;

   try
   {
    // Prepare stream for encoded info
    _callbackFile = outFile;

    // Get cert chain
    var chain = new X509Chain();
    chain.Build(cert);
    var chainElements = new X509ChainElement[chain.ChainElements.Count];
    chain.ChainElements.CopyTo(chainElements, 0);

    // Get certs in chain
    var certs = new X509Certificate2[chainElements.Length];
    for (int i = 0; i < chainElements.Length; i++)
    {
     certs[i] = chainElements[i].Certificate;
    }

    // Get context of all certs in chain
    var CertContexts = new Win32.CERT_CONTEXT[certs.Length];
    for (int i = 0; i < certs.Length; i++)
    {
     CertContexts[i] = (Win32.CERT_CONTEXT)Marshal.PtrToStructure(certs[i].Handle, typeof(Win32.CERT_CONTEXT));
    }

    // Get cert blob of all certs
    var CertBlobs = new Win32.BLOB[CertContexts.Length];
    for (int i = 0; i < CertContexts.Length; i++)
    {
     CertBlobs[i].cbData = CertContexts[i].cbCertEncoded;
     CertBlobs[i].pbData = CertContexts[i].pbCertEncoded;
    }

    // Get CSP of client certificate

    Win32.CRYPT_KEY_PROV_INFO csp;
    GetPrivateKeyInfo(GetCertContext(cert), out csp);

    bool bResult = Win32.CryptAcquireContext(
     ref hProv,
     csp.pwszContainerName,
     csp.pwszProvName,
     (int)csp.dwProvType,
     0);
    if (!bResult)
    {
     throw new Exception("CryptAcquireContext error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }

    // Populate Signer Info struct
    var SignerInfo = new Win32.CMSG_SIGNER_ENCODE_INFO();
    SignerInfo.cbSize = Marshal.SizeOf(SignerInfo);
    SignerInfo.pCertInfo = CertContexts[0].pCertInfo;
    SignerInfo.hCryptProvOrhNCryptKey = hProv;
    SignerInfo.dwKeySpec = (int)csp.dwKeySpec;
    SignerInfo.HashAlgorithm.pszObjId = cert.SignatureAlgorithm.Value; // Win32.szOID_OIWSEC_sha1;

    // Populate Signed Info struct
    var SignedInfo = new Win32.CMSG_SIGNED_ENCODE_INFO();
    SignedInfo.cbSize = Marshal.SizeOf(SignedInfo);

    SignedInfo.cSigners = 1;
    SignerInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(SignerInfo));
    Marshal.StructureToPtr(SignerInfo, SignerInfoPtr, false);
    SignedInfo.rgSigners = SignerInfoPtr;

    SignedInfo.cCertEncoded = CertBlobs.Length;
    CertBlobsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(CertBlobs[0]) * CertBlobs.Length);
    for (int i = 0; i < CertBlobs.Length; i++)
    {
     Marshal.StructureToPtr(CertBlobs[i], new IntPtr(CertBlobsPtr.ToInt64() + (Marshal.SizeOf(CertBlobs[i]) * i)), false);
    }

    SignedInfo.rgCertEncoded = CertBlobsPtr;

    // Populate Stream Info struct
    var StreamInfo = new Win32.CMSG_STREAM_INFO
                      {
                       cbContent = (int)inFile.Length,
                       pfnStreamOutput = StreamOutputCallback
                      };

    // Open message to encode
    hMsg = Win32.CryptMsgOpenToEncode(
     Win32.X509_ASN_ENCODING | Win32.PKCS_7_ASN_ENCODING,
     isDetached ? Win32.CMSG_DETACHED_FLAG : 0,
     Win32.CMSG_SIGNED,
     ref SignedInfo,
     null,
     ref StreamInfo);

    if (hMsg.Equals(IntPtr.Zero))
    {
     throw new Exception("CryptMsgOpenToEncode error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }

    // Process the whole message
    ProcessMessage(hMsg, inFile);
   }
   finally
   {
    // Clean up

    if (inFile != null)
    {
     inFile.Close();
    }

    if (_callbackFile != null)
    {
     _callbackFile.Close();
    }

    if (!CertBlobsPtr.Equals(IntPtr.Zero))
    {
     Marshal.FreeHGlobal(CertBlobsPtr);
    }

    if (!SignerInfoPtr.Equals(IntPtr.Zero))
    {
     Marshal.FreeHGlobal(SignerInfoPtr);
    }

    if (!hProv.Equals(IntPtr.Zero))
    {
     Win32.CryptReleaseContext(hProv, 0);
    }

    if (!hMsg.Equals(IntPtr.Zero))
    {
     Win32.CryptMsgClose(hMsg);
    }
   }
  }

  // Decode StreamCms with streaming to support large data
  public void Decode(Stream dataFile, Stream signFile, Stream outFile, bool isDetached)
  {
   // Variables

   IntPtr hMsg = IntPtr.Zero;
   IntPtr pSignerCertInfo = IntPtr.Zero;
   IntPtr pSignerCertContext = IntPtr.Zero;
   IntPtr hStore = IntPtr.Zero;

   try
   {
    // Get data to decode

    // Prepare stream for decoded info
    _callbackFile = outFile;

    // Populate Stream Info struct
    var StreamInfo = new Win32.CMSG_STREAM_INFO
                      {
                       cbContent = (int)signFile.Length,
                       pfnStreamOutput = StreamOutputCallback
                      };

    // Open message to decode
    hMsg = Win32.CryptMsgOpenToDecode(
     Win32.X509_ASN_ENCODING | Win32.PKCS_7_ASN_ENCODING,
     isDetached ? Win32.CMSG_DETACHED_FLAG : 0,
     0,
     IntPtr.Zero,
     IntPtr.Zero,
     ref StreamInfo);
    if (hMsg.Equals(IntPtr.Zero))
    {
     throw new Exception("CryptMsgOpenToDecode error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }

    // Process the whole message
    if (isDetached)
    {
     ProcessMessage(hMsg, signFile);
     ProcessMessage(hMsg, dataFile);
    }
    else
    {
     ProcessMessage(hMsg, signFile);
    }

    // Get signer certificate info
    int cbSignerCertInfo = 0;
    bool bResult = Win32.CryptMsgGetParam(
     hMsg,
     Win32.CMSG_SIGNER_CERT_INFO_PARAM,
     0,
     IntPtr.Zero,
     ref cbSignerCertInfo);
    if (!bResult)
    {
     throw new Exception("CryptMsgGetParam error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }

    pSignerCertInfo = Marshal.AllocHGlobal(cbSignerCertInfo);

    bResult = Win32.CryptMsgGetParam(
     hMsg,
     Win32.CMSG_SIGNER_CERT_INFO_PARAM,
     0,
     pSignerCertInfo,
     ref cbSignerCertInfo);
    if (!bResult)
    {
     throw new Exception("CryptMsgGetParam error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }

    // Open a cert store in memory with the certs from the message
    hStore = Win32.CertOpenStore(
     Win32.CERT_STORE_PROV_MSG,
     Win32.X509_ASN_ENCODING | Win32.PKCS_7_ASN_ENCODING,
     IntPtr.Zero,
     0,
     hMsg);
    if (hStore.Equals(IntPtr.Zero))
    {
     throw new Exception("CertOpenStore error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }

    // Find the signer's cert in the store
    pSignerCertContext = Win32.CertGetSubjectCertificateFromStore(
     hStore,
     Win32.X509_ASN_ENCODING | Win32.PKCS_7_ASN_ENCODING,
     pSignerCertInfo);
    if (pSignerCertContext.Equals(IntPtr.Zero))
    {
     throw new Exception("CertGetSubjectCertificateFromStore error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }

    // Set message for verifying
    var SignerCertContext = (Win32.CERT_CONTEXT)Marshal.PtrToStructure(pSignerCertContext, typeof(Win32.CERT_CONTEXT));
    bResult = Win32.CryptMsgControl(
     hMsg,
     0,
     Win32.CMSG_CTRL_VERIFY_SIGNATURE,
     SignerCertContext.pCertInfo);
    if (!bResult)
    {
     throw new Exception("CryptMsgControl error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
    }
   }
   finally
   {
    // Clean up
    if (!pSignerCertContext.Equals(IntPtr.Zero))
    {
     Win32.CertFreeCertificateContext(pSignerCertContext);
    }

    if (!pSignerCertInfo.Equals(IntPtr.Zero))
    {
     Marshal.FreeHGlobal(pSignerCertInfo);
    }

    if (!hStore.Equals(IntPtr.Zero))
    {
     Win32.CertCloseStore(hStore, Win32.CERT_CLOSE_STORE_FORCE_FLAG);
    }

    if (dataFile != null)
    {
     dataFile.Close();
    }

    if (signFile != null)
    {
     signFile.Close();
    }

    if (_callbackFile != null)
    {
     _callbackFile.Close();
    }

    if (!hMsg.Equals(IntPtr.Zero))
    {
     Win32.CryptMsgClose(hMsg);
    }
   }
  }

  private void ProcessMessage(IntPtr hMsg, Stream dataStream)
  {
   long streamSize = dataStream.Length;
   if (streamSize == 0)
    throw new CryptographicException("Cannot encode zero length data");
   var gchandle = new GCHandle();
   const int ChunkSize = 1024 * 1024;
   var dwSize = (int)((streamSize < ChunkSize) ? streamSize : ChunkSize);
   var pbData = new byte[dwSize];

   try
   {
    var dwRemaining = streamSize;
    gchandle = GCHandle.Alloc(pbData, GCHandleType.Pinned);
    var pbPtr = gchandle.AddrOfPinnedObject();

    while (dwRemaining > 0)
    {
     dataStream.Read(pbData, 0, dwSize);

     // Update message piece by piece     
     var bResult = Win32.CryptMsgUpdate(hMsg, pbPtr, dwSize, (dwRemaining <= dwSize));
     if (!bResult)
     {
      throw new Exception(
       "CryptMsgUpdate error #" + Marshal.GetLastWin32Error().ToString(),
       new Win32Exception(Marshal.GetLastWin32Error()));
     }

     dwRemaining -= dwSize;
     if (dwRemaining < dwSize)
      dwSize = (int)dwRemaining;

     // if (gchandle.IsAllocated)
     //  gchandle.Free();
    }
   }
   finally
   {
    if (gchandle.IsAllocated)
    {
     gchandle.Free();
    }
   }
  }

  internal static Win32.CertHandle GetCertContext(X509Certificate2 certificate)
  {
   var handle = Win32.CertDuplicateCertificateContext(certificate.Handle);
   GC.KeepAlive(certificate);
   return handle;
  }

  internal static bool GetPrivateKeyInfo(Win32.CertHandle safeCertContext, out Win32.CRYPT_KEY_PROV_INFO parameters)
  {
   parameters = new Win32.CRYPT_KEY_PROV_INFO();
   var invalidHandle = new Win32.SafeHandle(IntPtr.Zero);
   uint pcbData = 0;
   if (!Win32.CertGetCertificateContextProperty(safeCertContext, 2, invalidHandle.DangerousGetHandle(), ref pcbData))
   {
    if (Marshal.GetLastWin32Error() != -2146885628)
    {
     throw new CryptographicException(Marshal.GetLastWin32Error());
    }

    return false;
   }

   invalidHandle = Win32.LocalAlloc(0, new IntPtr(pcbData));
   if (!Win32.CertGetCertificateContextProperty(safeCertContext, 2, invalidHandle.DangerousGetHandle(), ref pcbData))
   {
    if (Marshal.GetLastWin32Error() != -2146885628)
    {
     throw new CryptographicException(Marshal.GetLastWin32Error());
    }

    return false;
   }

   parameters = (Win32.CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(invalidHandle.DangerousGetHandle(), typeof(Win32.CRYPT_KEY_PROV_INFO));
   invalidHandle.Dispose();
   return true;
  }
 }
}
Win32.cs
using System;
using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

namespace NS1
{
 [ComVisible(false)]
 public class Win32
 {
  #region "CONSTS"

  public const int X509_ASN_ENCODING = 0x00000001;

  public const int PKCS_7_ASN_ENCODING = 0x00010000;

  public const int CMSG_SIGNED = 2;

  public const int CMSG_DETACHED_FLAG = 0x00000004;

  public const int AT_KEYEXCHANGE = 1;

  public const int AT_SIGNATURE = 2;

  // public const string szOID_OIWSEC_sha1 = "1.3.14.3.2.26";

  public const int CMSG_CTRL_VERIFY_SIGNATURE = 1;

  public const int CMSG_CERT_PARAM = 12;

  public const int CMSG_SIGNER_CERT_INFO_PARAM = 7;

  public const int CERT_STORE_PROV_MSG = 1;

  public const int CERT_CLOSE_STORE_FORCE_FLAG = 1;

  #endregion

  #region "STRUCTS"

  [StructLayout(LayoutKind.Sequential)]
  [ComVisible(false)]
  public struct CRYPT_ALGORITHM_IDENTIFIER
  {
   public string pszObjId;
   public BLOB Parameters;
  }

  [StructLayout(LayoutKind.Sequential)]
  [ComVisible(false)]
  public struct CERT_ID
  {
   public int dwIdChoice;
   public BLOB IssuerSerialNumberOrKeyIdOrHashId;
  }

  [StructLayout(LayoutKind.Sequential)]
  [ComVisible(false)]
  public struct CMSG_SIGNER_ENCODE_INFO
  {
   public int cbSize;
   public IntPtr pCertInfo;
   public IntPtr hCryptProvOrhNCryptKey;
   public int dwKeySpec;
   public CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm;
   public IntPtr pvHashAuxInfo;
   public int cAuthAttr;
   public IntPtr rgAuthAttr;
   public int cUnauthAttr;
   public IntPtr rgUnauthAttr;
   public CERT_ID SignerId;
   public CRYPT_ALGORITHM_IDENTIFIER HashEncryptionAlgorithm;
   public IntPtr pvHashEncryptionAuxInfo;
  }

  [StructLayout(LayoutKind.Sequential)]
  [ComVisible(false)]
  public struct CERT_CONTEXT
  {
   public int dwCertEncodingType;
   public IntPtr pbCertEncoded;
   public int cbCertEncoded;
   public IntPtr pCertInfo;
   public IntPtr hCertStore;
  }

  [StructLayout(LayoutKind.Sequential)]
  [ComVisible(false)]
  public struct BLOB
  {
   public int cbData;
   public IntPtr pbData;
  }

  [StructLayout(LayoutKind.Sequential)]
  [ComVisible(false)]
  public struct CMSG_SIGNED_ENCODE_INFO
  {
   public int cbSize;
   public int cSigners;
   public IntPtr rgSigners;
   public int cCertEncoded;
   public IntPtr rgCertEncoded;
   public int cCrlEncoded;
   public IntPtr rgCrlEncoded;
   public int cAttrCertEncoded;
   public IntPtr rgAttrCertEncoded;
  }

  [StructLayout(LayoutKind.Sequential)]
  [ComVisible(false)]
  public struct CMSG_STREAM_INFO
  {
   public int cbContent;
   public StreamOutputCallbackDelegate pfnStreamOutput;
   public IntPtr pvArg;
  }

  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  [ComVisible(false)]
  internal struct CRYPT_KEY_PROV_INFO
  {
   internal string pwszContainerName;
   internal string pwszProvName;
   internal uint dwProvType;
   internal uint dwFlags;
   internal uint cProvParam;
   internal IntPtr rgProvParam;
   internal uint dwKeySpec;
  }

  #endregion

  #region "DELEGATES"

  [ComVisible(false)]
  public delegate bool StreamOutputCallbackDelegate(IntPtr pvArg, IntPtr pbData, int cbData, Boolean fFinal);

  #endregion

  #region "API"

  [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  [ComVisible(false)]
  public static extern Boolean CryptAcquireContext(
    ref IntPtr hProv,
    String pszContainer,
    String pszProvider,
    int dwProvType,
    int dwFlags);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern IntPtr CryptMsgOpenToEncode(
   int dwMsgEncodingType,
   int dwFlags,
   int dwMsgType,
   ref CMSG_SIGNED_ENCODE_INFO pvMsgEncodeInfo,
   String pszInnerContentObjID,
   ref CMSG_STREAM_INFO pStreamInfo);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern IntPtr CryptMsgOpenToDecode(
   int dwMsgEncodingType,
   int dwFlags,
   int dwMsgType,
   IntPtr hCryptProv,
   IntPtr pRecipientInfo,
   ref CMSG_STREAM_INFO pStreamInfo);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern Boolean CryptMsgClose(IntPtr hCryptMsg);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern Boolean CryptMsgUpdate(
   IntPtr hCryptMsg,
   Byte[] pbData,
   int cbData,
   Boolean fFinal);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern Boolean CryptMsgUpdate(
   IntPtr hCryptMsg,
   IntPtr pbData,
   int cbData,
   Boolean fFinal);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern Boolean CryptMsgGetParam(
   IntPtr hCryptMsg,
   int dwParamType,
   int dwIndex,
   IntPtr pvData,
   ref int pcbData);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern Boolean CryptMsgControl(
   IntPtr hCryptMsg,
   int dwFlags,
   int dwCtrlType,
   IntPtr pvCtrlPara);

  [DllImport("advapi32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern Boolean CryptReleaseContext(
   IntPtr hProv,
   int dwFlags);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern IntPtr CertCreateCertificateContext(
   int dwCertEncodingType,
   IntPtr pbCertEncoded,
   int cbCertEncoded);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern bool CertFreeCertificateContext(IntPtr pCertContext);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern IntPtr CertOpenStore(
   int lpszStoreProvider,
   int dwMsgAndCertEncodingType,
   IntPtr hCryptProv,
   int dwFlags,
   IntPtr pvPara);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern IntPtr CertGetSubjectCertificateFromStore(
   IntPtr hCertStore,
   int dwCertEncodingType,
   IntPtr pCertId);

  [DllImport("Crypt32.dll", SetLastError = true)]
  [ComVisible(false)]
  public static extern IntPtr CertCloseStore(
   IntPtr hCertStore,
   int dwFlags);

  [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  internal static extern CertHandle CertDuplicateCertificateContext([In] IntPtr pCertContext);

  [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  internal static extern bool CertGetCertificateContextProperty([In] CertHandle pCertContext, [In] uint dwPropId, [In, Out] IntPtr pvData, [In, Out] ref uint pcbData);

  [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  internal static extern SafeHandle LocalAlloc([In] uint uFlags, [In] IntPtr sizetdwBytes);

  [DllImport("kernel32.dll", SetLastError = true)]
  private static extern IntPtr LocalFree(IntPtr handle);

  #endregion

  public class SafeHandle : SafeHandleZeroOrMinusOneIsInvalid
  {
   public SafeHandle(IntPtr handle) : base(true)
   {
    SetHandle(handle);
   }


   public SafeHandle()
    : base(true)
   {
   }

   protected override bool ReleaseHandle()
   {
    return (LocalFree(handle) == IntPtr.Zero);
   }
  }

  public class CertHandle : SafeHandleZeroOrMinusOneIsInvalid
  {
   public CertHandle()
    : base(true)
   {
   }

   public CertHandle(bool ownsHandle)
    : base(ownsHandle)
   {
   }

   protected override bool ReleaseHandle()
   {
    return CertFreeCertificateContext(handle);
   }
  }
 }
}