COM-object in service application cannot be accessed - delphi

I`m developing a service application with COM-object in it (OPC Data Access 2.05 server). I have this code for registration my object, which is executed after installation:
procedure TOPCService.ServiceAfterInstall(Sender: TService);
var
lcHResult: HRESULT;
lcCLSIDString: String;
begin
ComServer.UpdateRegistry(True);
lcCLSIDString:=GUIDToString(CLASS_TestOPCServerDA2);
ComObj.CreateRegKey('AppID\'+lcCLSIDString, '', 'Test OPC Server DA2');
ComObj.CreateRegKey('AppID\'+Application.ExeName, 'AppId', lcCLSIDString);
ComObj.CreateRegKey('CLSID\'+lcCLSIDString+'\VersionIndependentProgID', '', C_TEST_OPC_DA2_SERVER_NAME);
<opc server registration stuff>
RegisterAsService(lcCLSIDString, Name);
end;
The service and COM-object are properly register in the system, so i can see my service in SCM and COM-object in OLE/COM object viewer (and also in OPC clients).
The COM-object itself looks like this:
type
TTestOPCServerDA2 = class(TAutoObject, ITestOPCServerDA2, IConnectionPointContainer, IOPCServer, IOPCCommon, IOPCItemProperties, IOPCBrowseServerAddressSpace)
with its factory registration code:
initialization
TAutoObjectFactory.Create(ComServer, TTestOPCServerDA2, Class_TestOPCServerDA2, ciMultiInstance, tmApartment);
The problem is when i try to CoCreateInstance(CLASS_TestOPCServerDA2) (via CreateComObject wrapper), i got freeze for 120 second and 0x80080005 (CO_E_SERVER_EXEC_FAILURE) error after. In SCM and Task Manager i see my service is started when COM-object is requested, but nothing else happens. If i stop the serivce and try againg, service would be started again, so i assume Windows knows about my COM-object, its executable and the fact that executable is a service.
I also tried to change user which my service is running under (to the same with the invoking application), but that did not help.
What am i missing?
Edit 1. I created new project and got rid of OPC (just left COM support) to isolate the problem, so now my class is looks like this:
type
TTestCOMServer = class(TAutoObject, ITestCOMServer)
end;
...
initialization
TAutoObjectFactory.Create(ComServer, TTestCOMServer, Class_TestCOMServer, ciMultiInstance, tmApartment);
And the service thread:
procedure TCOMService.ServiceExecute(Sender: TService);
begin
while (not Terminated) do
begin
ReportStatus;
ServiceThread.ProcessRequests(False);
Sleep(25);
end;
The problem persists: when i try to CoCreateInstance, nothing happens and calling app hangs for 120 seconds.
But! If i make 1 change: uncommenting Application.DelayInitialize := True; in dpr, COM-object gets created well and calling app freezes no longer! Is it the service execute thread that (not service main thread) processes COM-requests?
Edit 2. It seems that only DelayInititalization is requred. ProcessRequests can be called with False argument and sleep can have its place - i must have not properly rebuilded my project.
So, i think the answer to my question is to uncomment Application.DelayInitialize := True; in DPR-file. Delphi autogenerate text about that, but it mentions only Windows 2003 Server condition and my OS is Windows 10.

In my case (Delphi XE3 under Windows 10 Pro) i had to uncomment
Application.DelayInitialize := True;
in DPR. After this change, COM-object is created properly.

Related

Forcing TService OnStop event to wait until some job completes

working with a Windows Service application in Delphi, I stumbled on the issue as in the subject.
I do start a default Delphi Windows Service project on the IDE, follow the wizard and in the end I have a project and a
TService unit. I add to this project another unit, a Data Module (named DM) in which the service code logic is contained.
The DM has a TTimer (design-time) that runs a relatively long job.
Case 1:
DM is created by default in design-time. I have the following code in my TService Start/Stop
procedure TOmegaCAOraNT.ServiceStart(Sender: TService;
var Started: Boolean);
begin
DM.Timer1.Enabled := True;
Started := true;
end;
procedure TOmegaCAOraNT.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
DM.Agent_Stop;
Stopped := true;
end;
When I try to Stop the Service via Windows SCM - in appearance it confirms the Stop, field Status becomes empty - but it does not.
I can see the service .exe still running for a while, and what's more it does terminate the Timer's long job in the middle,
doing part of it only!
This is an undesired behavior!
I fixed this in the second case
Case 2:
DM is created in run-time. The Timer is enabled on DM.OnCreate
I have the following code in my TService Start/Stop
procedure TOmegaCAOraNT.ServiceStart(Sender: TService; var Started: Boolean);
begin
FDataModule := TDM.Create(nil);
Started := true;
end;
procedure TOmegaCAOraNT.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
FreeAndNil(FDataModule);
Stopped := true;
end;
When I try to Stop the Service via Windows SCM - it throws the following Warning:
"Windows could not stop the SERVICE> service on the Local Computer.
The service did not return an error. his could be an internal Windows error or an internal service error.
if the error persists, contact your system Administrator"
field Status remains Started. Timer' long job finishes until the end, and then the service really stops (refresh in SCM to see, Status empty)
This is desired behavior !
My problem is that I would like the DM to be created in design-time, and not in run-time
My question is:
can I have the right behavior of run-time DM (Case 2), with a design-time DM (Case 1) ?
thanks and best regards,
Altin
TService runs in is own worker thread at run-time.
If you configure the DM to be auto-created, it (and its TTimer) will be created in the main thread at run-time, not in the service thread. And thus, the TTimer will run in the main thread, and can be activated only by the main thread, not in the TService.OnStart event handler (an EOutOfResources exception will be raised if you try).
If you manually create the DM in the TService.OnStart event handler, it (and its TTimer) will be created in the service thread, not in the main thread. The TTimer will run in the service thread, and can be (de)activated in the TService events.
Either way, make sure your TTimer.OnTimer event handler uses thread-safe code.
Also, the TService.OnStop event handler must call the TService.ReportStatus() method periodically (before the TService.WaitHint interval elapses) while waiting for other threads to stop whatever they are doing. Which means you shouldn't use thread-blocking code in the TService.OnStop event handler.
You are not handling this correctly, which is why the SCM is having problems.

Application crash for unknown reasons on WinXP

I have Delphi XE3, Windows 7 Pro 64bit.
My application is working fine in my PC but my users told me application crashes upon start on their Win XP and also (!!) on their Win 7.
I tried to run the app on my Win 7 but logged as an ordinary user (no admin) - it works.
So now I have installed virtual machine with Windows XP and really - app crashes upon startup.
I need to find what can be the problem but I'm helpless.
As far as I can't use debugger (I dont have Delphi on that VM installed),
I tried to put some MessageBox(0, 'Hello', 'Test', MB_OK); in various places in my app to catch the place where it happens and this is what I found:
I have this in my project-source:
MessageBox(0, 'Hello', 'Test', MB_OK); // shows OK
Application.CreateForm(TfMain, fMain);
MessageBox(0, 'Hello', 'Test', MB_OK); // doesn't show - crash before this line
And this is in the OnCreate function of my main form called fMain:
procedure TfMain.FormCreate(Sender: TObject);
begin
MessageBox(0, 'Hello', 'Test', MB_OK); // doesn't show - crash before this line
...
So where can this app crash?
Not even first line in the OnCreate executes....
I have no idea... Anybody?
Don't know if this is important: I have some units in fMain uses clause under interface and also under implementation. Should I look there? But what happens just before OnCreate of my main form ?
Finally I got it !
place PRINT DIALOG component on your form (TPrintDialog)
set COPIES = 1 (or more than default zero) in the object inspector during design time
try to run such application on WinXP where NO PRINTERS are installed
Application just crashes upon start and in the details you will see only some kernel32.dll address...
I didn't test it on Win 7 without printers. I have no such system around...
Here's another way to track this down without Delphi on the VM...
Copy your project.
Remove all the units from Project Source except your main form.
Launch your application from XP see if it crashes.
If it crashes...then look at the units being dragged in from your main form...start removing them until your program stops crashing.
If it doesn't crash...start adding units/forms back into your project source until it crashes.
Do you have JCL/JVCL installed(JEDI)?
If so create a Logger...note the Logger needs to be created and hooked before your MainForm code executes...You will also need to set up a detailed Stack Trace from Unhandled Exceptions,
in Delphi select->Project/Options/Linker/Map file/Detailed}
You will need something like this in your Logger unit
procedure HookGlobalException(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean);
var
a_List: TStringList;
begin
if Assigned(TLogger._Instance) then
begin
a_List := TStringList.Create;
try
a_List.Add(cStar);
a_List.Add(Format('{ Exception - %s }', [Exception(ExceptObj).Message]));
JclLastExceptStackListToStrings(a_List, False, True, True, False);
a_List.Add(cStar);
// save the error with stack log to file
TLogger._Instance.AddError(a_List);
finally
a_List.Free;
end;
end;
end;
initialization
Lock := TCriticalSection.Create;
Include(JclStackTrackingOptions, stTraceAllExceptions);
Include(JclStackTrackingOptions, stRawMode);
// Initialize Exception tracking
JclStartExceptionTracking;
JclAddExceptNotifier(HookGlobalException, npFirstChain);
JclHookExceptions;
finalization
JclUnhookExceptions;
JclStopExceptionTracking;
Lock.Free;

EIntfCastError 'Interface not supported' when run as a TServiceApplication

I'm having problems using a COM-object when I run my application as a Windoes Service, i.e. TServiceApplication. The exception EIntfCastError 'Interface not supported' is raised.
If I run the application as a normal Delphi app then it works fine, including if I run as a service using srvany.exe
type IMyInter = interface (IUnknown)
['{9E6B311E-C6D3-4687-B272-3FBE9DBC2DD6}']
//...
end;
type
TMyObject = class
private
FMyInter: IMyInter;
published
constructor Create(const ClassID: TGUID);
end;
constructor TMyObject.Create(const ClassID:TGUID);
begin
CoInitialize(nil);
FMyInter := CreateComObject(ClassID) as IMyInter;
//....
end;
It seems like the error is raised after the call to CreateComObject when the result is going to be assigned to FMyInter. Both the application and COM-object are 32-bit. I'm running on Windows 7 64bit and using Delphi XE3. The COM-object has been registered with regsvr32.exe
Any help would be appreciated
I finally managed to solve the problem which resided on the COM-server side. When creating the object, i.e. TComObjectFactory.Create I changed the threading model from tmSingle to tmApartment. Then I unregistered and re-registered the server. Presto! Not quite sure why but it works for me.
...
initialization
TComObjectFactory.Create( ComServer, TMyComServerClass, Class_ComServerClassGUID, ‘My Com Server Class’, ‘My Descriptive text’, ciMultiInstance, tmApartment);

How to use Lookupqueue?

I use Delphi 2007 and I try to find out how to ask Windows (XP, Server 2003 or 2008) if a named MSMQ queue is installed. I have found this but it is in C++ so it is not easy to use from Delphi. Example, I have an installed queue named '.\private$\nctsinqueue'. It works fine to use it by:
var
QueueInfo : IMSMQQueueInfo2;
begin
QueueInfo := CoMSMQQueueInfo.Create;
The problem is that in some installations of Windows where my application is installed this queue does not exists. It depend of the preferences if a queue is needed. So I want to ask Windows if a named queue is installed and in that case I can go on with the code above.
EDIT:
Tried this code
function Test: Boolean;
var
QueueInfo : IMSMQQueueInfo2;
begin
Result := True;
QueueInfo := CoMSMQQueueInfo.Create;
QueueInfo.PathName := '.\private$\nonexistingqueue';
FQueue := QueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
end;
And it raise an exception on the last line. I can of course have a try/except here and return False in that case but I don't like to have exceptionhandling for this. I want to ask WinApi or something if the queue exists. Queue.IsOpen that kobik suggest only says if an existing queue is opened. It must of course exist before it can be opened.
Edit2:
I take a more practical approach to this, so I solved it with ini-files for my application.
It tries to open only if the queue is present in the ini-file.
Disadvantage is of course that the ini-file must be in sync with the queues in the system, but that part is rather static.

How can I make ADO database connections in 'TISAPIApplication` before processing incoming requests?

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.
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.
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.

Resources