Solved

Need some expert advice...

Posted on 2002-06-24
15
806 Views
Last Modified: 2007-12-19
Hello all. I have developed an ISAPI filter dll for IIS using Delphi 6. The filter will be running on a server we use for development purposes (my company is a website development firm). Its purpose is to remap URLs to sort of emulate host-header redirection without having to set it up manually on the web server. Basically, if you type in:

http://something.ourdomain.com/

the filter will modify the request to be

http://www.ourdomain.com/something/

The filter works as expected, but I am curious about a few things that are mostly Delphi specific (and have nothing really to do with filter dlls):

1. What does the global variable isMultiThreaded actually do and do I need to set it to true?  I have read that it is required by the memory manager to be set to true in multi-threaded apps, but is it necessary in something like this? Although my code is not multi-threaded, the dll can be called by multiple threads at the same time, so I am not sure if that is the same thing as far as the memory manager goes.

2. My code has to make a few calls involving passing some local variables as pchars... Which is the best way to do this.

var
  myBuffer: array [0..MAX_BUFFER] of char;
  buffSize: integer;
begin
  buffSize := MAX_BUFFER;
  GetApiProc(@myBuffer, @buffSize) //procedure expects pchar and pointer to an integer.
  //do something with myBuffer
  SetApiProc(@myBuffer);
end;

OR

var
  myBuffer: string;
  buffSize: integer;
begin
  buffSize := MAX_BUFFER;
  setLength(myBuffer, buffSize);
  GetApiProc(pchar(myBuffer), @buffSize) //procedure expects pchar and pointer to an integer.
  //do something with myBuffer
  uniquestring(buffSize);
  SetApiProc(pchar(myBuffer));
end;

or is there another way that is better... My goal here is for this routine to go as fast as possible, and I need to reduce the number of memory allocations to the bare minimum... So basically my question is are string types slower to allocate than character arrays???


Any other advice on getting this to run as fast as possible would be greatly appreciated.

Heath


 
0
Comment
Question by:heathprovost
  • 7
  • 4
  • 2
  • +1
15 Comments
 
LVL 5

Author Comment

by:heathprovost
ID: 7106262
Here is the source code for my filter if it helps... The ISAPIFilter.pas file is just type declarations and stuff, this is all the code.


library hhrfilter;

uses
  SysUtils,
  Windows,
  ISAPIFilters;

const
  C_BETA : pchar         = 'beta'#0;
  C_STAGE : pchar        = 'stage'#0;
  C_URLHEADER : pchar    = 'url'#0;
  C_SERVER_NAME          = 'SERVER_NAME';
  C_REMOTE_ADDR          = 'REMOTE_ADDR';
  C_FILTER_DESC          = 'TGG Host Redirect Filter, v1.0';
  C_MAX_URL_LEN          = 4096;

function GetFilterVersion(var pVer : HTTP_FILTER_VERSION) : BOOL; export; stdcall;
begin
  try
    pVer.dwFilterVersion := makelong(HTTP_FILTER_MINOR,HTTP_FILTER_MAJOR);
    pVer.dwFlags := (    SF_NOTIFY_NONSECURE_PORT
                      or SF_NOTIFY_PREPROC_HEADERS
                      or SF_NOTIFY_ORDER_DEFAULT
                    );
    StrPCopy(pVer.lpszFilterDesc, C_FILTER_DESC);
    result := True;
  except
    result := False;
  end;
end;

function HttpFilterProc( var pfc          : HTTP_FILTER_CONTEXT;
                         NotificationType : DWORD;
                         pvNotification   : LPVOID)
                         : DWORD; export; stdcall;
var
  pHeaders               : HTTP_FILTER_PREPROC_HEADERS;
  filterHost             : string;
  filterUrl              : string;
  returnUrl              : string;
  getSrvVar              : TGetServerVariable;
  i                      : Cardinal;

