Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

CoInitialize has not been called 1

Status
Not open for further replies.

Griffyn

Programmer
Jul 11, 2002
1,077
AU
Hi all,

Delphi XE6, and testing a new service application. Getting an EOleSysError exception "CoInitialize has not been called" when trying to connect to a Jet database. I'm using TADOConnection.

Except... I am calling it. Logging shows that CoInitialize(nil) gets called before the exception occurs. I've tried putting it in all sorts of places including in the TService.Create method before anything else happens. Still get the exception.

It's doubly weird because I realised that other similarly structured service applications (Delphi XE6, Jet, ADO) I've written recently are working fine, and I haven't called CoInitialize in them at all.

Feels like something weird is going on, eg. I haven't included a magic unit in my uses clause or something.

Thanks for your help.
 
More info to answer any questions:
[ul]
[li]The service application is single threaded. Functionality is gained through a TTimer event.[/li]
[li]I added code to check the result of the (only) CoInitialize I've added to the project - it returns S_FALSE, indicating CoInitialize has already been called. This is consistent with my other service application projects where I haven't had to worry about CoInitialize at all.[/li]
[li]I did a search in every *.pas file in my project folder, and my library folder looking for any custom unit that may be calling CoInitialize - none found aside from some Project JEDI units which I'm definitely not using in this project.[/li]
[/ul]

 
Not sure, but I think your problem my be related to the use of a TTimer within a service application. TTimer relies on a message pump to receive a WM_TIMER message. A normal VCL application sets up a message pump for you, but a console application or Service application do not. If you still want to develop your service as a single thread, then I suggest follow the instructions here.

Specifically:

Code:
procedure TCompanySqlDatabaseSpecialSomething.ServiceExecute(Sender: TService);
const
  SecBetweenRuns = 10;
var
  Count: Integer;
begin
  Count := 0;
  while not Terminated do
  begin
    Inc(Count);
    if Count >= SecBetweenRuns then //simulates timer event every 10 seconds.
    begin
      Count := 0;

      { place your service code here }
      { this is where the action happens }
      SomeProcedureInAnotherUnit;

    end;
    Sleep(1000); //now no timer is needed...the service sleeps for 1 sec 
    ServiceThread.ProcessRequests(False);
  end;
end;

Still make sure you call CoInitialize(nil) before and CoUninitialize after using ADO components, or better, in the initialization and finalization sections of your service.
 
It is simple really, make sure you are calling CoInitialize/CoUnItialize in the same context (thread).

Maljumbo, your suggestion of calling CoInitialize in the initialization section is wrong since it will be called on a different thread.

to expand your example:

Code:
procedure TCompanySqlDatabaseSpecialSomething.ServiceExecute(Sender: TService);
const
  SecBetweenRuns = 10;
var
  Count: Integer;
begin
 CoInitialize(nil);
 try 
  // now can act on COM objects (like the ADO components)
  Count := 0;
  while not Terminated do
  begin
    Inc(Count);
    if Count >= SecBetweenRuns then //simulates timer event every 10 seconds.
    begin
      Count := 0;

      { place your service code here }
      { this is where the action happens }
      SomeProcedureInAnotherUnit;

    end;
    Sleep(1000); //now no timer is needed...the service sleeps for 1 sec 
    ServiceThread.ProcessRequests(False);
  end;
 finally 
  CoUninitialize;
 end;
end;

/Daddy

-----------------------------------------------------
Helping people is my job...
 
Hi guys,

The TTimer is a red herring - the exception is occuring in the TService.OnStart event - I haven't even created the TTimer object yet. One of the first things to happen is open the connection to the MsAccess database to enable logging (fail over logging is to the system event log, which is how I know what is happening).

whorsdaddy - I've tried calling CoInitialize(nil) in the statement just prior to creating the TADOConnection object, as well as in the TService.OnCreate event. This service application doesn't create any additional threads.

