КриптоПро CSP  

Возвращение данных неопределенной длины

Возвращение данных неопределенной длины

Многие функции криптопровайдера и CryptoAPI возвращают неопределенное количество данных по адресу, предоставленному через параметр функции. Во всех таких случаях операция производится в следующем порядке. Параметр, который содержит указатель на возвращаемые данные, записываетcя с префиксом "pb" или "pv" с последующим именем параметра. Другой параметр функции имеет префикс "pcb" с последующим именем параметра. Данный параметр определяет длину в байтах данных, которые будут возвращены в адреса, описанные переменными с префиксами "pb" или "pv". Следующий пример показывает спецификацию функции такого типа:

	    
 BOOL WINAPI SomeFunction(
 PCCRL_CONTEXT pCrlContext,  // in
 DWORD dwPropId,             // in
 BYTE *pbData,               // out
 DWORD *pcbData              // in/out
 );
	

В данном примере параметр pbData содержит указатель на адрес, в который будут возвращены данные, а параметр pcbData определяет размет в байтах возвращаемых данных.

Примечание 1. Префикс параметров, содержащих возвращаемые данные, может иногда отличаться от "pcb", например "p" или "pv". Соответствующий параметр, определяющий длину данных тоже может иметь другой префикс, например для "pwsz" он может быть "pcch", где "pcch" определяет длину символов в кодировке Unicode или ASCII возвращаемых данных.

Если размер области памяти специфицированной параметром pbData недостаточен для возвращаемых данных, функция вернет код ошибки ERROR_MORE_DATA , который можно определить, используя функцию GetLastError, а в переменной pcbData установит требуемое количество байт памяти, необходимое для возвращаемых данных.

Если значение NULL указывается в качестве значения параметра pbData и переменная pcbData не NULL, ошибка не будет возвращена и функция возвратит размер требуемой памяти в байтах в переменной, определенной указателем pcbData. Это дает возможность приложению определить размер и зарезервировать требуемое количество памяти.

Примечание 2. Когда значение NULL указывается в качестве значения параметра pbData для определения размера возвращаемых данных, второй вызов функции может не использовать полностью область памяти. После второго вызова функции действительный размер данных возвращается по адресу переменной в pcbData. Используйте это значение для обработки данных.

Примечание 3. Использование двухпроходной схемы (NULL указывается в качестве значения параметра pbData) может приводить к снижению производительности для некоторых функций. К таким функциям в первую очередь относятся функции создания сообщений, относящийся к классам Low Level Message Functions и Simplified Message Functions, так как для определения размера сообщения производится его полное формирование, включая генерацию сеансовых ключей. Для приложений, в которых критерий производительности является определяющим, можно рекомендовать заранее резервировать буфер для выходных данных определенной длины и обрабатывать код возврата функции.

Последующие примеры показывают реализация обеих схем.