begin
  case NotificationType of
  SF_NOTIFY_PREPROC_HEADERS:
    begin
      try
        pHeaders := HTTP_FILTER_PREPROC_HEADERS(pvNotification^);
        i := C_MAX_URL_LEN;
        setlength(filterUrl, i);
        pHeaders.GetHeader(pfc, C_URLHEADER, pchar(filterUrl), @i);
        i := C_MAX_URL_LEN;
        getSrvVar := pfc.GetServerVariable;
        setlength(filterHost, i);
        GetSrvVar(pfc, C_SERVER_NAME, pchar(filterHost), @i);
        i := pos('.', filterHost);
        if i <> 0 then
        begin
          returnUrl := '/' + copy(filterHost, 1, i - 1) + filterUrl;
          uniquestring(returnUrl);
          pHeaders.SetHeader(pfc, C_URLHEADER, pchar(returnUrl));
        end;
        result := SF_STATUS_REQ_NEXT_NOTIFICATION;
      except
        result := SF_STATUS_REQ_ERROR;
      end;
    end;
  else
    result := SF_STATUS_REQ_NEXT_NOTIFICATION;
  end;
end;

exports
  HttpFilterProc,
  GetFilterVersion;

begin
  IsMultiThread := true;
end.
0
 
LVL 33

Expert Comment

by:Slick812
ID: 7106506
hello heathprovost, I don't think that IsMultithread needs to be set to anything, you can read it to see if there has been another thread created. I also think that a static array like
myBuffer: array [0..MAX_BUFFER] of char;
will be faster, and less cpu time, because array size does not need to be allocated. If you are a performance freak then you should look at the API QueryPerformanceCounter to get real small time measurements.


var
PerFreq, PerCount2, PerCount3: Int64;


if not QueryPerformanceFrequency(PerFreq) then
PerFreq := 0;
{QueryPerformanceFrequency gets the number of times a second
this PerformanceCounter ticks, it can be above a million ticks
per second}
QueryPerformanceCounter(PerCount2);

//test code goes here
  buffSize := MAX_BUFFER;
 setLength(myBuffer, buffSize);
 GetApiProc(pchar(myBuffer), @buffSize) //procedure expects pchar and pointer to an integer.
 //do something with myBuffer
 uniquestring(buffSize);
 SetApiProc(pchar(myBuffer))

        {the PerformanceCounter is a very fast high-resolution counter
        which can be used to find out the amount of time between 2 calls
        to QueryPerformanceCounter( )}
QueryPerformanceCounter(PerCount3);


Time := Int2Str(((PerCount3-PerCount2)*10000) div PerFreq);
        Insert('.',Time,Length(Time));
TextOut(FormDC,6,ScrolRect1.Top+6,PChar(Time), Length(Time));
        {this will show the milliseconds between the PerCount2 and PerCount3}

0
 
LVL 33

Expert Comment

by:Slick812
ID: 7106529
I forgot to add the test
if PerFreq <> 0 then Exit;
0
 
LVL 5

Author Comment

by:heathprovost
ID: 7106557
Thank you for the response Slick812. I will go ahead and test the code as you suggested. Anyone else interested in commenting, please do so. I will post points for anyone who gives me any relevant and useful info.
0
 
LVL 5

Author Comment

by:heathprovost
ID: 7106563
Slick812, As I want to leave this question open, you can pick up your points here

http://www.experts-exchange.com/jsp/qManageQuestion.jsp?ta=delphi&qid=20315819
0
 
LVL 2

Expert Comment

by:333
ID: 7106729
1. unless you call in your dll BeginThread, IsMultiThread will always be false, even if your dll was called from different threads of same process.  when IsMultiThread is true, delphi's memory manager locks other threads while one of them is using memory manager. so even if you set IsMultiThread to true, it won't do anything in your case, because there is only one thread.
2. using static arrays will be faster.
0
 
LVL 5

Author Comment