I'll try and make a new project without any libraries and just copying the relevant code lines into the OnStart event and see if I can get it to fail with CoInitialize(nil) being called at the beginning.
 
The problem is that TService automatically creates a new TServiceThread on its own when it starts. Try moving all your ADO startup code into the TService.OnStart event along with the CoInitialize statement.
 
Ok. I've copied bits of code out of my framework library into a single unit that compiles and still faults. You can test the code below by creating a new Service Application in Delphi, then replacing the main unit with everything below, and attaching the 5 events listed in TService1 using the Object Inspector.

Compile, install and Start to test. It creates a log file in the same folder with what each Start has performed.

You can change the ForceFault constant at the beginning of the implementation section to either fault or not to show the weirdness that's happening.

TServiceMate works by pointing the TService events to it's own to handle automatic logging and logic class handling. When used, TService1.ServiceStart and TService1.ServiceStop aren't called.

As you can see, there's no threads, or other objects being created aside from what's right here.

With ForceFault=True, the FConnection.Open causes an exception because CoInitialize hasn't been called (even though you can see it has, and the log shows it has tried, although the return result shows that it had already been called?). With ForceFault=False, TServiceMate is bypassed, and FConnection.Open works fine, and CoInitialize returns S_OK, rather than S_FALSE.

None of it makes any sense to me. Using Delphi XE6 Update 1.

Code:
[b]unit[/b] mainsvc;

[b]interface[/b]

[b]uses[/b]
  Winapi.Windows, System.SysUtils, Vcl.SvcMgr, ADODB;

[b]type[/b]
  TLogic = [b]class[/b]
  [b]private[/b]
    FConnection: TADOConnection;
  [b]public[/b]
    [b]constructor[/b] Create;
    [b]destructor[/b] Destroy; [b]override[/b];
    [b]procedure[/b] Initialise;
  [b]end[/b];

  TServiceMate = [b]class[/b]
  [b]private[/b]
    FAppLogic: TLogic;
    FDescription: [b]String[/b];
    [b]procedure[/b] ServiceStart(Sender: TService; [b]var[/b] Started: Boolean);
    [b]procedure[/b] ServiceStop(Sender: TService; [b]var[/b] Stopped: Boolean);
  [b]public[/b]
    [b]constructor[/b] Create(AService: TService; ADescription: [b]String[/b]);
    [b]destructor[/b] Destroy; [b]override[/b];
    [b]property[/b] AppLogic: TLogic [b]read[/b] FAppLogic [b]write[/b] FAppLogic;
  [b]end[/b];

  TService1 = [b]class[/b](TService)
    [b]procedure[/b] ServiceCreate(Sender: TObject);
    [b]procedure[/b] ServiceDestroy(Sender: TObject);
    [b]procedure[/b] ServiceStart(Sender: TService; [b]var[/b] Started: Boolean);
    [b]procedure[/b] ServiceStop(Sender: TService; [b]var[/b] Stopped: Boolean);
    [b]procedure[/b] ServiceExecute(Sender: TService);
  [b]private[/b]
    FLogic: TLogic;
    FSvcMate: TServiceMate;
  [b]public[/b]
    [b]function[/b] GetServiceController: TServiceController; [b]override[/b];
    [navy]{ Public declarations }[/navy]
  [b]end[/b];

  [b]procedure[/b] Log(AMessage: [b]String[/b]);

[b]var[/b]
  Service1: TService1;

[b]implementation[/b]

[b]uses[/b]
 ActiveX;

[b]const[/b]
  ForceFault = True;

[b]var[/b]
  GLogFile: [b]String[/b];

[navy]{$R *.DFM}[/navy]

[b]procedure[/b] Log(AMessage: [b]String[/b]);

[b]var[/b]
  t : TextFile;
