Thursday, November 16, 2006

Setting timeouts in Delphi SOAP

If you want to timeout your webservice call - either connecting, sending or receiving, you may be tempted to use the HttpRio.HttpWebNode properties: ConnectTimeout, SendTimeout and ReceiveTimeout. You will then face this error:

"The data area passed to a system call is too small. - URL:{some url here} - SOAPAction:{some other text here}"

The problem here is that calls made to InternetSetOption (in SOAPHTTPTrans.pas in the THTTPReqResp.Send procedure) is throwing an error. This is weird because although it returns an error code it does exactly what you want! There are calls to check the return code in the above file, and those checks fail.

Well, I'm not going to ask you to change the Borland source code this time. There's a way around it.

The way for you to do this is:
1) Do not set the ReceiveTimeout or the ConnectTimeout or other timeout properties of HTTPRio.HTTPWebNode.

2) Handle HTTPRio.HttpWebNode.BeforePost event and do this:

procedure TForm2.HTTPRIO1HTTPWebNode1BeforePost(
const HTTPReqResp: THTTPReqResp; Data: Pointer);
var TimeOut : integer;
begin
TimeOut := 2000; // in milleseconds.
InternetSetOption(Data,
INTERNET_OPTION_RECEIVE_TIMEOUT,
Pointer(@TimeOut),
SizeOf(TimeOut));
end;


(Do not check the return code of InternetSetOption above. It returns an error although everything's fine)

Note: This only works for INTERNET_OPTION_RECEIVE_TIMEOUT. The connect timeout (INTERNET_OPTION_CONNECT_TIMEOUT) and Send Timeout (INTERNET_OPTION_SEND_TIMEOUT) are broken with WinInet in synchronous mode (as HTTPRio uses) - check this Microsoft Support article. That means if you want to configure Send or Connect timeouts, you must use a different thread or use WinInet asynchronously (which is not at all an easy thing with HttpRio).

Wednesday, October 18, 2006

Showing Download Progress with .NET webservice clients

Let's say you were interested in displaying a progress bar in your .NET application form while a BIG SOAP message was coming down the line. There's an MSDN Article by Matt Powell explaining how you can do exactly that.

I managed to finally get it to twist and turn and
actually work with my Delphi (Win32) "Showing SOAP Download progress" article code.

The Microsoft article expects your client application to already know the transfer size, which honestly is not usually available beforehand. You would expect that the size of the content would vary and your progress bar should work for all transfer sizes.

But then I see why Matt has done that. You don't get any knowledge of the transfer size in your SoapExtension class (which gets you a non-seekable stream meaning you can't get Stream.Length from it)

So how do you get to the length then? Response.ContentLength is your
answer; since the data you are deserializing is the entire Response stream. But where can you see the Response that is actually received?

Answer: NOWHERE.

I had to use Reflector to back up the call stack a little bit and find a reasonable place, and that's another overridden method in the original article. Heck, let me not bore you with all of this. Here's the entire Extension code in one class:

namespace DotNetProgress
{
internal class ProgressClient :
DotNetProgress.localhost.IIDownloaderservice
{
public ProgressBar Progress;
public int TransferSize;
public Form1.UpdateDelegate ProgressDelegate;
public WebResponse _Response = null;

protected override WebResponse GetWebResponse(WebRequest
request)
{
_Response = base.GetWebResponse(request);
return _Response;
}
}

public class ProgressExtension : SoapExtension
{
// Holds the original stream
private Stream m_oldStream;
// The new stream
private Stream m_newStream;
// The buffer for reading from the old stream
// and writing to the new stream
private byte[] m_bufferIn;
// The progress bar we will be incrementing
private ProgressBar m_Progress;
// The size of each read
private int m_readSize;
private int m_totalSize;
// The delegate we will invoke for updating the
// progress bar.
private Form1.UpdateDelegate m_progressDelegate;
// Used to keep track of which stream we are trying
// to chain into
private bool m_isAfterSerialization;
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.AfterSerialize:
// To let us know that the next ChainStream call
// will let us hook in where we want.
m_isAfterSerialization = true;
break;
case SoapMessageStage.BeforeDeserialize:
// This is where we stream through the data
SoapClientMessage clientMessage
= (SoapClientMessage)message;
if (clientMessage.Client is ProgressClient)
{
ProgressClient proxy
= (ProgressClient)clientMessage.Client;
m_Progress = proxy.Progress;
// Read 1/100th of the request at a time.
// This will give the progress bar 100
// notifications.
//m_readSize = proxy.TransferSize / 100;
m_readSize = 8192;
m_totalSize = ((int)
proxy._Response.ContentLength) / 100;
m_progressDelegate = proxy.ProgressDelegate;
}
int CurRead = 0;
while (true)
{
try
{
int bytesRead
= m_oldStream.Read(m_bufferIn,
0,
m_readSize);
if (bytesRead == 0)
{
// end of message...rewind the
// memory stream so it is ready
// to be read during deserial.
m_newStream.Seek(0,
System.IO.SeekOrigin.Begin);
return;
}

m_newStream.Write(m_bufferIn,
0,
bytesRead);

// Update the progress bar
CurRead += bytesRead;
m_Progress.Invoke(m_progressDelegate, new
object[] { CurRead, m_totalSize });
}
catch
{
// rewind the memory stream
m_newStream.Seek(0,
System.IO.SeekOrigin.Begin);
return;
}
}
}
}

