Правильный способ обработки параметра NULL ppPins в методе EnumPins DirectShow Filter (Delphi / DSPACK)?

#delphi #filter #directshow #dspack

#delphi #Фильтр #directshow #dspack

Вопрос:

У меня есть пользовательский push-фильтр, написанный на Delphi 6 с использованием библиотеки компонентов DSPACK DirectShow на компьютере с Windows XP / 32. Во время кодирования я столкнулся с проблемой с ошибками проверки диапазона, возникающими в блоке базового класса, принадлежащем DSPACK (BaseClass.pas), после того, как я включил проверку диапазона в настройках компилятора моего приложения.

Ошибка возникает во время моего метода EnumPins(). Обратите внимание, что этот метод находится в модуле базового класса DSPACK, а не в моем приложении. Я отследил проблему и обнаружил, что она возникает, когда граф фильтров, который я создаю, который использует мой фильтр, воспроизводит фильтр. Обратите внимание, что мой фильтр напрямую включается в мое приложение как частный незарегистрированный фильтр, а не как внешний AX. Когда DirectShow вызывает метод базового класса TBCEnumPins.Next(), если параметр ppPins равен НУЛЮ, возникает ошибка проверки диапазона. Поскольку я не эксперт DirectShow, я не уверен, как правильно исправить эту ошибку, не нарушая надлежащего процесса перечисления выводов DirectShow. Если вместо этого это условие истинной ошибки, которое нельзя игнорировать, тогда мне нужно знать, какое правильное исключение выбрасывать или код HRESULT возвращать в этом событии. Может кто-нибудь сказать мне, как правильно настроить этот код для параметра NIL ppPins? Полный код метода приведен ниже, выделена строка, в которой возникает ошибка проверки диапазона:

 function TBCEnumPins.Next(cPins: ULONG; out ppPins: IPin; pcFetched: PULONG): HRESULT;
type
  TPointerDynArray = array of Pointer;
  TIPinDynArray = array of IPin;
var
  Fetched: cardinal;
  RealPins: integer;
  Pin: TBCBasePin;
begin
    // ATI: Debugging range check error.
    try
        if pcFetched <> nil then
          pcFetched^ := 0
        else
          if (cPins>1) then
          begin
            result := E_INVALIDARG;
            exit;
          end;
        Fetched := 0; // increment as we get each one.

        // Check we are still in sync with the filter
        // If we are out of sync, we should refresh the enumerator.
        // This will reset the position and update the other members, but
        // will not clear cache of pins we have already returned.
        if AreWeOutOfSync then
          Refresh;

        // Calculate the number of available pins
        RealPins := min(FPinCount - FPosition, cPins);
        if RealPins = 0 then
        begin
          result := S_FALSE;
          exit;
        end;

        {  Return each pin interface NOTE GetPin returns CBasePin * not addrefed
           so we must QI for the IPin (which increments its reference count)
           If while we are retrieving a pin from the filter an error occurs we
           assume that our internal state is stale with respect to the filter
           (for example someone has deleted a pin) so we
           return VFW_E_ENUM_OUT_OF_SYNC }

        while RealPins > 0 do
        begin
          // Get the next pin object from the filter */
          inc(FPosition);
          Pin := FFilter.GetPin(FPosition-1);
          if Pin = nil then
          begin
            // If this happend, and it's not the first time through, then we've got a problem,
            // since we should really go back and release the iPins, which we have previously
            // AddRef'ed.
            ASSERT(Fetched = 0);
            result := VFW_E_ENUM_OUT_OF_SYNC;
            exit;
          end;

          // We only want to return this pin, if it is not in our cache
          if FPinCache.IndexOf(Pin) = -1 then
          begin
            // From the object get an IPin interface
            TPointerDynArray(@ppPins)[Fetched] := nil; // <<<<<< THIS IS WHERE THE RANGE CHECK ERROR OCCURS.
            TIPinDynArray(@ppPins)[Fetched] := Pin;
            inc(Fetched);
            FPinCache.Add(Pin);
            dec(RealPins);
          end;
        end; // while RealPins > 0 do

        if (pcFetched <> nil) then pcFetched^ := Fetched;

        if (cPins = Fetched) then result := NOERROR else result := S_FALSE;
    except
        On E: Exception do
        begin
            OutputDebugString(PChar(
                '(TBCEnumPins.Next) Exception class name('   E.ClassName   ') message: '   E.Message
            ));

            raise;
        end;
    end;
end;
  

ОБНОВЛЕНИЕ: похоже, что код DSPACK хорош с технической точки зрения, но немного странен с точки зрения идиомы кодирования и структурирован таким образом, что он несовместим с проверкой диапазона. Значение NIL, поступающее через параметр ppPins «out», отображается в буфер назначения, который вызывающий объект передает TBCEnumPins.Next() в качестве параметра ppPins. Например, приведенный ниже код взят с этой страницы:

http://tanvon.wordpress.com/2008/09/07/enumerating-the-directshow-filter-pin/

На этой странице приведен следующий код, который взаимодействует с фильтром DirectShow для перечисления выводов фильтра:

 IEnumPins * pEP;
