2016-09-01 1 views
11

Это упрощенный сценарий, в Delphi 7:Можно ли использовать вложенную процедуру метода в качестве обратного вызова winapi?

procedure TMyClass.InternalGetData; 
var 
    pRequest: HINTERNET; 

    /// nested Callback 
    procedure HTTPOpenRequestCallback(hInet: HINTERNET; Context: PDWORD; Status: DWORD; pInformation: Pointer; InfoLength: DWORD); stdcall; 
    begin 
     // [...] make something with pRequest 
    end; 

begin 
    pRequest := HTTPOpenRequest(...); 
    // [...] 
    if (InternetSetStatusCallback(pRequest, @HTTPOpenRequestCallback) = PFNInternetStatusCallback(INTERNET_INVALID_STATUS_CALLBACK)) then 
    raise Exception.Create('InternetSetStatusCallback failed'); 
    // [...] 
end; 

Все это кажется, работает хорошо, но это действительно правильно и безопасно? Я бы хотел, чтобы он был инкапсулирован таким образом, потому что он более читабельный и чистый. Мое сомнение заключается в том, является ли вложенная процедура простой, нормальной процедурой или нет, так что она может иметь свое собственное соглашение о вызове (stdcall) и безопасно ссылаться на локальные переменные внешнего метода (pRequest).

спасибо.

+0

Я бы даже не сделал этого, не используя «внешние» переменные, но доступ к 'pRequest' кажется попрошайничеством за ошибки. :-) –

ответ

13

Реализация локальных функций в 32-битных компиляторах Delphi для Windows означает, что такой код работает так, как вы планируете. При условии, что вы не ссылаетесь ни на что из функции включения. Вы не можете ссылаться на локальные переменные, включая ссылку Self. Ваш комментарий предполагает, что вы хотите обратиться к pRequest, локальной переменной. Вам придется воздержаться от этого по причинам, описанным выше.

Однако, даже при соблюдении этих правил, он работает только из-за детализации реализации. Это явно указано как незаконные в documentation:

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

Если вы когда-нибудь возьмете свой код на другой платформе, такой как 64-битная Windows, тогда он потерпит неудачу. Этот вопрос более подробно рассмотрен здесь: Why cannot take address to a nested local function in 64 bit Delphi?

Мой совет заключается в том, что вы не используете локальные функции таким образом. Выполнение этого просто устанавливает ловушки для себя, что вы попадете в какое-то время в будущем.

Я бы также посоветовал использовать строго типизированные объявления для функций обратного вызова, чтобы компилятор мог проверить, что ваш обратный вызов имеет правильную подпись. Это требует исправления любой функции Win32 API из-за неактивных объявлений Embarcadero с использованием нетипизированных указателей. Вы также захотите отказаться от использования @, чтобы получить указатели на функции, и пусть компилятор работает на вас.

6

Method Pointers документация советует против этой практики:

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

Поведение не определено.

+0

спасибо. Я принял ответ Дэвида только потому, что он более полный, но я понял. Я прочитал документацию, но не понял, что «процедурные ценности» были моим делом. – yankee

+0

@yankee: afaik, это не так. Ответ Дэвида на то, чтобы понять, как компилятор влияет на встроенную процедуру, можно использовать как обратный вызов * (или непригодный для использования) *, но это imo полностью отличается от процедурных значений. –

+3

Нет, это то, что означает документация, @Lieven. Он использует термины * процедурные типы *, чтобы покрывать указатели на обычные подпрограммы, указатели на методы и ссылки на методы.Значение представляет собой экземпляр типа, поэтому процедурное значение является экземпляром процедурного типа. Это включает дело Янки. –