public override Stream ChainStream(Stream stream)
{
if (m_isAfterSerialization)
{
m_oldStream = stream;
m_newStream = new MemoryStream();
m_bufferIn = new Byte[8192];
return m_newStream;
}
return stream;
}
// We don't have an initializer to be shared across streams
public override object GetInitializer(Type serviceType)
{
return null;
}

public override object GetInitializer(
LogicalMethodInfo methodInfo,
SoapExtensionAttribute attribute)
{
return null;
}

public override void Initialize(object initializer)
{ m_isAfterSerialization = false; }
}
}

and in the Main Form, where I have a button and progress bar:


private void btnDownload_Click(
object sender, EventArgs e)
{
ProgressClient srv = new ProgressClient();
srv.Progress = this.progressBar1;
srv.TransferSize = 8192 * 100;
srv.ProgressDelegate = new
UpdateDelegate(ProgressBarUpdate);
srv.DownloadFile(edtFileName.Text);
}

public delegate void UpdateDelegate(
int CurRead, int TotalSize);

private void ProgressBarUpdate(
int CurRead, int TotalSize)
{
progressBar1.Value = CurRead / TotalSize;
Application.DoEvents();
}


That works for responses of all sizes, and works with the Delphi Win32 server I'd created in the earlier mentioned article.

Note: If you're using the Delphi server code, you'll have to comment out the Delphi TSoapAttachment based code, because MIME attachments aren't supported by .NET. Commenting it out in the interface should work just fine.

Friday, September 22, 2006

SQL Server: How to get the server's IP Address using T-SQL

Someone asked this on a newsgroup, and my answer: If you have admin rights on the server, you can use xp_cmdshell to run a command line application, grab the results and parse them.
CREATE TABLE #temp1
(t varchar(3000))

insert into #temp1
exec xp_cmdshell 'ipconfig'
DECLARE @t1 varchar(300)

-- subject to localisation
SET @t1 = (SELECT top 1 t from #temp1
where t like '%IP Address%' order by t DESC)

declare @len int
set @Len = CHARINDEX(':', @t1)

SELECT TOP 1 LTRIM(RTRIM(SUBSTRING(@t1, @Len+1, LEN(@T1)))) as T

drop table #temp1

Monday, August 21, 2006

Keep a "Non Modal" window active when a modal window is being shown

Recently, I was exploring writing an ADO SQL logging tool (sorta like BDE monitor) - more details on that later - which would show SQL statements on the screen as they ran. All was well until I figured I needed to be able to see my SQL logging form even when a Modal window was being displayed. TForm.ShowModal() disables all other windows in the current thread, so my SQL logging form is there in the background - but that's not much use because I don't want to close my modal form to get to the SQL log.

So is there a way to have another active while a Modal form is being displayed?

The answer is yes. Going through the Delphi source code in Forms.pas, in TForm.ShowModal() a call to "DisableThreadWindows" is made to ensure that all non modal forms are inactive, and when the modal form is closed "EnableThreadWindows" is called. What these do is simply disable all other windows and then re-enable them.

What you can do is:
1) Handle the WM_ENABLE message that is sent to your form to set the enabled state.
2) Use "PostMessage" to post a custom message (say WM_REENABLE) back to your form.
3) Handle the WM_REENABLE custom message and in the handler, Enable your window.

So in your Delphi form create a form like so:

type
TForm2 = class(TForm)
...
procedure WMEnable(var Message: TWMEnable); message WM_ENABLE;
...

procedure TForm2.WMEnable(var Message: TWMEnable);
begin
if not Message.Enabled then
begin
PostMessage(Self.Handle, WM_REENABLE, 1, 0);
end;
end;


where WM_REENABLE is defined earlier as:

const
WM_REENABLE = WM_USER + $100;


Then add a WM_REENABLE handler like this:
type
TForm2 = class( TForm )
...
procedure WMReenable(var Message: TMessage); message WM_REENABLE;
...
procedure TForm2.WMReenable(var Message: TMessage);
begin
EnableWindow(Self.Handle, True);
end;


Now your form will be active even if a modal window is shown.

Note: I have tried the following and they do not work:
1) Calling EnableWindow() in the WM_ENABLE handler.
2) Posting a WM_ENABLE message in the WM_ENABLE handler.
3) Trying to change Window styles of my window to Stay on Top.

Note#2: Do not call "SendMessage" instead of PostMessage, it will not work consistently.

Monday, June 05, 2006

Converting dd/MM/yyyy strings to Datetimes in .NET