pSF->EnumPins(amp;pEP);
IPin * pOutPin;
while(pEP->Next(1,amp;pOutPin,0) == S_OK)
{
    PIN_DIRECTION pDir;
    pOutPin->QueryDirection(amp;pDir);
    if(pDir == PINDIR_OUTPUT)
        break;// success
    pOutPin->Release();
}
pEP->Release();
  

Сообщая методу Next(), сколько выводов нужно извлечь, код метода TBCEnumPins.Next() с его необычным приведением динамического массива оказывается безопасным, поскольку он будет копировать в параметр ppPins «out» только столько выводов, сколько было запрошено в функциях Next() «cPins»параметр. Пока вызывающий объект передает буфер назначения, который может содержать количество выводов, запрошенных в «cPins», все работает нормально (пока проверка диапазона отключена). Обратите внимание, что в этом случае переменная IPin с именем «outPin» является буфером назначения. Ошибка проверки диапазона возникает, если включена проверка диапазона, поскольку Delphi обрабатывает NIL как массив нулевой длины.

Ответ №1:

Я с подозрением отношусь к вашему объявлению ppPins параметра и вашим приведениям к TPointerDynArray и TIPinDynArray .

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

Я бы сделал это, как показано ниже. Это соответствует объявлениям, используемым в MSDN. Вам нужно немного использовать арифметику указателей, но я считаю, что это проще сделать, поскольку теперь ваш код будет легко соотносить с любыми примерами C , которые вы можете найти в Интернете.

 type
  PIPin = ^IPin;

function TBCEnumPins.Next(cPins: ULONG; ppPins: PIPin; pcFetched: PULONG): HRESULT;
begin
  ... 
  // this is the meat of the loop, assign to the output array, and increment
  Pointer(ppPins)^ := nil; // initialise the intf ref to avoid bogus _Release
  ppPins^ := Pin;
  inc(ppPins);
  ...
end;
  

Комментарии:

1. Привет @David Heffeman. Я бы объяснил код, но, как я уже сказал, это стандартный код, поставляемый с DSPACK, поэтому я не знаю причины его структуры. Обратите внимание, что «ошибка проверки диапазона» является известной ошибкой на форумах DSPACK (progdigy). Я попробую ваш код.

2. @Robert Я добавил одну строку кода. Я понял, что пропустил тот факт, что массив должен быть инициализирован нулем, чтобы избежать записи Delphi в вызовах _Release по неинициализированным ссылкам. Честно говоря, это, вероятно, проще всего с вызовом FillChar или ZeroMemory вне цикла.

3. Хеффман. Пожалуйста, посмотрите мой комментарий к Роману в его ответе о комбинации параметра «out» / NIL.

4. @Robert ppPins никогда не должен быть NULL . И я имею в виду ppPins в соответствии с MSDN, а не в вашем коде. Было бы намного проще, если бы вы ответили на мой вопрос о том, что есть на самом деле NULL . В любом случае, ясно, что ваш исходный код неверен, когда включена проверка диапазона. Я действительно считаю, что мой ответ объясняет все, о чем вы сообщили.

5. Хеффман. Как я уже сказал, это не мой код. Это часть базового класса DSPACK. Значения ppPins могут быть равны нулю, если это то, что оказывается в буфере назначения, когда вызывающий вызывает TBCEnumPins.Next() . Если значение равно нулю, то при включенной проверке диапазона оно обрабатывается как массив нулевой длины. Значение NIL — это то, что находится в начале буфера назначения, на который указывают ppPins . Принимая адрес параметра «out» (по ссылке), автор оригинала, по сути, указывает на начало буфера назначения, который получит выбранный pin-код. Смотрите Мое «обновление» к моему исходному сообщению.

Ответ №2:

IEnumPins::Next метод не должен вызываться с ppPins == NULL помощью . Таким образом, лучший способ обработки — это немедленное возвращение с E_POINTER кодом ошибки.

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

Комментарии:

1. Я действительно сомневаюсь, что ppPins действительно равен NULL. Если бы это было так, то имело бы место нарушение доступа, а не ошибка диапазона. Это также осложняется тем фактом, что объявление COM of ppPins является указателем на an IPin , но код в вопросе использует out параметр сомнительным образом.

2. Согласен. Говоря о проверке диапазона, похоже, что проблема может возникнуть, если вызывающий абонент запрашивает перечисление более 2 контактов одновременно, а функция определяется таким образом, что ppPins представляет собой одно значение, а не массив. Тем не менее, OP спросил об NULL аргументе, и вот мы идем. Я предположил, что вызывающий код не является внешним, и у OP может возникнуть проблема на стороне вызывающего абонента в его собственном коде.

3. @Дэвид Хеффернан. Вы уверены, что здесь обязательно будет запущен A / V? Delphi обрабатывает нулевой массив как нулевую длину, и если компилятор сначала обработал его как таковой, ошибка проверки диапазона будет вызвана до нарушения доступа, когда индекс применяется к пустому массиву, поскольку он имеет нулевую длину.

4. @RobertOschler Да, вы правы. Моя ошибка. Но вы должны принять мою точку зрения о неправильном использовании динамических массивов. Возможно, скопированный вами пример кода работает с отключением проверки диапазона.

5. Во фрагменте кода за ссылкой выше ppPins никогда не будет NULL , это всегда действительный указатель.