Wednesday, June 30, 2004

Cancelling a SOAP function call

A newsgroup user asked "Is it possible to cancel waiting for the webservice to return the result of a function?" To be extremely brief: Yes.

Less succinctly, the answer lies in the ingenious use of threads. Firstly, if you're going to have a single threaded application, you can't stop a function call while it's running. It's like you're reading the paper in your car, stopped at a traffic light, alone; and the light's just turned green. You have no idea because well, there's no one around to tell you. (for the record, I've never done this)

So in order to cancel a function call, you need to put that function call in a separate thread, and in the main UI thread (where your form is) - implement a cancel button that will somehow terminate the function call. How does one do that? The SOAP call that you make actually waits for the Internet call to return - and you have no way to tell this code to stop gracefully.

Now, a HTTPRio uses WinInet by default, and you get the handle to the Request in the HttpWebNode.OnBeforePost event. You can call InternetCloseHandle with this handle anytime - this will raise an exception in the thread - with a "The operation has been canceled...." message. The thread will terminate, and your function call get cancelled.

Note: the server call will continue to run on the server till termination. To actually terminate whatever's happening at the server you'll need to use some other logic (like a server thread that can be terminated from another SOAP call or some kind of constant check on the request validity).

I've got a test application here that demonstrates what I'm talking about. Download it at http://www.agnisoft.com/downloads/SoapCancelWait.zip. And as usual, tell me what you think!

Thursday, June 24, 2004

Launched from a shortcut?

If you would EVER need a way to check if your application was launched from a shortcut, rather than accessing the EXE itself, this article should help.

Now why you would want to do this is, frankly, beyond my imagination. The only thing I can think of is that on XP/2000, you can choose to run a program with different credentials through a shortcut. Even here you can't specify the credentials - the user gets to choose them (and enter a username/password) And the user can actually choose the *same* credentials as the logged on user, even with this flag set.

This is the programming equivalent of saying "There may be someone else on the computer right now, or maybe not.". Which might not be the lifesaver we're all looking for. (And no, we don't get them in India. The famous mints here are "polo" and "minto", if you're curious. Which you probably aren't, but I'm bored to death of the programming stuff) If anyone knows what scenerio such code might be useful in, I'd really appreciate a hint...

Tuesday, June 22, 2004

The FileZilla FTP Client

There are tons of FTP clients out there, I know. But if you will, check out FileZilla. Awesome client...I used it to do a ton of uploads/downloads - very impressive in terms of speed and features.

They're all blogging!

TeamB and Borland have joined the blogging fray. (This is old hat, I know). Oh btw, you can check my blog on the TeamB blog server.

Introducing "AltInvoke"

I've started getting all sorts of ideas now with this Microsoft page giving you alternatives to common PInvoke calls. My first set: Include it in my PBroker tool - now you have a "Get Alternatives" button that gives you...well, alternatives for your PInvoke calls. The first screen shot: (Click for a larger pic)



And you can download PBroker here.

Thursday, June 17, 2004

.NET equivalents of Unmanaged calls

You have to check this site if you're even thinking of using PInvoke. Read:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/win32map.asp

It contains the equivalents of a number of Win32 functions. Now that gets me thinking....

Wednesday, June 16, 2004

Shenoy In BusinessWorld

Seems I made it into a news article on Business World (India). But the Nokia 7650 was my old phone. My new phone is a Sony Ericsson P900.

And the Sony even runs .NET apps, through something called Crossfire. Haven't tried it yet, but this definitely looks interesting. Of course, the phone is Symbian OS based and supports MIDP and J2ME apps too. Gadgets rule!

Tuesday, June 15, 2004

Web App Debugger problem

Lachlan was wondering if I had a workaround for:
http://qc.borland.com/wc/wc.exe/details?ReportID=7738

Ok, that problem really sucks. The problem is that a lot of the Delphi code actually depends on the prog id being part of the path info. Here's the fix:

Copy "SockHTTP" from the Delphi Source\Internet folder into your project folder, and most importantly, Add it to your project. Then, change the TSockWebRequest.GetStringVariable to:

