View Full Version : Message handler callback routine

07-04-2005, 04:58 PM
I already posted this message on the gamedev.net forums, but I'm gonna try my luck here too. :)

I downloaded the Clootie's DirectX 9 header conversions and wanted to try out the DirectPlay stuff, but I ran into problems with my message handling callback routine.

Here's my callback routine:

function TDirectPlayServer.DPMessageHandler(pvUserContext: Pointer; dwMessageType: DWORD; pMessage: Pointer): HRESULT; stdcall;
Result := S_OK;

I try to use it like this (like I've done with the Windows API callbacks before):

hr := DPServer.Initialize(NIL, @DPMessageHandler, 0);

The problems is that my code won't compile, the error message that I get is "Variable required".

The initialize function is declared like this:

function Initialize(pvUserContext: Pointer; pfn: TFNDPNMessageHandler; dwFlags: DWORD): HResult; stdcall;

And the callback is declared like this:

TFNDPNMessageHandler = function (pvUserContext: Pointer; dwMessageType: DWORD; pMessage: Pointer): HRESULT; stdcall;
{$NODEFINE TFNDPNMessageHandler}

I can easily set the handler if I declare it outside the class like this:

function DirectPlayMessageHandler(pvUserContext: Pointer; dwMessageType: DWORD; pMessage: Pointer): HRESULT; stdcall;
Result := S_OK;

But it's kinda useless, because I can't access the private methods/variables on my server class. :)

The solution is simple, but I just can't grasp it right now. :P

Any help greatly appreciated.

07-04-2005, 07:25 PM
what i think you could do is to make DPMessageHandler a nonclass function.

when you then call initialize then do it like this:
DPServer.Initialize(@self, @DPMessageHandler, 0);

in your callback function you can then use the servers properties by writing: name_of_the_server_class(pvUserContext).values_in_ the_server

this was how i did a little hack with ode

07-04-2005, 09:43 PM
What JSoftware said. You must declare the message handler as a normal function outside of the class. When you call Initialize, pass Self as the pvUserContext parameter. In the message handler, typecast the pvUserContext parameter to TDirectPlayServer to access members of the class.

This is how I have done the same thing in my own DirectPlay classes.

08-04-2005, 05:24 PM
Thanks guys, now it works.

I got similar answer from Clootie too. :)

Still having trouble with the client part tho, can't get it to connect to my server...

I keep getting "DPNERR_INVALIDHOSTADDRESS" when making the connection.

pHostName: PWideChar;
pw: array [0..127] of WideChar;
pHostName := StringToWideChar(pServerAddress, pw, Length(pServerAddress) + 1);
hr := DPHostAddress.AddComponent(DPNA_KEY_HOSTNAME, //pwszName
pHostName, //lpvData
(Length(pServerAddress) + 1) * 2, //dwDataSize in bytes

Is the data size wrong or what? But I don't get any kind of errors here, only later when trying to make the connection.
EDIT: pServerAddress is a String property.

DPDeviceAddress: IDirectPlay8Address;
DPClient: IDirectPlay8Client;
DPHostAddress: IDirectPlay8Address;
hr := DPClient.Connect(DPAppDesc, // pdnAppDesc
DPHostAddress, // pHostAddr
DPDeviceAddress, // pDeviceInfo
NIL, // pdnSecurity
NIL, // pdnCredentials
NIL, 0, // pvUserConnectData/Size
NIL, // pvAsyncContext
NIL, // pvAsyncHandle

Any idea what's wrong this time? :P

09-04-2005, 08:36 AM
This is how I create the address object.

DKNetCheck() is my own function that checks for errors and raises an exception if anything goes wrong.

procedure TDKNetClient.CreateHostAddress(out Address: IDirectPlay8Address);
{ Create the address object }
if CoCreateInstance&#40;CLSID_DirectPlay8Address, nil, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Address, Address&#41; <> S_OK then
raise EDKNetClientError.Create&#40;'Could not create DirectPlay8Address COM object'&#41;;

procedure TDKNetClientIP.CreateHostAddress&#40;out Address&#58; IDirectPlay8Address&#41;;

&#123; Set the service provider for the address object &#125;

&#123; Set the port component of the address &#125;
if FHostPort > 0 then
DKNetCheck&#40;Address.AddComponent&#40;DPNA_KEY_PORT, @FHostPort, SizeOf&#40;FHostPort&#41;, DPNA_DATATYPE_DWORD&#41;&#41;;

&#123; Set the hostname component of the address &#125;
if Length&#40;FHostName&#41; > 0 then
DKNetCheck&#40;Address.AddComponent&#40;DPNA_KEY_HOSTNAME, PWideChar&#40;FHostName&#41;, 2 * &#40;Length&#40;FHostName&#41; + 1&#41;, DPNA_DATATYPE_STRING&#41;&#41;;

FHostPort is declared as an integer. FHostName is declared as WideString. Here's a neat trick:

TDKNetClientIP = class&#40;TDKNetClient&#41;
FHostName&#58; WideString;
function GetHostName&#58; String;
procedure SetHostName&#40;const Value&#58; String&#41;;
property HostName&#58; String read GetHostName write SetHostName;

function TDKNetClientIP.GetHostName&#58; String;
Result &#58;= FHostName;

procedure TDKNetClientIP.SetHostName&#40;const Value&#58; String&#41;;
FHostName &#58;= Value;

Now you can access the HostName property as a normal string in your application (read and write as a standard string), but to DirectPlay it is a WideString (so you can typecast directly to PWideChar).

Hint: if you have the DirectX SDK installed, change to the debug version of DirectPlay (in the DirectX Control Panel applet) and set the debug output fairly high. Then open the Event Log from the View/Debug Windows menu (if you have Delphi Professional or higher). If not, go to http://www.sysinternals.com and get DebugView. It is a small utility that allows you to see all the debug information that an application can spit out. Remember to switch back to the retail version of DirectPlay after debugging though! :)

Hope that helps.