2

TADOConnection is failing to connect in the application initialization section of Delphi ISAPI App (TISAPIApplication):

Application is built with Delphi XE SPI, running Win 7 64/IIS 7.5 and WinServer 2008 RS2 - it cannot connect with ADO in the global ISAPI application context. (Example code is using MS-SQLServer OLEDB - but we also fail using Sybase ASE provider.)

The following code fails when conn.Open is called - TADOConnection.open never returns - ISAPI app hangs in la-la land, no exception raised:

library ISAPIBareBones;

uses
  ActiveX,
  ADODB,

    (...)

var
  conn: TADOConnection;

begin

  CoInitFlags := COINIT_MULTITHREADED;
  Application.Initialize;

  coinitialize(nil);
  conn := TADOConnection.Create(Application);
  conn.ConnectionString := 'Provider=SQLOLEDB.1;xxx';

//Fails here:

  try
    conn.Open;
  except on e:exception do
    logException(e)
  end;


  Application.WebModuleClass := WebModuleClass;
  Application.Run;

end.

The same code within a specific request handler (Delphi webAction) runs fine.

We suspect a problem with execution privileges in IIS at the ISAPI application level. But as far as we can tell, the entire IIS application stack from the webServer itself down to the specific virtual directory and the ISAPI dll itself are all running under the same credentials with same execution privileges.

Meanwhile, my workaround has been to initialize the database infrastructure from within an http response call (an ISAPI thread), and then simply check that it's initialized on each subsequent call. This works, but encumbers me with some constraints that I'd prefer not to deal with.

How can I make ADO database connections in a TISAPIApplication instance, before handling incoming requests.

Vector
  • 10,879
  • 12
  • 61
  • 101
  • What happens if you use 'Provider=SQLOLEDB;xxx'; instead? – Michael Riley - AKA Gunny Sep 01 '11 at 00:27
  • Since the code runs perfectly within a thread context, (as well in in many other places with same connection string) the problem would not seem to be related to provider/connection string issues. And it also succeeds and fails in exactly the same way with a Sybase connection - again indicating that this isn't a provider/connection string issue. – Vector Sep 01 '11 at 02:42
  • What do you mean by "application initialization section"? – Ondrej Kelle Jan 16 '15 at 08:29
  • I'm asking because your [comment](http://stackoverflow.com/questions/7264136/how-do-i-connect-to-a-database-using-ado-in-application-initialization-section-o#comment8755529_7271831) "the code I posted actually runs as a dll export called by IIS, not in loadLibrary-DllMain" contradicts with the code actually posted in the question. – Ondrej Kelle Jan 16 '15 at 09:33
  • @TOndrej - perhaps I am mistaken but I don't believe that is correct: The Delphi ISAPI application is initialized only after loadLibary has excecuted. The code posted after _applicaiton.initlialize_ code has the TISAPI web application object available. Note my comment to mrabat: Delphi calls can be made once _applicaiton.initlialize_ has run. The _application initialization section_ means quite simply the section of the code that initializes the TISAPI application object. – Vector Jan 16 '15 at 11:40
  • Of course, code in a DLL can only be run after the DLL is loaded. The code you've posted in your question (the DLL project's main begin..end block) is executed once after the DLL is loaded by the host application (IIS in this case). On the other hand, ISAPI requests are executed by IIS calling exported ISAPI functions. Because of this contradiction, it's unclear what the actual problem is. The term "application initialization section" is ambiguous, too, because Delphi has unit initialization sections (but not application). – Ondrej Kelle Jan 16 '15 at 12:46
  • I have changed the title and edited the question. I believe it is clearer now. Tnx. – Vector Jan 16 '15 at 17:47

2 Answers2

3

The begin ... end or an initialization part of a dll is the Delphi equivalent to dllmain in C++. So the same restrictions apply including:

  • Don't call CoInitialize
  • Don't call COM functions

This implies that you cannot create an ADO connection.

Do you know all the things that happen when you call TADOConnection.Create(Application);?

So what you're trying to do isn't going to work. And even if it did, you should not do it. Here's some better explanations:

http://msdn.microsoft.com/en-us/library/ms682583%28VS.85%29.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dn633971(v=vs.85).aspx
http://blogs.msdn.com/b/oldnewthing/archive/2004/01/27/63401.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2004/01/28/63880.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2014/08/21/10551659.aspx