Процедура с учетом производительности

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

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

	    
 //--------------------------------------------------------------------
 // Функция SomeFunction (из предыдущего примера) вызывается приложением 
 // в цикле. Размер буфера для выходных данных устанавливается 
 // значением 50 байт.
 
 //--------------------------------------------------------------------
 // Определение переменных функции SomeFunction.
 
 PCCRL_CONTEXT pCrlContext; // Инициализируется в другом месте.
 DWORD dwPropId;            // Инициализируется в другом месте.
 
 //--------------------------------------------------------------------
 // Изначально под буфер выделяется 50 байт. Затем этот буфер используется
 // при вызове функции SomeFunction, которая в большинстве случаев 
 // осуществляет обработку и вывод некоторых желаемых данных. 
 // Даже если для выходных данных требуется больше 50 байт, изначально
 // выделяется именно 50 байт. В этом случае первый вызов функции
 // будет неудачным, но возвращенное им значение cbData будет использоваться
 // для выделения большей памяти под буфер. Таким образом второй вызов 
 // функции SomeFunction будет использовать буфер большей длины. 
 
 //--------------------------------------------------------------------
 //   Считается, что переменные pCrlContext и dwPropId были определены  
 //   и проинициализированы в другом месте.
 
 DWORD dwTries = 0; // количество вызовов функции
 BOOL fMore=TRUE;   // флаг, определяющий, следует ли осуществлять вызов
                      // функции
 DWORD cbData = 50;
 BYTE  *pbData;
 
 //--------------------------------------------------------------------
 //  Начало обработки. Этот цикл выполняется не более двух раз.
 
 while (fMore)
 {   
 if (pbData = (BYTE *)malloc(cbData))
 {
      printf("Memory has been allocated.\n");
 }
 else
 { 
      HandleError("Memory allocation error.");
 }
 //--------------------------------------------------------------------
 //  Память выделена. Вызов функции.

    if (SomeFunction(pCrlContext, dwPropId, pbData, &cbData))
        // Вызов функции был успешным. Выход из цикла.
    {
             printf("The function succeeded. Exit the loop.\n");
             fMore=FALSE;
    }
    else
    {
          // Вызов функции был неудачным. Освобождение выделенной памяти.
           free(pbData);  // Освобождение буфера в случае неудачного исхода.
 
          // Если ошибка отличается от того, что под буфер выделено
          // недостаточное количество памяти, то цикл успешно не 
          // завершается. Обработка ошибки.
 
          if(GetLastError() != ERROR_MORE_DATA )
          {
               HandleError("General function failure.");
          }
          // Если ошибка заключается в том, что под буфер выделено
          // недостаточное количество памяти, то осуществляется вторая,
            // но не третья попытка.
          if( ++dwtries > 1 )
           {
               HandleError("Function call failed twice ");
           }
 }  // Конец цикла
 
 //--------------------------------------------------------------------
 //  Если цикл завершился, то вызов функции был успешным либо во время 
 //  первой, либо во время второй итерации цикла. В обоих случаях в процессе  
 //  выполнения значение cbData заменяется фактическим размером  
 //  возвращаемых данных. Для обработки байт в буфере, на который указывает 
 //  pbData, следует пользоваться этим размером.
 
 //--------------------------------------------------------------------
 //  В данном месте осуществляется обработка pbData.
 
 //--------------------------------------------------------------------
 //  Очистка после обработки.
 
 free(pbData); // Освобождение буфера данных 
               // после того, как данные были обработаны.
	

Увеличение производительности может быть получено также за счет использования alloca для отведения памяти в стеке вместо использования malloc для отведения памяти в куче (heap). Из за того, что буфер находится в стеке, нет необходимости вызова free при завершении работы с буфером. При использовании alloc, данные буфера должны быть использованы локально, так как по завершению функции, буфер автоматически освобождается очисткой стека. Вместе с этим, вызов alloc уменьшает размер стека, что может провести к его переполнению.

Процедура с предварительным определением длины буфера

Следующий пример показывает способ определения требуемой длины буфера для возвращаемых данных:

	    
 #include <stdio.h>;
 #include <windows.h>;
 #include <wincrypt.h>;
 #define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
 
 void HandleError(char *s);
 
 void main()
 {
 //--------------------------------------------------------------------
 // Определение переменных функции SomeFunction.
 
 PCCRL_CONTEXT pCrlContext; // Инициализируется в другом месте.
 DWORD dwPropId;            // Инициализируется в другом месте.
 DWORD cbData;
 BYTE  *pbData;
 
 //--------------------------------------------------------------------
 // Вызов функции SomeFunction для определения необходимого размера 
 // буфера pbData. Это значение устанавливается в cbData.
 
 if(SomeFunction(
      pCrlContext, 
      dwPropId, 
      NULL, 
      &cbData))
 {
        printf("The function succeeded.\n");
 }
 else
 {
 // Вызов функции завершился неудачей. Обработка ошибки.
        HandleError("Function call failed.");
 }
 
 //--------------------------------------------------------------------
 // Вызов функции был успешным. На данный момент cbData содержит 
 // необходимый размер буфера в байтах.
 
 //--------------------------------------------------------------------
 // Выделение памяти в соответствии с размером сообщения.
 
 if(pbData = (BYTE*)malloc(cbData))
 {
    printf("Memory has been allocated.\n");
 }
 else
 {
    // Приложение потерпело неудачу. Вывод сообщения об ошибке и 
    // выход из программы.
    HandleError("Malloc operation failed. ");
 }
 
 //--------------------------------------------------------------------
 // Память под буфер была выделена. Вызов функции SomeFunction
 // для заполнения буфера данными.
 
 if(SomeFunction(
       pCrlContext, 
       dwPropId, 
       pbData, 
       &cbData))
 {
        printf("The function succeeded.\n");
 }
 else
 {
    // Повторный вызов функции завершился неудачей. Обработка ошибки.
    HandleError("The second call to the function failed.");
 }
 //*******************************************************************
 // Функия выполнилась успешно. На данный момент данные находятся в 
 // буфере, на который указывает pbData. Необходимо помнить, что 
 // в процессе выполнения значение cbData заменяется фактическим размером  
 // возвращаемых данных. Для обработки байт в буфере, на который указывает 
 // pbData, следует пользоваться этим размером.

 } // Конец main
 
 //--------------------------------------------------------------------
 // В этом примере используется функция HandleError для обработки 
 // кода ошибок, вывода сообщения и выхода из программы. В приложениях 
 // рекомендуется заменить данную функцию другой, которая позволит 
 // произвести более подробную диагностику кода ошибки 
 
 void HandleError(char *s)
 {
     fprintf(stderr,"An error occurred in running the program.\n");
     fprintf(stderr,"%s\n",s);
     fprintf(stderr,"Error number %x.\n",GetLastError());
     fprintf(stderr,"Program terminating.\n");
     exit(1);
 }