Have you ever been thinking about something and started noticing it all over the place? I find this happens when looking to buy a car. This phenomenon started happening for me lately with CoInitialize. CoInitialize has come up a dozen or more times in the last few months and then again last week, so I decided to start a little series on CoInitialize and CoUninitialize. Since this is a common source of problems for COM applications, even people who know better, I think everyone will benefit from reading this.
In this post I want to go over a bit more than just CoInitialize and CoUniniitalize, I want to show you how Delphi compiler magic can cause you problems when initializing the COM Library. Take the following program, that does just about nothing but cause an error:
program Project1;
{$APPTYPE CONSOLE}
uses SysUtils, ActiveX, XMLintf, XMLDoc;
var
Document: IXMLDocument;
begin
CoInitialize(nil);
Document := TXMLDocument.Create('foo.xml');
CoUninitialize;
end.
This very simple program, calls CoInitialize, creates a TXMLDocument and then calls CoUninitialize. The failure happens after the CoUninitialize. Do you know why?
Delphi manages the lifetime of interfaces calling IUnknown._AddRef on an interface assignment and IUnknown._Release on cleanup of the method. In this case, the COM Library has already been cleaned up when the destructor of TXMLDocument occurs. So really this very simple program, calls CoInitialize, creates a TXMLDocument, increments the reference count of IXMLDocument, calls CoUninitialize, decrements the reference count and destroys the object. We need to change the program so the call to IUnknown_Release happens before CoUninitialize. The simplest way to solve this problem is to change the program by adding a method:
program Project2;
{$APPTYPE CONSOLE}
uses SysUtils, ActiveX, XMLintf, XMLDoc;
procedure DoSomething;
var
Document: IXMLDocument;
begin
Document := TXMLDocument.Create('foo.xml');
end;
begin
CoInitialize(nil);
DoSomething;
CoUninitialize;
end.
Now this very simple program, calls CoInitialize, creates a TXMLDocument, increments the reference count of IXMLDocument, decrements the reference count and destroys the object, calls CoUninitialize.
31 comments:
So, the whole point of adding another method is letting the compiler know that it's done with the IXMLDocument by the time one calls CoUninitialize?
Would it be feasible to add CoInitialize and CoUninitialize to the start/finish sections of some used unit (taking care to ensure it was the first one listed on the program)?
BTW: Hallvard's blog is listed twice! ;)
Fernando,
Correct, the whole point of the function is to change the order of the calls and force when the compiler generate calls happen.
I'll talk about the initialization and finalization sections in another post, but short answer is that can cause problems. You would never want to add CoInitialize to an initialization section of a unit in a DLL, so that would not be a "general" solution.
Not tested, but wouldn't even a simpeler solution be adding the line:
Document := nil;
before CoUnitialize;
???
Thanks Chris, I was getting a AV in some code and it was because the calls to CoInitialize and CoUninitialize in the same method where my interfaces were being created and used.
Chris Miller,
Glad my blog could help!
hi,
do u have an example for using C++?
Did you ever follow up with a post on Initialization and Finalization as mentioned to Fernando? What you are saying is a bad idea is exactly what I'm doing, and I do have a probelm so would like to know the correct way of doing it. Have searched your blogs, but not found such a post.
My DLL is to have an instance of a form available all the time it is loaded by the host application. Because a DLL does not have a Finalization, I have a unit in my DLL project which creates a form in the Initialization, and free's the form in Finalization. This is also where I am calling CoInitialize and CoUnInitialize.
initialization
CoInitialize(nil);
ExportForm := TfrmExport.Create(nil);
finalization
ExportForm.Free;
CoUninitialize;
I find that if I have ever activated/deactivated the ADOTable on my form, then on unloading the DLL the call to CoUninitialize never returns, whereas if I have never activated the table it is ok.
Where should I CoInit and CoUninit Chris?
Hi, before you go to too much effort into replying I have changed my DLL to export InitialiseDLL and UninitialiseDLL procedure which now do the creation of the form & CoInitialize, and freeing the form & CoUnInitialize.
My application then always calls InitialiseDLL after loading the DLL, and UnInitialiseDLL before unloading the DLL.
This seems to work (or I've just not found the failure yet). I this a better way of doing it? Thanks in advance.
Sox,
The best practice for calling CoInitialize and CoUninitialize is to call them from an EXE. You do not want to call them from within a DLL. The reason for this is you don't want to leave the application in a state where COM isn't initialized. I know this is problematic and Microsoft software such as IE doesn't follow their own rules, that's why it is a best practice. So I would add the calls to your application in the .dpr or very early on and when the program terminates. If your DLL is to be used by 3rd parties then you need to decide if you want to require them be called or you just call them yourself. If you call them yourself make sure you check the return value and only call CoUninitialize if the call succeeded.
Hi Sky Angel,
I do not have examples in C++, but these examples should be fairly easy to port to C++.
OK, I know nothing about Delphi, just Googling for a C++ CoUninitialize problem of my own. In C++ we would say that the COM object has to go out of scope before you call CoUninitialize so that it gets cleaned up before the call. In C++, you might run into an analogous situation that could be solved by putting curly brackets around your COM object usage, with the CoInit/CoUninit outside the curly brackets.
(By the way, my conundrum involves using COM objects from different threads in a multithreaded app, which calls OleInitialize on startup. I have a spot in the code where I have to call CoUnitialize twice before calling CoInitializeEx, in order for it to work! I feel I should take a little time to figure out why, instead of just adding "// I dunno why, but whatever you do, leave this second CoUninit call in!!")
Anonymous,
Delphi is the same. The managed object must go out of scope before it is safe to call CoUninitialize.
OleInitialize should only be called from the main thread and calls to OleInitialize/CoInitialize/CoInitializeEx should only affect the current thread. It sounds like you may be using a library which makes theses calls for you.
Do any of you know why I am getting Page Fault count increasing when I use XML Binding with IXMLDocument? GetServerList isa function that xml binding wizard created that returns IXMLServerListType. For some reason my Page Faults keeps increasing rapidly when doing this over and over again. Any ideas?
procedure TForm1.ProcessXML(XML: String);
var
ServerList: IXMLServerListType;
begin
ServerList := GetServerList(LoadXMLData(XML));
end;
Chris,
We have a 3rd party DLL we need to access from Delphi 2007. We tried importing the type libary and using the classes generated from this but we get Access Violations.
Is there a way to explicitly call the DLL without using the imported type libary? The VB example program works but we can not find documentation on how to translate VB to Delphi. Got any suggestions on how to go about doing this task? Here is the example VB code:
VERSION 5.00
Begin VB.Form Form1
Caption = "Get_Definitions"
ClientHeight = 5415
ClientLeft = 60
ClientTop = 345
ClientWidth = 10065
LinkTopic = "Form1"
ScaleHeight = 5415
ScaleWidth = 10065
StartUpPosition = 3 'Windows Default
Begin VB.TextBox Text1
Height = 4815
Left = 120
MultiLine = -1 'True
ScrollBars = 2 'Vertical
TabIndex = 0
Top = 480
Width = 9735
End
Begin VB.Label Label1
Caption = "Process Definitions:"
Height = 255
Left = 120
TabIndex = 1
Top = 120
Width = 2655
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' Get process definitions on a folder.
' ---Modify the following variables with proper information. ----------------
Const SERVER_IP = "testserverip"
Const SERVER_PORT = 7201
Const LOGIN_ID = "username"
Const PASSWORD = "password"
Const SERVER_URI = "http://testserverip:8080/workflow/rpcrouter"
Const FOLDER_ID = 52 ' 52 is the ID of "Process Definitions" folder.
' ---------------------------------------------------------------------------
Private Sub Form_Load()
Dim oSession As New HWSession
Dim oProcDefs As HWProcessDefinitions
Dim oProcDef As hwProcessDefinition
On Error GoTo Err:
' Login
oSession.ServerIP = SERVER_IP
oSession.ServerPort = SERVER_PORT
oSession.Protocol = hwProtocolTCPIP
oSession.URI = SERVER_URI
oSession.Login LOGIN_ID, PASSWORD, True
' Get prcoess definitions on the folder
Set oProcDefs = oSession.GetProcessDefinitionsByFolder(oSession.ServerID, FOLDER_ID)
For i = 1 To oProcDefs.Count
Set oProcDef = oProcDefs.Item(i)
' Get name of the process definition
Text1.Text = Text1.Text + oProcDef.Name + vbCrLf
Set oProcDef = Nothing
Next
oSession.Logout
Set oProcDefs = Nothing
Set oSession = Nothing
Exit Sub
Err:
MsgBox "Error!!! " & Err.Number & "," & Err.Description
End Sub
Regards,
Bruce
Nebraska Workers' Compensation
Bruce,
I can't say why you would get an AV. That reminds me of a sparse v-table issue Delphi had in previous versions when calling VB COM objects. Here is a post about that subject Fun With V-Tables. I'm not all that familiar with VB, but you should be able to export a function that you could then call with Delphi using LoadLibrary and GetProcAddress. Good luck!
Chris,
Our App is developed in Delphi 2007 and we have an API for clients/vendors to access some of our functionality. One of our API users was getting errors calling our API using a Visual Studio 2005 executable. We had never had this problem before, and I tracked it down to some new code we added for reading xml documents. I was getting an “CoInitialize not called” error when trying to set the active flag on the IXMLDocument to true. So, I tried calling CoInitialize and CoUninitialize. This allowed the function to execute correctly, but when the executable closed it throws an AV (just like your example in the blog). I’ve tried several of your suggestions about where to put the CoInit and CoUnint without any success. I’ve even tried to put them in the MS executable with the same result. I’m not starting another thread, but what I assume is happening is for some reason VS 2005 isn’t doing this when it creates the executable. Calling the same code (without CoInit) works fine from a Delphi executable. Any help or suggestions would be much appreciated! Thanks for your time…
James
Hi Chris
Your explanation on CoInitialize and CoUninitialize has helped solve a puzzle with Delphi 5 and AcroPDF dll. With version 9 of Adobe reader and llPDFLibX.dll v3.1, the application exe began AV on closing. Adding CoUninitialize only, to the destructor in the pdf viewer unit 'fixes' the bug.
Paul
James,
I replied to your email.
Paul,
Awesome, glad I could help!
Hi Chris, very helpful explanation.
Still I want to understand more about delphi and COM. I have a nice solution, where you don't have to worry about uninitialize or makeing procedures.
// same project ------------>
program Project1;
{$APPTYPE CONSOLE}
uses SysUtils, ActiveX, XMLintf, XMLDoc;
type
TceCOMinit = class(TInterfacedObject, IUnknown)
private
NeedToUninitialize: Boolean;
public
constructor Create(const CO_INIT:integer=COINIT_APARTMENTTHREADED);
destructor Destroy; override;
end;
{ TceCOMinit }
constructor TceCOMinit.Create(const CO_INIT: integer=COINIT_APARTMENTTHREADED);
begin
inherited Create;
NeedToUninitialize := Succeeded(CoInitializeEx(nil, CO_INIT));
end;
destructor TceCOMinit.Destroy;
begin
if NeedToUninitialize then
CoUninitialize();
inherited;
end;
function NewCoinit(const CO_INIT: integer=COINIT_APARTMENTTHREADED):IUnknown;
begin
Result := TceCOMinit.Create(CO_INIT) as IUnknown;
end;
var
Document: IXMLDocument;
begin
NewCoinit();//CoUninit will get called last
Document := TXMLDocument.Create('foo.xml');
end.
Vojko
I just had a weird issue with Delphi 2007 and CoInitialize/CoUninitialize.
I have a service that must perform some WMI calls. This service creates several threads, so I can not use CoInitialize/Uninitialize in the initialization/finalization sections of my generic "WMI" unit (This unit implements functions such as WMIGetString, WMIGetStringList, WMIRunMethod etc).
Then, I used to call CoInitialize at the beginning of each function that was supposed to use a "WMI function" and Uninitialize at the end. It seemed to work well.
I developed "proof of concept" applications that all worked well. I had a console application (not a service) and a Delphi Service application.
Unfortunately, I had to get notified when the system was about to shutdown.
Since TService in Delphi does not allow to get SERVICE_CONTROL_PRESHUTDOWN control code, I ported my TService to a "console as service" application in which I do the "service stuff" by myself (DispatchTable, RegisterServiceCtrlHandlerEx etc).
And here comes the weird stuff.
After the first call to CoUnitialize in my WMI unit, the stack of the thread became corrupted! Especially, the return address was not correct anymore.
I solved the issue by removing CoInitialize/CoUnitialize from the functions in my WMI unit and call them in all the threads' execute functions when said threads were supposed to use any function of my WMI unit.
I did not have the time to investigate using a debugger, and since I imagine that I would need to trace all calls made by CoUnitialize to understand what happens, and why this does happen only with my "console as service" application, I think that I will never find the time to investigate...
MrTheV, From your description it sounds like your solution is the correct one; Each thread should initialize COM (CoInitialize/CoUninitialize). Depending on your threading needs you will want to use CoInitializeEx...
Hi Chris,
I recently had to deal with this issue for a TXMLDocument and I found your blog very helpful.
My question is, where did this additional thread come from (I'm not using any TThread objects)?
e: sam-english@hotmail.com
Sam, the sample code I provided doesn't deal with threads. Other examples in the comments are working with threads. Do you have a thread in your project?
Just the main thread I presume, since I don't created any threads myself. My thinking is, if Delphi automatically calls CoInitialize for the main thread, and I haven't created any other threads, then if I get a message that CoInitialize hasn't been called, then there must be another thread or Delphi didn't even call CoInitialize for the main thread. So I'm wondering if the TXMLDocument component that I'm using is creating a new thread by itself? The strange thing is that the code used to work without CoInitialize, but now it won't work without it... I'm quite sure that I haven't introduced this into my code. I'm also wondering if installing FastMM and madExcept might have had an impact on this issue (because they are the most notable things I've done in this timeframe).
What do you think?
Hi,
There was a very simple explanation for why the bug happened. When you declared the
var
Document: IXMLDocument;
this means that the compiler using the interface as "variable". As soon as the method is finished, the compiler purges all local variables, including the interface you just created, and by default, calls the IUnknown._Release.
Of course, there are more details behind it but clears up for short explanation...
@FSPeralta - You got the idea!
Thanks for sharing this sample about Delphi
Hi Chris,
This looks very interesting, and raises hope that it might be the solution to all my AV's when using TADOConn's. Is this applicable to all versions of Delphi, including BD7 and XE?
I'm a C#:er and gosh darnit I've been about to throw the 'puter out the window over these AV exceptions after being forced to "go delphi" in a new project. Like, wtf... :)
Nope, that didn't do it. Seems like I'm just a noob at configuring compiler/building options in RAD studio XE ...
Currently, I have been having some problem with DLL, that is how I saw your post. Your suggestion has helped me with CoinIT, so thanks for this information.
Post a Comment