function TSockWebRequest.GetStringVariable(
Index: Integer): string;
var iPos : integer;
begin
Result := FIntf.GetStringVariable(Index);
if Index=4 then // pathinfo
begin
result := Copy(Result, 2, Length(Result));
iPos := Pos('/', Result);
if iPos>0 then
Result := Copy(Result, iPos,
Length(Result))
else
Result := '/';
end;
end;


Also change TSockWebRequest.GetInternalPathInfo to: (You'll need to comment out a few lines)

function
TSockWebRequest.GetInternalPathInfo: string;
var
P: Integer;
begin
if UsingStub then
begin
Result := PathInfo;
Assert(Length(Result) > 0, 'Unexpected length');

(* COMMENTED BY DEEPAK

Assert(Result[1] = '/', 'Unexpected value');
Delete(Result, 1, 1);
// Remove first section of path. This is a reference
// to the progid.
P := Pos('/', Result); {do not localize}
if P > 0 then
Delete(Result, 1, P-1)
else
Result := '/';

END COMMENTED BY DEEPAK *)

end
else
Result := inherited GetInternalPathInfo;
end;


Remember, Copy the SockHTTP.pas to your project folder, don't edit it in the Borland source folder!

Singletons and more...

I just read Nick's excellent post on Singleton datamodules explaining how he accessed a datamodule using a global function.

Which got me thinking, a fairly rare occurance these days. What went through my mind was: What if someone, ignorantly or maliciously, did a

var MyModule : TMyDatamodule;
begin
MyModule := TMyDataModule.Create(nil);
end;


I know what you're thinking. "You're supposed to use the global function!!!!". Er...this is a big team, someone forgot to document that, I was trying to figure out what would happen, I don't really like you...tons of excuses! And you'll only figure out something was wrong when you did a code review, or a synchronization or resource conflict occured...and probably have ZERO control over it if you were writing a library (like Borland is with the "Printer" variable).

Now, what could be better than to allow users to use the ".Create" convention, but return them a singleton instance on subsequent calls? It is possible. And no hacks.

Here's the scoop: Override the NewInstance function. This is a class (static for you C++ folks) function that gets called during object construction. Here's an example. I've used a form, with a label on it. Each unique instance gets a "Number" - so we can track what's going where. In this class, TForm2, (laziness written all over the name, of course):


var UseSingleTon : boolean = False;

implementation

var
iNUM : integer = 1;
Form2: TForm2 = nil;

class function TForm2.NewInstance: TObject;
begin
// don't call inherited if we already have an instance
if UseSingleTon then
if Assigned(Form2) then
begin
Result := Form2;
Exit;
end;

Result := inherited NewInstance;
end;


UseSingleton is a global variable that's in there only for demonstration. You can change it or set it to true forever.

You calling code is:

procedure TForm1.Button1Click(Sender: TObject);
var frm : Tform2;
begin
frm := Tform2.Create( self );
frm.Show;
end;

Will this work? Actually, No. It's more than just that. There are two things you want to avoid:

a) Having the base class constructor (TForm.Create) run on every subsequent call to TForm2.Create. It should run just once!
b) Even the FormCreate call should run only once.

This isn't very difficult, just needs a little more searching. Here's what I did:

procedure TForm2.AfterConstruction;
begin
if UseSingleTon and Assigned(Form2) then Exit;
inherited;
Form2 := Self;
end;

constructor TForm2.Create(AOwner: TComponent);
begin
if UseSingleTon and Assigned(Form2) then Exit;
inherited;
end;


Simply put, I'm avoiding calling the base constructors or construction based functions if the object is alive.

Next, we have to ensure that destruction is all right too - since we're going to use .Create to create the instance, we'll expect to use .Free to free it. A singleton should only be freed when the program shuts down, probably in a .Free that you have set up in a finalization section. For this I have included some "reference counting" in the code. But I'm way too lazy to type it all. So, download the code and check it out.

Web App Debugger

A paper I'd written for Borcon 2002 is finally ready on my company's web site. It's about how to use the Web App Debugger in Delphi. Read more at http://www.agnisoft.com/white_papers/wad/default.asp.

(And I finally updated the phone numbers on the Contact page)

Thursday, June 10, 2004

Search for Delphi files in Windows Explorer