It's not easy to find documentation on "How to parse a string into a DateTime variable in .NET?". I had to create an application that parsed a date entered as dd/MM/yyyy, and I had to ensure the code would run on any system, including U.S. based computers which would have the culture info set to MM/dd/yyyy.

All documentation points to the DateTime.Parse() method. Now this method is of no real use, because it always uses the culture information, which according to me is a no-entry zone because heck, all I want to do is parse a date, not change a whole culture.

Now there's this other function named DateTime.ParseExact( string s, string Format, IFormatProvider provider). This is EXACTLY what I needed, except I didn't know what to do with the provider parameter. All the documentation says is "An IFormatProvider that supplies culture-specific format information about s."

Another mention of the dreaded "culture". I tried to understand what this IFormatProvider was, and got to some custom class that converts strings to radixes (whatever they are). I don't know any culture that has radixes but I might have been living in a shell somewhere and all of this just happened.

Completely flummoxed, I thought, "to heck with this culture and IFormatProvider business" and passed "null" instead, and I got exactly what I wanted.

All you have to do is call:

DateTime d = DateTime.ParseExact( s, "dd/MM/yyyy", null);

and the string s will get converted to a DateTime, if it's in the dd/MM/yyyy format.

I wish they'd mentioned "optional" in the documentation for the provider parameter.

Monday, May 15, 2006

.NET Soap: Capturing Request and Response streams

There's just no simple way to capture the Raw Request and Response inside a SOAP server application in .NET. Of course you do have the Context.Request reference, which I thought was logically where the request would be. Boy, was I wrong.

It turns out that if you write code to access Context.Request.InputStream or use Context.Request.BinaryRead (which uses InputStream, as I decoded from Reflector) you will always get nothing. Zilch. Nada.

All sorts of google searches resulted in dead ends. In the end I found out about Soap Extensions on MSDN. This article shows you how you can add an attribute called TraceAttribute on your method like so:

 
[WebMethod]
[SoapInclude(typeof(ArrayList))]
[TraceExtension(Filename = @"C:\soaplog.txt")]
public ArrayList GetMyData()
....


and you will get a log of your request and response in the file mentioned.

I need to extend this to only log requests or responses, and to use TraceListeners if necessary. Also, this should be available as a stream rather than saving to a file only.

But I hope this helps people who need to see their request/response in a .NET SOAP WebService.


Thursday, February 09, 2006

Borland says goodbye to IDEs, and Delphi collects $200