by:heathprovost
ID: 7106805
Thanks 333. I follow your explanation, but what I am really getting at is do I need to worry about the memory manager screwing up when being called from multiple threads? Since it will NOT be doing any kind of locking internally is there potential for something to go wrong? The reason I set IsMultiThread to true was I was hoping that this would FORCE the memory manager to act like it was being called from seperate threads... If setting it doesnt achieve the desired affect (making the heap thread-safe), what would, or do I even need to worry about it?
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 2

Expert Comment

by:333
ID: 7106915
somehow i missed earlier that you are using strings.
dll uses calling thread's stack, but allocates memory from process address space. so there may be a problem when two threads called your dll at the same time and you are using memory manager.
memory manager is used for: GetMem, ReallocMem, FreeMem, New and Dispose, allocation and deallocation of objects, allocation and deallocation of dynamic strings. in your case you should worry about dynamic strings. you need to set IsMultiThread to true.

also, i found this article http://community.borland.com/article/0,1410,15657,00.html , that  says you need to set IsMultiThread:=True in ISAPI dll, because otherwise it won't handle multiple connections.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 7107023
1. I don't agree with other comment telling you not to set IsMultiThreaded to true. You need to set IsMultiThreaded to true, since your code will actually run in multiple treads (even if the tread were not created by your DLL). This flag enables the global lock used by the memory manager, which is needed to avoid corruption of memory manager structures when concurrent memory operations occur. Another alternative may be the use of a thread-safe replacement memory manager.

2. This depends on the actual API call. Some API calls (especially in ISAPI) return the minimum size of the buffer needed when an operation fails. In such a case, the SetLength method is better to use. However, if you know the max length needed and the buffer is not more than about 2k (half of one memory page on the stack), is a little faster because is does do less calls to the memory manager (which is usually slow).

If you need some assistance with ISAPI filter issues, let me know, I have written several filters and may be able to help out.
0
 
LVL 5

Author Comment

by:heathprovost
ID: 7108557
0
 
LVL 5

Author Comment

by:heathprovost
ID: 7108605
Thanks AvonWyss. That makes alot more sense to me. Since you mentioned that you have written filters before, I have discovered an issue with mine...

If you look at my code, you will see that I am intercepting SF_NOTIFY_PREPROC_HEADERS in order to rewrite the url during a request. Although this works as intended, the one issue we are having is that the PATH_INFO variable shows the url path as it is after I have modified it.  Since we use Cold Fusion and sometimes use this varaible in order to determine the current url path for the purpose of creating links, this would create problems because in that case we need to create the link using the munged path. I decided the easiest way to deal with this was to create a new header using addHeader. I called it HTTP_PATH_INFO_MUNGED and it is supposed to contain the url as the client should see it.  The problem is what do I do when the request involves accessing the default document?  if the request is for http://something.ourdomain.com/, my code will rewrite it to http://www.something.ourdomain.com/, but at some later point the server adds "index.cfm" to that path since that is the default document.  I need to also update this value in the HTTP_PATH_INFO_MUNGED variable, but cant seem to figure out a way to do it....

Heath
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 7108813
Heath,

interesting enough, I have also done an URL remapper (which loads its patters from a database and allows multiple replacements with conditions, variables etc. in the URL).

The best way to prevent the problem you describe is to write your pages and scripts in such a matter that they use and emit (to the browser in the HTTP document) relative paths exclusively. Becuase, when you are using relative paths, the browser will rebuild the new URLs for you without ever knowing anything about the mungling of the URLs, thus always requesting the correct URLs.

You could also hook the SF_NOTIFY_URL_MAP notification. Cold Fusion will use the server's NormalizeUrl server support call to resolve virtual paths to physical ones. This call will trigger a URL_MAP event, which you could "abuse" to react in certain conditions to return a reverse-mungled (*g*) path, like, whtn the URL starts with "&" or any other char unlikely ever used in a real path.