I just read Steve Trefethen's page. Awesome site - Steve has some really cool stuff here! Check out his Tips and Tricks section where he tells you how you can use Windows Explorer to search .PAS files. And .DFM files, I guess. Thanks for that, Steve!

PInvoke and Delphi

Adam Nathan has written a PInvoke.Net Webservice - a webservice that gives you C# and VB PInvoke definitions for Win32 functions. Actually, for any DLL function that you used to access in Win32. Obviously "Header conversion" is a painful job, and we've all seen that in Delphi.

I think this is a wonderful idea. A community maintained Wiki that contains definitions of the functions you might need, and you can add your own. I just hope no one abuses this system.

There's still one tiny little problem. Since C# is so ubiquitous in the .NET world, there were way too many functions with just C# definitions, not even VB. And ZERO in Delphi, though the site is fairly language agnostic. Now this gets me thinking: John Kaster has written the BabelCode webservice using Corbin's CodeDOM engine (I guess :) ). This converts C# code to Delphi (for .NET). And it's also a webservice serving SOAP requests.

My next thought was: make an application that calls the PInvoke.net webservice, takes the C# code from there, calls the BabelCode webservice and voila, we have the equivalent Delphi PInvoke declaration. (Note: Do not try this at home) Being me, I couldn't resist this - and being me, I had to write it in Delphi 7. It's all done and all that - available at http://www.agnisoft.com/downloads/pbroker.zip. Check it out, tell me what you think.

Wednesday, June 09, 2004

Virtual PC and Windows ME

I tried to install Windows ME on VPC 2004 today - Virtual PC, btw, is an awesome tool by Microsoft, giving you a virtual machine on which you can install other operating systems. So, on Windows XP, You can set up multiple VMs running other versions of Windows. Being short of test systems, I tried installing WinME from my MSDN CD, on my XP pro. The CD wasn't bootable!

So the only way out was to try and find a DOS boot disk somehow and let Virtual PC boot into DOS, and then install WinME. But how the heck do I get a boot disk? I haven't used a floppy disk for ages! I found out that VPC allows you to "capture" a floppy disk image from a VFD file, and even boot from it. Good. Now a quick google search gave me this amazing blog.

You can download the Windows ME boot disk directly. Or, choose the boot disks for Windows XP Professional, Windows 98 SE, or any of the others mentioned.

I'm just putting this in my blog for reference...

Tuesday, June 08, 2004

Instances and Interfaces

I found this thread - and therefore a very interesting piece of code. It gets you the "implementing object" from an interface pointer. Here's the function (all credit to Hallvard here):


function GetImplementingObject(const I: IInterface): TObject;
const
AddByte = $04244483; // opcode : ADD DWORD PTR [ESP+4],
// Shortint
AddLong = $04244481; // opcode : ADD DWORD PTR [ESP+4],
// Longint
type
PAdjustSelfThunk = ^TAdjustSelfThunk;
TAdjustSelfThunk = packed record
case AddInstruction: longint of
AddByte : (AdjustmentByte: shortint);
AddLong : (AdjustmentLong: longint);
end;
PInterfaceMT = ^TInterfaceMT;
TInterfaceMT = packed record
QueryInterfaceThunk: PAdjustSelfThunk;
end;
TInterfaceRef = ^PInterfaceMT;
var
QueryInterfaceThunk: PAdjustSelfThunk;
begin
Result := Pointer(I);
if Assigned(Result) then
try
QueryInterfaceThunk :=
TInterfaceRef(I)^.QueryInterfaceThunk;
case QueryInterfaceThunk.AddInstruction of
AddByte: Inc(PChar(Result),
QueryInterfaceThunk.AdjustmentByte);
AddLong: Inc(PChar(Result),
QueryInterfaceThunk.AdjustmentLong);
else Result := nil;
end;
except
Result := nil;
end;
end;


The idea here is that if you have code like this:


IMyInterface = interface
[SOMEGUID]
procedure DoSomething;
end;

TMyClass = class( TInterfacedObject, IMyInterface)
protected
procedure DoSomething;
public
//note: This proc is not in interface
procedure DoSomethingElse;
end;

...
var
MyInterface: IMyInterface;
MyClass : TMyClass;
begin
MyInterface := TMyClass.Create;
MyInterface.DoSomething;