Borland is selling off its IDEs. That means Delphi, C++ Builder, JBuilder and other IDE tools are being sold to another company or investor. (who hasn't yet been identified, so will the real buyer-of-IDE please stand up.)

There's tons of messages everywhere in the Borland Blogosphere. Marco Cantu has collated a list of Borlander messages, and Nick Hodges has some interesting viewpoints too.

The Shenoy Take
Shenoy is ambivalent. These are exciting times, not because the deal is mindblowingly fantastic. But because the options that are on the table make everything a lot more dynamic. And Shenoy's has a nine year investment in this technology. So is it going down the toilet, or will we see the next developer tool giant?

[We will now stop referring to ourselves in the third person. Instead we will refer to us in the plural.]

Start with the facts
Let's take some base facts:
  • Borland is still losing money. A company that's losing money has to focus on what it needs to do to earn money. Borland has thus decided it's the ALM route to take, not the IDE route.
  • Borland has done a lousy job for marketing IDEs. Nick has argued, and in quite a convincing manner, that the Borland Sales Force tends to focus on Enterprise sales rather than channel sales (where Delphi comes in). Plus they don't advertise much, and their channel doesn't either.
  • The IDE tools haven't really gotten investment. I'm not talking about building new versions, which Borland has done, but about a larger strategy in the developer world. Giving more power and resources to Evangelism. Buying or fostering third party add ons. Venturing into tech areas that have long term potential.
So what are the Good Things about the deal?

Good Thing #1: IDE Marketing improvement

The IDE tools are out of Borland's vice-like grip. Borland has been tying their hands behind their back all this time, and now the IDE tools will be able to strategize better. They can, for instance:
- Build SKUs at lower prices to address competition
- Advertise, Advertise, Advertise.
- Build BDN content into much much more, including online marketplaces for third party products, eBay links for Product sales, Blog aggregators, etc.
- Increase channel sales.
- Partner with web hosting companies to include Borland DLLs in their shared hosting packages.
- Training: Get more training companies affiliated, and build more certification levels.
- Revive the "Publishing" unit and promote authors for books/technology, even build online PDF sales of book content, chapter by chapter! Sell CDs of "Best of BDN" etc. or bundle it with IDE products. Stupid, you think? It was, earlier. Now is the time to start it again.
- Additional focus on the neglected Asian/European market, where a lot of development seems to be happening with Borland products.
- Put more money into Evangelism.

These are all possible only with an IDE spin off. Borland will just not allow them to do any of this without showing a multi million revenue potential in immediate quarters.

Good Thing #2: The Poor Man's ALM
Borland gets to focus on ALM: Application Lifecycle management. And tie in with Microsoft and other offerings, rather than be stuck with its own toolkit. So they become an integrator, with products like Caliber for Requirements Management, StarTeam for Configuration Management, Segue for Quality, Legadero for Project Managment/Time Tracking/Bug Tracking etc.

Each of the above "ALM" tools cost a LOT of money. I would guess that a 10 to 20 person company will have to invest more than $10,000 for each of these tools, and in addition to buying these tools, a company has to buy Professional Services from Borland which I'm guess is going to be a significant expense.

Note also that they don't mention cost of any of these products, or sell them online.That's because it's expensive. That's because they're difficult to sell off the shelf, because using them needs training, consulting and deployment and that can only be sold face to face.

So what I'm suggesting is the New Company ("NewCo") can build or buy such tools for a lot lesser cost. I mean:
- Lower cost bug tracking tools. You get 'em for $100 a piece.

- Simpler web based project management tools, integrating with the IDE.

- Version control: link with Subversion or CVS

- Add: build and deployment tools like BuildStudio or FinalBuilder

- Add: Sell the IDE as a prototyping tool as well (or build a CUT down designer version to prototype)

- Add: Use of "agile" programming techniques in the IDE. For instance, Pair programming using VNC technology to "repeat" actions in multiple computers. This also helps in team code reviews over an intranet (or even, over the internet!)

- Add: Chat, Voice and Video Integration WITHIN the IDE. Why? Today development can be done on the same project by people in multiple locations. Building IDE level interaction will make many things possible over long distances, such as code discussions, refactoring. etc. And perhaps as importantly, this is a great training tool.

Technology wise, all the above are simple to integrate for a company like Borland. With its presence it can easily partner with such vendors of such tools, or integrate open source tools like VNC and Subversion. The business benefit is only visible in the long term: if Borland pulls this off, they will be the most flexible IDE in the market for small to mid size companies: a market Visual Studio is slowly moving away from.

Good Thing #3: Team Retention
It's scary for us when people at Borland leave.

Why? Because we fear their going means loss of an irreplacable resource at Borland. That these people have been been masters of their domain and the knowledge is not spread across many others, because hiring has been low.

Why has Hiring been low? Because Borland's strategy was to focus on buying ALM companies, not augmenting current team strengths. Because they wanted to keep head counts low for idiotic-stock-market-appeasing purposes. Because they wouldn't release budgets for building bigger high quality development teams.

The deal may change all that. With an increased focus on IDE products, the best way ahead is to
- innovate in the products, meaning a bigger better team
- increase evangelism resources meaning more people on DevRel

A bigger team means less fear of people leaving, and of course lesser fear that one person leaving will trigger other exits as well.

What about Bad Things?
Of course there are bad things. Every deal has nightmare speculators jumping in, and I'll put a few important (to me) Bad Things.

Bad Thing #1: The IDEs will Die.
You might think a Microsoft or Oracle will gobble up this company and kill the IDE products. Small change for some of these companies, actually.

You might think the buyer won't put in additional funding for a real strategy. That is no different from Borland, and will kill the IDE company.

Both could be true, but Borlanders don't seem to think so, with David I, John Kaster and Anders Ohlsson repeatedly saying in the newsgroups that the "NewCo" will have a strategic investor who will take the business forward.

Bad Thing #2: They will open source Delphi.
I don't think this is a good idea. I, for one, hate it, but there is a a feeling that if they don't get a buyer they will try something like open sourcing Delphi.

I like open source, but frankly, open sourcing Delphi gives no revenue for Borland, and very little promotional possibility for the IDE ecosystem. So I'm saying this will be a bad thing.

Bad Thing #3: The loss of the Borland Name
This is a definite bad thing, in the sense that the Borland name will no longer be available to market. Like in the IBM Thinkpad/Lenovo scene, I believe the brand name has a lot to say in a sale.

But having said that, I believe the product names - Delphi, Interbase etc. - have a good brand standing on their own. So this may not hit NewCo too much, unless we're talking "Enterprise" sales, which obviously will go to the Borland stable, not to NewCo.

Conclusion
This is the longest post I've ever made on this blog. I have to add a conclusion so that you know I'll finally shut up sometime.

This is a great step for Delphi (and other IDE products). The team seems extremely excited in a happy sort of way, and seems to be waiting to throw away the shackles that hold them back. The message is universal, and clear: The IDE people at Borland want this, and want this now. It's time for us, the community, to watch them weave their magic.

Who does Shenoy Think it is?
Shenoy thinks it's the big search engine giant. Shenoy may be an idiot. A fool. Shenoy's head may be full of little wooden pieces that he doesn't remember the technical name for, because his brain is full of little wooden pieces. You just can't say, though.

Saturday, February 04, 2006

Soap Download Progress

I had written an article about how to provide feedback during a SOAP call using a progress bar in Delphi. That article seems to be offline, temporarily. You can now also access this article at:

http://blogs.teamb.com/deepakshenoy/articles/SoapDownloadProgress.aspx

Saturday, January 28, 2006

The PInvoke Live Template

Live Templates in Delphi 2006 are a pretty cool feature. The idea is that you can extend the Code Template mechanism (Ctrl+J since, well, a long time) - by including your snippets of code, and even add functionality to the IDE beyond simple code replacements. For instance, typing in "if" and hitting the space bar immediately brings up a series of things in the IDE that look like this:

You can type in a condition and hit [tab] : the cursor will move to the next line for you to continue. Awesome stuff, and more importantly, it's all extensible! Check out the set of files in your ObjRepos directory, by default at C:\Program Files\Borland\BDS\4.0\Objrepos\code_templates\delphi.

The XML files are a little complicated, but you will find Deborah Pate's detailed explanation of these files very helpful to start. Also you can refer to the Wiki for Live Templates, containing more helpful information.

From the XML file, you can call "script" functions that will make Delphi automatically declare a variable, or Invoke Code Completion or call some other functions. But, and this is very interesting, you can extend this script. What this means is, a live template can call YOUR custom function when a user invokes it. For instance, a template could brings up a message saying "How disgusting." when a user types "GOTO", and then proceed to erase the GOTO.

Sparky's blog post tells you how to extend this script schema. The extension involves writing a package with a new "script engine" and then calling that script engine from the live template XML file. The post contains a very good example of how to paste clipboard text wrapped around a try/finally block, by just typing clippy in the code and hitting TAB.

This got me thinking. Can we call external applications using live templates? "Of course you can.", is the right answer. And I'm not the first one to get there or blog about it - Daniel Wischnewski has written an article about a web search live template script that invokes your browser from the IDE.

I wrote something too. It's called a "PInvoke extension" for Delphi and C#. You need to do the following:

1) Download the source code from http://cc.borland.com/item.aspx?id=23915.
2) compile/install the package in the IDE.
3) Copy the .xml files over to your ObjRepos folder (appropriate sub folder) or to the User Folder (by default: c:\Documents and Settings\[USERNAME]\Local Settings\Application Data\Borland\BDS\4.0\code_templates)