MSDN suggests creating the database connection in GetExtensionVersion. This is how your isapi dll is initialized. It is not just for reporting the extension version. So that is the way to go. Create your own GetExtensionVersion function that initializes your database and then call the previous Delphi function.

library Project1;

uses
  Winapi.ActiveX,
  System.Win.ComObj,
  Web.WebBroker,
  Web.Win.ISAPIApp,
  Web.Win.ISAPIThreadPool,
  Winapi.Isapi2,
  Winapi.Windows,
  WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule};

{$R *.res}

function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall;
begin
  Result := Web.Win.ISAPIApp.GetExtensionVersion(Ver);
  // create your ado connection here
end;


exports
  GetExtensionVersion,
  HttpExtensionProc,
  TerminateExtension;

begin
  CoInitFlags := COINIT_MULTITHREADED;
  Application.Initialize;
  Application.WebModuleClass := WebModuleClass;
  Application.Run;
end.
Sebastian Z
  • 4,520
  • 1
  • 15
  • 30
  • This would make sense if not for the fact that initialization code for `TISAPIApplication` does some very _interesting things_ in that segment, including working with COM, as far I could determine from some cursory examination of the VCL code now. However, perhaps `TADOConnection.Create(Application)` is just a bit too _interesting_ .... – Vector Jan 16 '15 at 19:57
  • I don't see anything scary concerning dllmain in TISAPIApplication right now. Even if it did that would not mean that it is OK. There is a chance that you can get away with some things, but that could work now and fail tomorrow. It is OK to create normal classes as long as those don't call COM functions during creation. I've added two more links to msdn that explain the problem. – Sebastian Z Jan 16 '15 at 21:55
  • What I saw was not in TISAPIApplication itself, but in one of the parents someplace in there. No matter - I see in the MSDN blogs that it is possible to do something evil and not get screwed by it - it's essentially hit or miss - but apparently `TADOConnection.Create` is always **miss**. You have done the requisite legwork and I think I have accept your answer and give you the bounty. I will wait a bit and see if something more specific comes in, or perhaps a good **solution.** – Vector Jan 16 '15 at 22:21
  • My hunch is that once `TISAPIApplication` is initialized and `Application.run` has been called, it should be safe. But I can't find a good hook in there to use for the ADO code - somewhere to call it after `Application.run` has been called and the Application is ready to handle requests, but before a request comes in. – Vector Jan 16 '15 at 22:24
  • Application.run is still called from dllmain. So that is still unsafe. But I've found that msdn suggests using GetExtensionVersion for initializing the database. So that is the way to go. I've added it to my anser. – Sebastian Z Jan 17 '15 at 09:15
  • _Application.run is still called from dllmain. So that is still unsafe_ Yes - I believe at one point I tried putting the ADO code after Application.run and it still hung. I will try your new suggestion - there has to be some sort of hook that can be used for this - `GetExtensionVersion` - which I never paid attention to, sounds like it might be what I'm looking for. – Vector Jan 17 '15 at 10:15
  • `GetExtensionVersion` did not seem to work. I will have to try it again more carefully. – Vector Jan 19 '15 at 08:37
  • 3
    I think you should try again more carefully. MSDN says: *Initialization is handled by the entry-point function GetExtensionVersion. This function's role is to perform all initialization, **including the creation** of worker threads, synchronization objects, **and database connections**, and to establish the version of ISAPI that was used to build the DLL.* – Sebastian Z Jan 19 '15 at 09:44
  • I finally got that working - I had to clean up some other code that was illegally executing in DLLMain first. Now I hooked it all into `GetExtensionVersion ` and it seems to be working well. – Vector Jan 23 '15 at 00:12
2

I think the problem is that the code you run runs in the dll's main procedure. This part of the initialization is very restrictive e.g. you may not load any dll nor you are allowed to call CoInitialize (see http://msdn.microsoft.com/en-us/library/ms678543%28v=vs.85%29.aspx) . Your ActiveX alls there will cause some troubles which are most likely the reason for your exception.

Vector
  • 10,879
  • 12
  • 61
  • 101
mrabat
  • 802
  • 7
  • 15