views:

243

answers:

2

Hello.

I am thinking about elegant way to encapsulate WinAPI callbacks inside a class. Suppose I am making a class handling asynchronous I/O. All Windows callbacks should be stdcall functions, not class methods (I need to pass their addresses to ReadFileEx WinAPI function for example). So, I cannot just pass method addresses as a callback routines to WinAPI functions.

What is the most elegant way to encapsulate functionality of this type inside a class so that the class have events OnReadCompleted and OnWriteCompleted (I am using Delphi as a primary language, but I guess the situation must be the same in C++ because class methods are different from simple methods by the fact, that the first hidden parameter of them is this link. Of course this class is not a singleton and there can be many of them created by app at the same time.

What do you think would be the good way to implement this?

+3  A: 

I doubt this is in any way elegant but, IMO, the easiest is to convert the address of a method of a class to a procedure address and pass it to the winapi. Sure, it's a hack, but the VCL does the very same with classes.MakeObjectInstance, if only for a specific construct.. See this question for a source for this kind of implementaion and some other, more OO ways to handle the situation.

Sertac Akyuz
I wonder whether this approach will work with the 64bit delphi version in (far?) future (Proc calls are very different in 64). I really doubt it. But there is no way to tell now.
ChristianWimmer
A: 

You can use static keyword for that. But it's available only in new Delphi versions.

Like this:

type
  TMyThread = class
  private
    // ...

    class function ThreadProc(Param: Pointer): DWord; stdcall; static; // <- WinAPI call back

    function Execute: DWord; // <- actual callback
  public
    constructor Create;
    // ...
  end;

{ TMyThread }

constructor TMyThread.Create;
begin
  // ...
  FHandle := CreateThread(nil, 0, @ThreadProc, Self, 0, FID);
end;

class function TMyThread.ThreadProc(Param: Pointer): DWord;
begin
  Result := TMyThread(Param).Execute;
end;

function TMyThread.Execute: DWord;
begin
  MessageBox(0, 'Hello from thread', 'Information', MB_OK or MB_ICONINFORMATION);
  Result := 0;
end;

Here: ThreadProc is WinAPI callback routine. It requires to have some form of custom argument, where you can pass Self. It can not access instance members. That's why it's just a wrapper for real callback (Execute), which is part of class and can access its fields and methods.

Alexander
You can also get rid of the wrapper (ThreadProc), if you ensure that callback (Execute) will have the very same binary signature that required WinAPI callback. In the above example you can add "stdcall" to Execute and remove ThreadProc completely, passing @Execute to CreateThread. But it's a dangerous path.
Alexander
Thank you, but I mentioned, that my class is not a singleton and there can be a plenty of this class' instances in the application. So, static method solution is not so good. Unless may be I store somewhere a list of method pointers needs to be called depending on something passed to callback.
FractalizeR
@FractalizeR Ummm... why do you think that you can create only one instance? You can use any number of instances you want. Please, study my example more carefully. The exact instance is saved.
Alexander
@Alexander - Not all api callbacks employ a "pass-back" parameter. How would you adapt this approach, say, for instance for a CBTProc (SetWindowsHookEx) callback?
Sertac Akyuz
@Sertac Akyuz - @FractalizeR asked a *specific* question about ReadFileEx - and you CAN pass Self to ReadFileEx's callback (via hEvent). I've answered on that specific question only. If you ask *different* question (say, how do I do that with callback without custom parameter) - then this answer will not be applicable (obviosly).
Alexander
Remark: hEvent field is not used in ReadFileEx/WriteFileEx. You can use it to pass self to completion routine. See Jeffrey Richter's "Programming Server-Side applications for Microsoft Windows 2000".
Alexander
Just for comprehension.The reserved word "static" in combination with "class" generates a method without invisible "Self" Parameter as the first parameter. So unlike to a simple "class" method we cannot access Self in the method itself. And without the "static" word, winapi would call a method with incorrect parameters. Correct?
ChristianWimmer
@ChristianWimmer yes, that's correct.
Alexander