Then in a new Delphi unit type: pinvd and you'll see this:
I typed in PeekMessage and I got:


(Click to enlarge)

You can use pinvc in a C# file as well, and the result is the C# code for the Win32 call. I've used both Adam Nathan's pinvoke.net site and John Kaster's BabelCode C# to Delphi for .NET converter.

(The pinvoke.net site only gave me C# definitions. I used that and sent it over to BabelCode for the Delphi translation of the same C# code.)

What? No Stock Quotes?

And in the spirit of a true web service programmer, you will also need to get Stock Quotes when you are in the IDE. Which means you can type squote and hit TAB, and you'll get:


and if you type BORL and [enter] you get:

Can't live without it.

Anyhow, all source is available, so feel free to dig in, and tell me what you think.

Wednesday, November 16, 2005

Stress testing Web Services

If you need to test your Web Service for performance, you can use the Web Application Stress Test (WAST) tool. It's a microsoft tool, free of cost.

You can learn more about how to use it in my paper on optimizing web applications. But that paper only talks about using WAST for web apps, i.e. browser based applications. What about web services?

Let's see how WAST works. When you hit "record", WAST opens a new browser window, where you must access your web page, click links, enter data etc. When you're done, you close the browser, and WAST will then allow you to simulate the same steps with lots more users, bandwidth etc. and gets the results to see how your web app responds to stress.

What WAST does is it acts like a proxy server, and records your requests. It will then "play back" the requests through multiple threads to simulate multiple users.

Web services can be stress tested the same way.

First, ensure you're using WinInet in
your Delphi SOAP and not Indy (WinInet is the default so if you don't know what this is, don't bother) This step is not necessary for .NET based web services of course.

Run WAST, and hit "Record". A browser window will come up. Ignore it, but do NOT close it.

Run your Web Service Client (something that accesses all functions of your web service) in the same machine as your server.

Go through a simulated use case on the client - eg. logon, get something, save something etc.

Close that (ignored) browser window when done.

You'll notice that WAST has done all the recording of XML sent etc. This can then be simulated for lots more users.

Note that WAST will only report on bad web results (Server errors returned as HTTP result codes etc.) You might need a different approach if you need to figure out SOAP Faults as well (maybe a temporary try/catch handler in the server that returns a different HTTP code if there's an exception)

Also if you want to see web requests in general, check out Fiddler.

Tuesday, September 27, 2005

Yes, a Delphi Roadmap.

It's here, finally. From Danny's post on Borland's Delphi Roadmap, the whole "strategy" thing is a lot clearer. So we have a future, for both the Win32 and .NET side of things.

What we have is:
Dexter in 2005

  • Language improvents: (e.g. operator overloading)
  • Speed: IDE Startup and runtime speed improvements (with stuff from the Fastcode Project)
  • C++ support (Win32) including support for the VCL
  • Command line CF compiler

Highlander in 2006

  • Another dumb project name ("Highlander"???)
  • .NET 2.0 support (if .NET 2.0 is actually out by then)
  • We're talking generics, partial classes, and nullable types.
  • The CF "IDE" with VCL.NET support

Overall this shows that Borland has a vision for a product that's always been good, but has lost a lot of its former shine. Now, what we need is for Borland Management to not backtrack. Let there be more such informal fixes and more informal previews of upcoming products.

Oh yes, there's even talk of a Delphi Win64 compiler in 2007.

    Wednesday, September 14, 2005

    "Beginning to Offshore"?????

    Why do people write like this? Choice phrases:
    • "clich├ęs, outlook and a realistic suggestions"
    • But, flourishing outsourced development engages more than defining a product spec.
    • "remember: the more an outsourcer comprehends the milieu, the better prepared it will be..."

    Overall the entire article is as clear as mud. But there's more! It's a ripoff from Bharat Khatau's original article, which is a magnitude better in content and grammar. The rip off is terrible - it's just run through a thesaurus, methinks. Every single sentence has been rephrased...no, has been gramatically assaulted - to produce something that might pass a copyright test, but fails on the ground that it's utter nonsense.

    The original article has this for instance:
    "Success requires careful definition of how your company and the outsourcer will interact - and the right spirit governing those interactions.".

    The rip-off says :
    "Success necessitates fastidious description of the way your company and the outsourcer will interrelate".

    Also, Good projects for successful pilots involve minimal risks to the business strategy or core product

    becomes First-rate projects for triumphant pilots rivet least risks to the business strategy or core product.

    ("rivet least risks"???? What are they smoking?)

    The rip-off has, very helpfully, placed a link to the original article. Which kinda sums it up, I think: it's not a malicious act, they're just stupid.

    Let me not take away from the entertainment value, of course. You need such articles so you can tell people - "I may be bad at documentation, but I ain't *this* bad".

    (If you like using one-and-a-half-foot words, you'll love sesqupedal pedantry)

    Friday, September 02, 2005

    A TeamB'er wrote Dr. Watson!

    Just went through a Matt Pietrek post about Dr. Watson.The original author, Don Corbitt, was a member of TeamB before he joined Microsoft! (Very sadly, Don died in a private plane crash)

    Also Matt was a Borlander, and he wrote a "Dr. Frank" to overcome the limitations - named after, I guess, Frank Borland. Speaking of debugging tools, I just found some free hex editors and disassemblers here. Amazing how much stuff is available out there, for free.

    Thursday, September 01, 2005

    Microsoft Research

    I just visited Dan Miser's Blog and found a link to Microsoft Research Downloads. I knew they did some research out there, but this stuff is just awesome! They have Allegiance- a multiplayer space-combat game, source code to attach to WiFi Networks using a single WiFi card, and even a safe versoin of C.

    I haven't yet experiemented with ConferenceXP - another one of the research projects. Essentially it's software that allows video conferences to be "multi-cast" across a network - currently it needs a good/fast link, multi cast support and a really high end dedicated server for such stuff, but I think in the near future that will not be a big issue. This is a killer app, especially for multi location offices like ours. What I like about this is the ability to "archive" sessions - so that people who couldn't attend a session can view it at a later date. I've always wanted such things for conference calls - i.e. record a call to mp3 and then run it back for a transcript. (If anyone knows such a tool...)

    Also, we've started to use Skype for office communication. It's amazing in terms of quality and also has an answering machine. Not that I hate regular phones - but I think they're too darn expensive when you're calling between India and the UK.

    Yeah, no code in this post either. I have not compiled anything for weeks now...but I have found time to play squash. Maybe they're mutually exclusive.

    Thursday, July 07, 2005

    New Job!

    It's time for a new job! I'm now at Flovate Technologies, as Chief Executive Officer, India. That's a fancy title all right. But wait till you hear about my computer! (This is geeky)

    I get a Dual Xeon 3 Ghz monster, with a 22" main and a 15" secondary monitor. There's 1 GB ram and SCSI RAID disks totalling 140 Gigs of Bytes. And a 128 MB ATI Card, and three mice running around in wheels. Awesome stuff. I just get lost in the incredible real estate on the screen. My 15.4" Dell latitude is like a crowded alley in comparison.

    I get to manage the team in India and co-ordinate with the folks in the UK, where I must say there are some incredibly talented developers! I just went through a couple of frameworks and man, this stuff is really impressive. I can't reveal the juicy details, but let me just say that if there's a Workflow map in the future, we're going to be on it.

    Non geeky stuff: I'm learning the ropes on a different kind of management. It's so tempting to get into code, but it's simply better not to touch it until you really understand the situation. I used to think the code was king, that you could figure out anything just looking at the source code : Yes, at this point I can probably identify a ton of patterns by looking deep enough. But does that really matter? What you really need to understand, right off the top, is what the product or project is about. What kind of users use it, what the business proposition is. Imagine you're the customer, and ask stupid questions (the former sometimes implies the latter). Only then get into the code!

    I also need to understand that every developer will take short cuts, even the best of 'em. To refactor is one thing. To identify and create an environment that will support and encourage refactoring is quite another. And then there's the whole scheduling, project management etc. issue - I think I'm going to become a PHB.

    Anyhow, what's important is this: I can have THREE rows of icons on the status bar and still have enough on the screen to eat dinner out of. Ha!

    No seriously, this is going to be one helluva challenge. I'm quite looking forward to it! My next few posts might involve lesser technology. Or more. I don't know. Interesting times, indeed.

    Friday, July 01, 2005

    Delphi problem with uploading compressed streams

    In my Advanced Web Services article, I had demonstrated how you could do Datapacket compression by compressing the ENTIRE packet before it was sent from the server, and decompressing it at the client end. (Applies to encryption also)

    Now I'd said you could do this the other way around too : compressing packets before sending on the client end, and expanding them at the server, using the BeforeRequest event of THTTPRio. This doesn't work: Firstly there is a bug in D7 SOAP, that ignores the var parameter in the BeforeRequest handler. Look here for a fix I've mentioned.

    A bigger problem is this: The BeforeExecute event is declared like this:

    TBeforeExecuteEvent =
    procedure( const MethodName: string;
    var SOAPRequest: InvString) of object;


    Now if you do some kind of compression or encryption, you might not like to deal with the WideString parameter. The process might introduce some NULL (0) characters in the character array, which will then be truncated by Delphi (which assumes a string ends on a NULL character).

    Fortunately, the source is king. Here's a few steps:

    1. Write a descendant component from THttpRio (call it THTTPRioEnh)

    2. Declare a type like so:

      TBeforeExecuteStreamEvent =
      procedure(const MethodName: string;
      Request: TStream) of object;


    3. In the THTTPRioEnh class, declare a published variable of type:


      property OnBeforeExecuteStream: TBeforeExecuteStreamEvent
      read FOnBeforeExecuteStream
      write FOnBeforeExecuteStream;


      Hit Ctrl+Shift+C. The private variable should get declared
      automatically.

    4. override the DoBeforeExecute procedure and in it put:


      procedure DoBeforeExecute(const MethodName: string;
      Request: TStream);
      begin
      inherited DoBeforeExecute(MethodName, Request);

      if Assigned( FOnBeforeExecuteStream ) then
      FOnBeforeExecuteStream( MethodName, Request );
      end;


    5. Then register the component using a Register procedure, put it in a package, compile and install.

    You can then drop a THTTPRioEnh component on your form, and assign a handler to OnBeforeExecuteStream of this component.

    I am lazy, so I did all this in Rio.Pas and instead of creating a handler in the IDE I set this up in code instead, something like:

    HTTPRio1.OnBeforeExecuteStream := BeforeExecuteStream;


    Where I do the modifications to the request stream.

    Thursday, June 30, 2005

    Last Day at Agni

    It's my last day at Agni Software. It's been a remarkable 7 years here, and I've had a lot of fun and learning at Agni. I was a tinhorn when I started, and I'm a little less of one now - I don't file notches on my guns, but I haven't learnt to shoot any better. (They keep moving the targets!)

    Arun and Chirag, my co-founders, will continue to grow Agni, and I wish them and everyone at Agni the very best for the brightest possible future. We're all very good friends, and I'm sure there's going to be a lot of getting together for coffee. (they're teetotallers, so beer is out) (No, that's not why I'm leaving! :) )

    I'll be around on the blog scene, and I'll tell you all about my new job. Tomorrow.

    Skype - and how to use it in Delphi

    Skype has a public API, and it's almost impossible to find. Actually there's no specific file for the API - Skype itself responds to calls using WM_COPYDATA. It'll also send you notifications - stuff that you must handle in your form's Message Handler.


    Ok, this isn't that simple to explain. Check out a sample Skype Tracer that I've developed, and it's available at:


    http://www.agnisoft.com/downloads/SkypeTracerDelphi.zip. Let me know what you think...

    Sunday, June 26, 2005

    .Text Comment Spam

    I got hit recently with a lot of Comment Spam on my TeamB blog. It was incredibly boring to sit and delete all of the spam comments - which, because of what I would only call laziness, had grown to around 500 in number - since .Text only allows you to delete one comment at a time! And each time it would bring up a confirmation web page from the server, and then, when you select "Yes, I know what I'm doing, just delete the darn message" it throws up another page with "Message Deleted" or something like that. You have to then click once more to get back to the message list. Note: I don't have access to the database.

    Obviously this could be rectified by changing some code. The .Text source is available - so maybe it could be modified.

    I downloaded the .Text source and then managed to actually get it compiled and running on my laptop. What I started to do was to check how the code worked - and it turns out there's a way to enter "comments" by creating a "trackback" to your page. I'm not going to reveal how - but the source code of the page reveals all.

    Solution: Simply remove trackbacks from your page. Let people actually enter the trackback in, as a comment. And introduce a CAPTCHA in the comment entry page.

    The CAPTCHA solution is fairly well documented. You can downloadThe Clearscreen CAPTCHA control for .Text by Miguel Jiminez and install/run it.

    For removing trackbacks: Go to your web.config on your .Text site and remove the lines starting with <HttpHandler and that contain the text pingback or trackback. Or set enableTrackBacks="false" and enablePingBacks="false" in the <Tracking element.

    No trackbacks then? Maybe the comment api can be modified to allow it as a different field - I don't think I'll have time to do this, but if anyone has, please let me know.

    Friday, June 17, 2005

    Setting Focus with MessageDlg

    One of the things I've had to do manually many times is to have dialog boxes pop up, but logically "OK" should not be the focussed button. (Like "Do you want to format this disk?")


    In Delphi, you'd usually call MessageDlg for a dialog that had a Yes/No/Cancel or an Ok/Cancel input only - but that doesn't give you a way to change the focus or default button. So here's how you would do it - call MessageDlgFocus instead:


    var
    ButtonNames: array[TMsgDlgBtn] of string = (
    'Yes', 'No', 'OK', 'Cancel', 'Abort', 'Retry', 'Ignore',
    'All', 'NoToAll', 'YesToAll', 'Help');

    function MessageDlgWithFocus(const Msg: string;
    DlgType: TMsgDlgType;
    Buttons: TMsgDlgButtons;
    FocusBtn: TMsgDlgBtn;
    HelpCtx: Longint): Integer;
    var Btn: TComponent;
    begin
    with CreateMessageDialog(Msg, DlgType, Buttons) do
    try
    HelpContext := HelpCtx;
    Position := poScreenCenter;

    Btn := FindComponent( ButtonNames[FocusBtn] );
    if (Btn <> nil) and (Btn is TWinControl) then
    ActiveControl := Btn as TWinControl;

    Result := ShowModal;
    finally
    Free;
    end;
    end;


    Call it with:

    MessageDlgWithFocus('test', mtCOnfirmation,
    [mbOk, mbCancel], mbCancel, 0);


    This code will bring up a dialog with OK and Cancel buttons, and the Cancel button is focussed.

    Thursday, April 28, 2005

    Really stupid web site tricks?

    What I refer to, is Julian Bucknall's post on Stupid Web Site Tricks - about how nutty it is when good links are usually orphaned during a site re-org.

    Turns out Microsoft is one of 'em. I had, in my Optimizing Web Applications article, linked to the Microsoft Web Application Stress Tool which has been moved from here to here. Grrr.

    Maybe they don't want us to find it, and buy some of their non free tools instead. Well, they should hide it where no one can find it, instead of playing hide-and-seek with the links.

    Btw, I've reorg-ed my site a few times, and I'm guilty of not really maintaining any links either - but all of MY past has some excuse for it.

    Optimizing web applications - white paper

    After reading Dan Miser's post about the ISAPIThreadPool unit, I dug up an article I'd written long back...and managed to reformat it so I could make it public. Well, here it is at http://www.agnisoft.com/white_papers/optimizingwebapps/. Cheers, have fun, and let me know what you think!

    Tuesday, April 26, 2005

    Multiple windows in Delphi

    From a recent thread in borland.public.delphi.nativeapi.win32:

    There's a problem in the way Delphi handles multiple open forms. Delphi applications can have multiple forms, and you will notice that each form does not appear on the task bar. Delphi forms do not have the WS_EX_APPWINDOW style set - this style is required if a window needs to appear in the task bar. But there is one icon, isn't there? That's the icon of the Delphi "Application" - and clicking on it in the task bar simply focusses the main window (or any other modal window above it)

    If you need your "other" windows to appear in the task bar, then you can do this: Override the CreateParams procedure in your other forms, and write this code in there:

    procedure CreateParams(var Params: TCreateParams); override;
    ...
    procedure TForm1.CreateParams(var Params: TCreateParams);
    begin
    inherited;
    Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
    Params.WndParent := GetDesktopWindow;
    end;


    Now, if you're doing this, you probably are writing applications where ndividual windows behave independently of the "main" form. You might notice that suddenly, the main form comes up in FRONT of the other top level form, like when showing hints or displaying dialog boxes from them. Reason: Delphi's Application variable is the culprit - it handles a lot of things including hint display, Dialog box messages etc. When it gets these messages, the main form gets activated, for some reason.

    Fix: Don't just make your "other" forms with WS_EX_APPWINDOW. Make ALL your top level forms have that style, including your main form. (Use the same logic for the main form). Then, REMOVE the WS_EX_APPWINDOW style from the "Application" - using this code:



    SetWindowLong(Application.Handle, GWL_EXSTYLE,
    GetWindowLong(Application.Handle,GWL_EXSTYLE)
    and not WS_EX_APPWINDOW
    or WS_EX_TOOLWINDOW);


    You'll have a lot less trouble then.