EInvalidCast с фиктивной функцией, возвращающей тип указателя

Я написал интерфейс, чтобы обернуть Windows Threadpool API, и многие из этих функций возвращают простой Pointer типы.

Сейчас я пишу тесты и хочу использовать инфраструктуру delphi-mocks для макета интерфейса оболочки.

Проблема в том, что TMock интерфейс настройки занимает TValue объект, чтобы указать возвращаемое значение по умолчанию для ложных функций, и я не могу найти способ, как сделать это правильно из доступных TValueфункции. Хотя я видел этоekPointerявляется действительнымTTypeKind значение.

Когда вызванная функция вызывается, я получаюEInvalidCastисключение из соответствующего вызова RTTI.
Это происходит конкретно, когда вызов RTTI пытается привести значение возвращаемого типа из неявногоTValueобъект.

Мой код выглядит примерно так1:

type
    PTP_POOL = Pointer;
    PTP_CLEANUP_GROUP = Pointer;

    IThreadPoolApi = interface(IInterface)
        {...}
        function CreateThreadPool() : PTP_POOL;
        function CreateThreadpoolCleanupGroup() : PTP_CLEANUP_GROUP;
        {...}
    end;

тестируемый класс

type
    TThreadPool = class(TInterfacedObject, IThreadPool)
        FTPApi : IThreadPoolApi;
        FPTPPool : PTP_POOL;
        FPTPCleanupGroup : PTP_CLEANUP_GROUP;
        {...}
    public
        constructor Create(iapi : IThreadPoolApi);
    end;

implementation
constructor TThreadPool.Create(iapi : IThreadPoolApi);
begin
    inherited Create();
    FTPApi := iapi;

    {**** Here I get a EInvalidCast exception when mocking the interface ****}
    FPTPPool := FTPApi.CreateThreadPool();
    if(not assigned(FPTPPool)) then
    begin
        {Raise exception}
        raise EThreadPoolError('Cannot create TP thread pool');
    end;

    {**** This case should be tested ****}
    FPTPCleanupGroup := FTPApi.CreateThreadpoolCleanupGroup();
    if(not assigned(FPTPPool)) then
    begin
         {Raise exception}
         raise EThreadPoolError('Cannot create TP cleanup group');
    end;

    {...}
end;

и тестирование

procedure ThreadPoolTest.TestFail_CreateThreadpoolCleanupGroup();
var
   apiMock : TMock<IThreadPoolApi>;
   testproc : TTestLocalMethod;
begin
   apiMock := TMock<IThreadPoolApi>Create();
   {**** Needed to reach the call of CreateThreadpoolCleanupGroup
    but EInvalidCast is raised ****}
   apiMock.Setup.WillReturnDefault('CreateThreadPool',PTP_POOL($FFFFFFFF));
   {**** The case to be tested ****}
   apiMock.Setup.WillExecute('CreateThreadpoolCleanupGroup',
       function (const args : TArray<TValue>; const ReturnType : TRttiType) 
       : TValue
       begin
           result := nil;
       end);

    testproc := 
       procedure() 
       var
           threadpool : IThreadPool;
       begin
           threadpool := TThreadPool.Create(apiMock);
       end;
    DUnitX.Assert.WillRaise(testproc,EThreadPoolError,
                            'Cannot create TP cleanup group');
end;

TL;DR;

Итак, вопрос:
Что мне для этого нужноTValueсоздан правильно, чтобы содержатьPTP_POOL тип указателя?


1)Это слишком много кода для настройки MCVE, поэтому я просто набросал его здесь, чтобы дать вам представление, см.{**** highlighted comments ****}

2 ответа

Решение

При назначении необработанного указателя на TValue, использовать TValue.From<T>() метод, например:

TValue.From<PTP_POOL>(nil);

Или, если вы хотите вернуть буквальное значение:

TValue.From<PTP_POOL>(PTP_POOL(value));

TValue не имеет неявного преобразования для необработанных указателей, но оно имеет неявное преобразование для TObject а также TClass указатели.

Если вы назначите нетипизированный нулевой указатель напрямую TValue это вызывает TValue.Implicit(TObject), который вернет TValue.Empty свойство, когда передается нулевой указатель.

Если вы назначаете типизированный TObject указатель прямо на TValue это вызывает TValue.Implicit(TClass), который возвращает TValue.Empty если указатель равен нулю, в противном случае он возвращает TValue типа TClass со значением указателя, даже если он на самом деле не указывает на допустимый тип класса.

Если вы передаете указатель на TValue.From<T>(), он возвращает TValue типа T со значением указателя, даже если это ноль.

Эта разница тонкая, но очень важная.

Что мне для этого нужно TValue создан правильно, чтобы содержать PTP_POOL тип указателя?

Похоже, @Remy уже ответил на мой вопрос в своем комментарии.

Чтобы это значение указателя содержалось в TValue экземпляр он должен быть создан с использованием общего TValue.From<T>() функция:

apiMock.Setup.WillReturnDefault('CreateThreadPool',
                                TValue.From<PTP_POOL>(PTP_POOL($FFFFFFFF)));

и немного кастинга.

Другие вопросы по тегам