[b]begin[/b]
  AssignFile(t, GLogFile);
  [b]if[/b] FileExists(GLogFile) [b]then[/b]
    Append(t)
  [b]else[/b]
    Rewrite(t);
  [b]try[/b]
    WriteLn(t, DateTimeToStr(Now) + [teal]': '[/teal] + AMessage);
  [b]finally[/b]
    CloseFile(t);
  [b]end[/b];
[b]end[/b];

[b]procedure[/b] ServiceController(CtrlCode: DWord); stdcall;
[b]begin[/b]
  Service1.Controller(CtrlCode);
[b]end[/b];

[b]function[/b] TService1.GetServiceController: TServiceController;
[b]begin[/b]
  Result := ServiceController;
[b]end[/b];

[b]procedure[/b] TService1.ServiceCreate(Sender: TObject);
[b]begin[/b]
  GLogFile := ChangeFileExt(ParamStr([purple]0[/purple]), [teal]'.log'[/teal]);
  DeleteFile(GLogFile);
  [b]if[/b] ForceFault [b]then[/b]
  [b]begin[/b]
    FSvcMate := TServiceMate.Create(Self, [teal]'Test CoInitialise service'[/teal]);
    FSvcMate.AppLogic := TLogic.Create;
  [b]end[/b];
[b]end[/b];

[b]procedure[/b] TService1.ServiceDestroy(Sender: TObject);
[b]begin[/b]
  FSvcMate.Free;
[b]end[/b];

[b]procedure[/b] TService1.ServiceExecute(Sender: TService);
[b]begin[/b]
  Log([teal]'ServiceExecute entered'[/teal]);
  [b]with[/b] Sender [b]do[/b]
    [b]while[/b] [b]not[/b] Terminated [b]do[/b]
      ServiceThread.ProcessRequests(True);
[b]end[/b];

[b]procedure[/b] TService1.ServiceStart(Sender: TService; [b]var[/b] Started: Boolean);
[b]begin[/b]
  FLogic := TLogic.Create;
  FLogic.Initialise;
[b]end[/b];

[b]procedure[/b] TService1.ServiceStop(Sender: TService; [b]var[/b] Stopped: Boolean);
[b]begin[/b]
  FLogic.Free;
[b]end[/b];

[navy]{ TLogic }[/navy]

[b]constructor[/b] TLogic.Create;
[b]begin[/b]
  [b]inherited[/b];
  [b]case[/b] CoInitialize([b]nil[/b]) [b]of[/b]
    E_INVALIDARG : Log([teal]'CoInitialize failed: E_INVALIDARG'[/teal]);
    E_OUTOFMEMORY: Log([teal]'CoInitialize failed: E_OUTOFMEMORY'[/teal]);
    E_UNEXPECTED: Log([teal]'CoInitialize failed: E_UNEXPECTED'[/teal]);
    S_OK: Log([teal]'CoInitialize completed successfully'[/teal]);
    S_FALSE: Log([teal]'CoInitialize already called'[/teal]);
    RPC_E_CHANGED_MODE: Log([teal]'CoInitialize already called with different concurrency model'[/teal]);
  [b]else[/b]
    Log([teal]'Unknown response from CoInitialize'[/teal]);
  [b]end[/b];
  FConnection := TADOConnection.Create([b]nil[/b]);
[b]end[/b];

[b]destructor[/b] TLogic.Destroy;
[b]begin[/b]
  FConnection.Free;
  CoUninitialize;
  [b]inherited[/b];
[b]end[/b];

[b]procedure[/b] TLogic.Initialise;
[b]const[/b]
  DBName = [teal]'c:\test.mdb'[/teal];
[b]begin[/b]
  FConnection.ConnectionString := [teal]'Provider=Microsoft.Jet.OLEDB.4.0;Data Source='[/teal]
    + DBName + [teal]';Persist Security Info=False;'[/teal];
  FConnection.LoginPrompt := False;
  [b]try[/b]
    Log([teal]'About to connect to DB'[/teal]);
    FConnection.Open;
    Log([teal]'Connected ok'[/teal]);
  [b]except[/b]
    [b]on[/b] E: Exception [b]do[/b]
    [b]begin[/b]
      Log(Format([teal]'(%s) %s'[/teal], [E.ClassName, E.Message]));
      [b]raise[/b];
    [b]end[/b];
  [b]end[/b];
