#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
является указателем на anIPin
, но код в вопросе используетout
параметр сомнительным образом.2. Согласен. Говоря о проверке диапазона, похоже, что проблема может возникнуть, если вызывающий абонент запрашивает перечисление более 2 контактов одновременно, а функция определяется таким образом, что
ppPins
представляет собой одно значение, а не массив. Тем не менее, OP спросил обNULL
аргументе, и вот мы идем. Я предположил, что вызывающий код не является внешним, и у OP может возникнуть проблема на стороне вызывающего абонента в его собственном коде.3. @Дэвид Хеффернан. Вы уверены, что здесь обязательно будет запущен A / V? Delphi обрабатывает нулевой массив как нулевую длину, и если компилятор сначала обработал его как таковой, ошибка проверки диапазона будет вызвана до нарушения доступа, когда индекс применяется к пустому массиву, поскольку он имеет нулевую длину.
4. @RobertOschler Да, вы правы. Моя ошибка. Но вы должны принять мою точку зрения о неправильном использовании динамических массивов. Возможно, скопированный вами пример кода работает с отключением проверки диапазона.
5. Во фрагменте кода за ссылкой выше
ppPins
никогда не будетNULL
, это всегда действительный указатель.