// This will not work
TMyClass(MyInterface).DoSomethingElse; // crash boom bang!

// This is slightly better
MyClass := TMyClass(GetImplementingObject(MyInterface));
MyClass.DoSomethingElse;
end;


Now this may or may not work in future versions of Delphi, but this is the only option if you can't modify the code of the interface or the implementation. If you can, here's a better option:


IMyInterface = interface
[SOMEGUID]
procedure DoSomething;
end;

TMyClass = class;

IMyClassAccess = interface
[SOMEGUID2]
function GetMyClass : TMyClass;
end;

TMyClass = class( TInterfacedObject, IMyInterface,
IMyClassAccess)
protected
procedure DoSomething;
function GetMyClass : TMyClass ; // Result := Self;
public
// note: This proc is not in interface
procedure DoSomethingElse;
end;

var
MyInterface: IMyInterface;
MyClass : TMyClass;
obj : IMyClassAccess;
begin
MyInterface := TMyClass.Create;
MyInterface.DoSomething;

// This is slightly better
MyInterface.QueryInterface( SOMEGUID2, Obj);
MyClass := Obj.GetMyClass;
MyClass.DoSomethingElse;
end;


Better option, but requires you to modify the implementation class. All thanks to Hallvard...

No longer an unknown blog

Anders found me! :) (Thanks Anders!)

Monday, June 07, 2004

Delegation, Active Directory and WMI

Note: Do not start "researching" anything on Friday afternoon.

I can explain. I have this project where I'm on Computer A, and I need to run an executable on Computer B. (This executable is one of them command line utilities with no UI) The problem really is that this little executable is on Computer C...and A, B and C are all on my little network here.

Since everyone was running Windows 2000, I assumed we could use WMI to connect from A to B as an Administrative user. Then, I could run this executable from C by creating a "Win32_process" on B (with \\C\something.exe as the executable filename) Not that simple, it turned out.

The problem is that when you run code on Computer A which connects to Computer B using WMI, you need to pass a set of credentials, usually a domain admin username/password. When you request B to access a third computer (C), the code runs in the context of the "LocalSystem" user in B, NOT the username/password you supplied! And LocalSystem, by default, does not get access to network resources.

So what do you do? One of two things:
1) This URL explains the architecture of Delegation. Basically you allow Computer B, a domain computer, access to all network resources. The caveats? Computer B needs to be in the same domain, and needs to be registered in Active Directory. Also, your friendly neighbourhood Network Administrator is going to kill you.

2) You can "copy" the executable file to the remote computer and then execute it. (the LocalSystem has access to Local System resources. "Duh" - Ed.) I found another URL at Microsoft that shows you how to do all of this. Concept? Map a drive, z:, on to the remote computer (B) with the domain admin username/password, and then use the CopyFile function to copy from "\\C\somefolder\myexe.exe" to "z:\temp\myexe.exe", for example. You might wonder, what if nothing's shared on the remote computer - well, there's always an "ADMIN$" share or "C$" share that you can use. Or so I think.

Coming back to the note above. I had to stay late figuring all of this out, because I hate coming in on Saturdays. So, once again, DO NOT start something new, at work, on Friday afternoon.

Friday, June 04, 2004

The SOAP Insanity: "expecting text/xml, received text/html"

Welcome to my new blog! Here's where I'll write about my experiences with Programming.

First, the most often asked question about Delphi SOAP: What's the "expecting text/xml, received text/html" error?

The Delphi Web Service wizard, when generating the code for an ISAPI application, generates this code in the .dpr file:

uses
ActiveX,
ComObj,
WebBroker,
ISAPIThreadPool,
ISAPIApp,
Unit1 in 'Unit1.pas' {WebModule1: TWebModule};

You need to Interchange the positions of ISAPIThreadPool and ISAPIApp in the .dpr file. The reason is that the initialization section of the ISAPIThreadPool unit needs to run after the ISAPIApp unit's initialization section, so the thread pool is initialized properly.

You don't need to do anything for CGI, Apache DSO or Web App Debugger executables. This fix is specific to Delphi 7 generated ISAPI SOAP applications.

Note: The Delphi 6 Thread Pool unit does nothing. Throw it away.