Note that you have to be careful in the PREPROC_HEADERS event. To this point, the server has already done some work even though the event could lead to the assumtion that this is not the case. In fact, the site/host ID has already been determined, and I think to remember that default documents also already have been applied if found on the given path. I'd suggest to use the Event Log or a log file to log debug information which you can then examine without the hassle of real-time debugging and it's difficulties in conjunction with IIS.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 7108895
Also, by the way, when you declare constant PChars, the trailing #0 is implicitly added by Delphi and thus not needed explicitly. In fact, you can even supply a constant string right away when a PChar is requested as parameter. Also, you don't need to call UniqueString before a cast when the PChar will not be modified (read-only). Also, every string modification will create a unique string (concat, copy, setlength etc.), so that you only have to use UniqueString on strings which have not been assinged to or from (!) after being altered.

I have written pretty smart (I think) wrapper classes for filters, which allow me to concentrate on the important stuff while keeping my code clean, safe (excpetions are handled etc.) and still fast. A copy of your filter would look about like this with my wrapper:

{URL Mapping}

library HHRFilter;

{$R *.RES}

uses
     Windows,
     SysUtils,
     BSISAPIFilters;

type
     TWebMappingGroup=class(TISAPIFilterGroup)
     public
          procedure Install; override;
     end;
     TPreProcFilter=class(TISAPIPreprocHeadersFilter)
     public
          procedure Execute(var FilterContext: Pointer); override;
     end;

{ TWebMappingGroup }

procedure TWebMappingGroup.Install;
begin
     Install('URL Mapping Filter', 1.0, [TPreProcFilter], fpBoth, fpLow);
end;

{ TPreProcFilter }

procedure TPreProcFilter.Execute(var FilterContext: Pointer);
var
     FilterHost: string;
     I: Integer;
begin
     FilterHost:=GetServerVariable('Host');
     I:=Pos('.', FilterHost);
     if I>0 then
          SetHeader('URL', '/' + Copy(FilterHost, 1, I-1) + GetHeader('URL'));
end;

begin
     InstallFilter:=TWebMappingGroup;
end.

Such a wrapper does make your code much easier to use and support. All the initialisations, entry-point and callback functions etc. are done in the wrapper. In my model, every kind of filter has its own class type with all the necessary declarations (like, GetHeader, SetHeader, DeleteHeader, GetServerVariable, ExitNotificationHandled, ExitAccessDenied, ExitAuthenticationRequired, etc.). Also, I have added support for all new notifications up to IIS5 (the ISAPI file which ships with Delphi is from IIS3 headers I think).
0
 
LVL 5

Author Comment

by:heathprovost
ID: 7109044
Thanks for the comments AvonWyss. As for the url thing, unfortunately my design goals require this filter to work regardless of how the pages are coded.  Baically, what I am trying to do is emulate host-header redirection, and I need it to act for the most part the same way as IIS's internal mechanism. I realize their are issues with doing it this way, such as logging and stuff, but logging isnt required on the server this will be running on.

I will try as you said to hook the URL_MAP stuff and see if that does the trick. I think my solution will have to involve both hooks...

Also, thanks for the tips with strings and such. I am not at all familiar with using them as pchars like this, that helped alot.

Your wrapper sounds nice, but I think I prefer doing it "by-hand" at this point. It helps me learn the details of what I am doing a bit better. Thanks for the help.  
0
 
LVL 14

Accepted Solution

by:
AvonWyss earned 300 total points
ID: 7109733
If you really want to provide code that is independent, you could also hook the SEND_DATA event. In the event, you first receive the headers, which you would check for the line "Content-Type: text/html". If you find another content type, call the server support function to disable further notifications for the currently running request. On the other hand, if you are dealing with HTML data, search and replace any occurence of the "new" URL with the "old" one, so that the browser never gets in touch with the new one. Hoever, be aware that you should be really careful to code efficient code when dealing with the data events of IIS, since they are called frequently and a slow filter can really decrase server performance (that's why I suggest to disable notifications on all data but HTML, therefore not slowing down the server for anything but HTML).
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Suggested Solutions

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
This video discusses moving either the default database or any database to a new volume.
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now