[b]end[/b];

[navy]{ TServiceMate }[/navy]

[b]constructor[/b] TServiceMate.Create(AService: TService; ADescription: [b]String[/b]);
[b]begin[/b]
  Log([teal]'ServiceMate.Create entered'[/teal]);
  [b]inherited[/b] Create;
  Log([teal]'ServiceMate.Create inherited'[/teal]);
  FDescription := ADescription;
  [b]if[/b] Assigned(AService) [b]then[/b]
  [b]begin[/b]
    AService.OnStart := ServiceStart;
    AService.OnStop := ServiceStop;
    Log([teal]'ServiceMate.Create adding service'[/teal]);
  [b]end[/b];
  FAppLogic := [b]nil[/b];
  Log([teal]'ServiceMate.Create complete'[/teal]);
[b]end[/b];

[b]destructor[/b] TServiceMate.Destroy;
[b]begin[/b]
  FreeAndNil(FAppLogic);
  [b]inherited[/b];
[b]end[/b];

[b]procedure[/b] TServiceMate.ServiceStart(Sender: TService; [b]var[/b] Started: Boolean);
[b]begin[/b]
  Log([teal]'ServiceMate.ServiceStart entered'[/teal]);
  [b]if[/b] Assigned(FAppLogic) [b]then[/b]
  [b]try[/b]
    Log([teal]'Initialising logic'[/teal]);
    FAppLogic.Initialise;
    Log([teal]'Service started'[/teal]);
    Started := True;
  [b]except[/b]
    [b]on[/b] E: Exception [b]do[/b]
    [b]begin[/b]
      Log(Format([teal]'(%s) %s'[/teal], [E.ClassName, E.Message]));
      Started := False;
    [b]end[/b];
  [b]end[/b];
[b]end[/b];

[b]procedure[/b] TServiceMate.ServiceStop(Sender: TService; [b]var[/b] Stopped: Boolean);
[b]begin[/b]
[b]end[/b];

[b]end[/b].




 
Hi Prattaratt,

I saw your comment after I'd posted my code - and can confirm you're correct. By moving all the code from TLogic.Create to TLogic.Initialise, it doesn't fault. Vice versa, moving TLogic.Create from TService1.ServiceStart to TService1.ServiceCreate (as an else to ForceFault), it will always fault.

Finally it makes sense. Thank you!

 
Griffyn,

did you see my code?
The execute block will run in a different thread context as you found out.
Keep all COM related code in the same thread and you are good to go...

/Daddy

-----------------------------------------------------
Helping people is my job...
 
Hi whosrdaddy,

I did see your code, but I didn't realise that TService.Create and TService.Start run in different threads, so when I first read through it I figured it didn't really apply. It would have solved my problem of course. My TService.Execute events never contain more than the service thread loop shown in my code above.

I know you have extensive experience with service applications, and the framework I'm developing has tried to adhere to all the good points you've given out over the years. Would you recommend I abandon the TService.Create and TService.Start events and have everything execute in TService.Execute?
 
The point is that you don't need to implement TService.Execute.
Just use the Start and Stop events to fire up your worker thread.
Execute the inialization block right at the beginning of the worker thread Execute method and finalize and the end of this method.
Doing so will have multiple advantages:
- you remove the dependency on the service application, this means that you can test easily (create a forms app and create your workerthread)
- you will never run into problems like you saw with CoInitialize and stuff

My service applications are always 2 in one :
- a forms application
- a service application

depending on a startup parameter I can start the forms application which is used to configure the service.

I will see if I can post the barebone bits here...

/Daddy

-----------------------------------------------------
Helping people is my job...
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top