Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

WMI/WBem console app: access violation

85 views
Skip to first unread message

p.ka...@atlascollege.nl

unread,
Apr 23, 2007, 10:20:54 AM4/23/07
to
Hi all,

SideNote:
I am trying make a console app which will have to return the
number of connections to a certain terminal server. The final
program wil be used as a pluging for NRPE/Nagios.

I found a WMI class which will return the information I need:
win32_perfformatteddata__termService_terminalServices

I found a delphi example to implement a query to WMI:
http://www.swissdelphicenter.ch/torry/showcode.php?id=2135

I remade the example to use the WMI class I need, and it runs just
fine.... in a GUI app. But I need it to be a console app. So I rewrote
the code:
===========================snip=======================================
program TsProcesses;

{$APPTYPE CONSOLE}

uses
Windows,
Controls,
Variants,
SysUtils,
ActiveX,
WbemScripting_TLB;

function ADsEnumerateNext(pEnumVarian: IEnumVARIANT;
cElements: ULong;
var pvar: OleVARIANT;
var pcElementsFetched: ULong): HRESULT;
safecall; external 'activeds.dll';

procedure DumpWMI_Process(Process: SWBemObject);
var
Enum: IEnumVARIANT;
varArr: OleVariant;
lNumElements: ULong;
SProp: ISWbemProperty;
Prop: OleVariant;
PropName: string;
PropType: string;
PropValue: string;
begin
writeln('+ WMI Path: '+Process.Path_.Path);
Enum:= Process.Properties_._NewEnum as IEnumVariant;
while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements)))
and
(lNumElements > 0) do
begin
if
(Succeeded(IDispatch(varArr).QueryInterface(SWBemProperty,SProp))) and
Assigned(SProp) then
begin
try
PropName:= SProp.Name;
Prop:= SProp.Get_Value;
PropType:= VarTypeAsText(VarType(Prop));
PropValue:= VarToStr(Prop);
writeln(' + '+PropName+'['+PropType+'] = '+PropValue);
except
on E: Exception do
begin
writeln('Oeps');
end;
end;
end;
end;
Enum._Release;
end;

procedure WmiQuery();
var
Enum: IEnumVARIANT;
varArr: OleVariant;
lNumElements: ULong;
begin
//Run the WMI Query
CoInitializeEx(nil,0);
Enum:= CosWbemLocator.Create.ConnectServer('','root
\cimv2','','','','',0,nil).ExecQuery(
'Select * from
Win32_PerfFormattedData_TermService_TerminalServices',
'WQL', wbemFlagBidirectional, nil)._NewEnum as IEnumVariant;
while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements)))
and
(lNumElements > 0) do
begin
DumpWMI_Process(IUnknown(varArr) as SWBemObject);
end;
Enum._Release;
CoUninitialize()
end;

begin
{ TODO -oUser -cConsole Main : Insert code here }
writeln('Hello World');
WmiQuery();
writeln('Bye World');
end.
===========================snap=======================================

At first I got a complaint that I needed to call CoInitialize(), so I
did.
Seemed logical that I had to - some C++ sample on the MS site I had
read
before allready mentioned that - but I allready started to wonder why
the
example I had did not make that call.

Having done that the program seems to work, but fails with an access
violation.

Again with reference to te MS C++ article I added the Enum._Release
and
CoUnitialize(). But it still fails with an access violation before
the
writeln('Bye World').

Sure hope someone can shed some light on this mistery...

Peter

Maarten Wiltink

unread,
Apr 23, 2007, 4:47:20 PM4/23/07
to
<p.ka...@atlascollege.nl> wrote in message
news:1177338054.1...@n76g2000hsh.googlegroups.com...
[...]

> Again with reference to te MS C++ article I added the Enum._Release
> and CoUnitialize(). But it still fails with an access violation
> before the writeln('Bye World').

It would seem that Delphi has better COM interface support than the
compiler expected by the sample code. _Release is called automatically
by Delphi programs when a reference is lost, by a variable either
being reassigned or going out of scope. The lack of a corresponding
_AddRef call in the code is especially suspicious (and also handled
automatically by Delphi).

Sounds like you have a double-free due to poor interface management.
Remove the _Release call and see what happens.

Groetjes,
Maarten Wiltink


Peter Kaagman

unread,
Apr 23, 2007, 5:13:59 PM4/23/07
to
On 23-4-2007 22:47:16, "Maarten Wiltink" wrote:
> <p.ka...@atlascollege.nl> wrote in message
> news:1177338054.1...@n76g2000hsh.googlegroups.com...
> [...]
>> Again with reference to te MS C++ article I added the Enum._Release
>> and CoUnitialize(). But it still fails with an access violation
>> before the writeln('Bye World').
>
> It would seem that Delphi has better COM interface support than the
> compiler expected by the sample code. _Release is called automatically
> by Delphi programs when a reference is lost, by a variable either

That is exactly the reason why I prefer pascal/Delphi. But bear in mind that
I changed the code to make it work. The origional sample was intended for a
Delphi VCL/Gui app. It did not have any reference to CoInitialize,
CoUnitialize, _Release or any of that sort.... those were added by me trying
to get it to work in my console program.

> Sounds like you have a double-free due to poor interface management.
> Remove the _Release call and see what happens.

Tried it before, and just now again... but it does not make a difference.
It just keeps coming up with the access violation error.

The other thing I keep wondering about is the CoInitialize. It was not there
in the VCL/GUI sample (which I got from that site). But that app just worked
fine. Makes me think that the VCL/GUI apps are COM apps by themselves and
that the COM initialisation and de-initialisation is implicitly called by the
apps initialisation.

_After_ I added the CoInitialize the IDE would no longer complain about it
not being called. And the query would return the results I expected. It would
just crash before the writeln('Bye World').
The CoUnitialize does not make a difference either, the program does not even
reach that point in the code (just checked it with a breakpoint)

>
> Groetjes,
> Maarten Wiltink
>
>

Anyway.... thanks for your time spend on this problem...

Groetjes terug

Peter Kaagman

Rob Kennedy

unread,
Apr 23, 2007, 7:26:40 PM4/23/07
to

That's not the right declaration. Either change the calling convention,
or remove the function's return value.

The following should all be equivalent declarations:

procedure ADsEnumerateNext(pEnumVarian: IEnumVARIANT; cElements: ULong;
out pvar: OleVARIANT; out pcElementsFetched: ULong); safecall;

function ADsEnumerateNext(pEnumVarian: IEnumVARIANT; cElements: ULong;

out pvar: OleVARIANT; out pcElementsFetched: ULong): HResult; stdcall;

function ADsEnumerateNext(pEnumVarian: IEnumVARIANT; cElements: ULong;

out pvar: OleVARIANT): ULong; safecall;

Declaring something as safecall causes the compiler to *assume* it is a
function that returns HResult, so you cannot provide that return value
yourself. When you call a safecall function, the compiler will check the
returned HResult value for you, and if it fails, the compiler generates
an exception instead of passing back the original HResult.

When a safecall routine is declared as a function, the compiler assumes
that the return value will be passed from the DLL as the last "out"
parameter.

> procedure DumpWMI_Process(Process: SWBemObject);
> var
> Enum: IEnumVARIANT;
> varArr: OleVariant;
> lNumElements: ULong;
> SProp: ISWbemProperty;
> Prop: OleVariant;
> PropName: string;
> PropType: string;
> PropValue: string;
> begin
> writeln('+ WMI Path: '+Process.Path_.Path);
> Enum:= Process.Properties_._NewEnum as IEnumVariant;
> while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements)))
> and
> (lNumElements > 0) do
> begin
> if
> (Succeeded(IDispatch(varArr).QueryInterface(SWBemProperty,SProp))) and
> Assigned(SProp) then

I think you can replace that line with this:

if Supports(IDispatch(varArr), SWBemProperty, SProp) then

> begin
> try
> PropName:= SProp.Name;
> Prop:= SProp.Get_Value;
> PropType:= VarTypeAsText(VarType(Prop));
> PropValue:= VarToStr(Prop);
> writeln(' + '+PropName+'['+PropType+'] = '+PropValue);
> except
> on E: Exception do
> begin
> writeln('Oeps');

Write a little more than that. Include the exception's type name and
message, too.

> end;
> end;
> end;
> end;
> Enum._Release;

That's not necessary. Delphi already releases interface types for you
when the variable goes out of scope.

> end;
>
> procedure WmiQuery();
> var
> Enum: IEnumVARIANT;
> varArr: OleVariant;
> lNumElements: ULong;
> begin
> //Run the WMI Query
> CoInitializeEx(nil,0);
> Enum:= CosWbemLocator.Create.ConnectServer('','root
> \cimv2','','','','',0,nil).ExecQuery(
> 'Select * from
> Win32_PerfFormattedData_TermService_TerminalServices',
> 'WQL', wbemFlagBidirectional, nil)._NewEnum as IEnumVariant;

That's quite a lot to be performing all in one statement. I suggest
breaking it up into multiple statemens. Say, one method call per line.

> while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements)))
> and
> (lNumElements > 0) do
> begin
> DumpWMI_Process(IUnknown(varArr) as SWBemObject);
> end;
> Enum._Release;

Instead of Release, set the variable to nil.

> CoUninitialize()

This could lead to problems. There are still a bunch of interface
references active in this WmiQuery function. You mustn't uninitialize
COM until all those objects have been destroyed. The interfaces aren't
cleared until the compiler-generated code runs while WmiQuery returns,
which occurs after CoUninitialize gets called above.

It's better to call CoInitialize and CoUninitialize just once in your
program. Do this in your program's main begin-end block, or in the
initialization and finalization sections of a unit. Don't initialize and
uninitialize COM every time you want to call a COM-related function.

> end;
>
> begin
> { TODO -oUser -cConsole Main : Insert code here }
> writeln('Hello World');
> WmiQuery();
> writeln('Bye World');
> end.
> ===========================snap=======================================
>
> At first I got a complaint that I needed to call CoInitialize(), so I
> did.
> Seemed logical that I had to - some C++ sample on the MS site I had
> read
> before allready mentioned that - but I allready started to wonder why
> the
> example I had did not make that call.

There is a Delphi unit -- I don't recall which one -- that already calls
CoInitialize in its initialization section. That's why most Delphi
programs don't need to call it in their main threads.

--
Rob

Peter Kaagman

unread,
Apr 27, 2007, 4:42:47 AM4/27/07
to
Ok.... should have reacted sooner... but this problem is frustrating me a bit
and I've put it aside for a bit.

On 24-4-2007 1:26:41, Rob Kennedy wrote:


> p.ka...@atlascollege.nl wrote:
>> function ADsEnumerateNext(pEnumVarian: IEnumVARIANT;
>> cElements: ULong;
>> var pvar: OleVARIANT;
>> var pcElementsFetched: ULong): HRESULT;
>> safecall; external 'activeds.dll';
>
> That's not the right declaration. Either change the calling convention,
> or remove the function's return value.
>
> The following should all be equivalent declarations:
>
> procedure ADsEnumerateNext(pEnumVarian: IEnumVARIANT; cElements: ULong;
> out pvar: OleVARIANT; out pcElementsFetched: ULong); safecall;
>
> function ADsEnumerateNext(pEnumVarian: IEnumVARIANT; cElements: ULong;
> out pvar: OleVARIANT; out pcElementsFetched: ULong): HResult; stdcall;
>
> function ADsEnumerateNext(pEnumVarian: IEnumVARIANT; cElements: ULong;
> out pvar: OleVARIANT): ULong; safecall;
>
> Declaring something as safecall causes the compiler to *assume* it is a
> function that returns HResult, so you cannot provide that return value
> yourself. When you call a safecall function, the compiler will check the
> returned HResult value for you, and if it fails, the compiler generates
> an exception instead of passing back the original HResult.
>
> When a safecall routine is declared as a function, the compiler assumes
> that the return value will be passed from the DLL as the last "out"
> parameter.
>

Learned a lot from your remarks... but the program still fails.
I've tried every combination you have given me, all with different result (up
to hogging all the resources on my pc), but in the end I still get an access
violation.

The problem does seem the excist within that bit of code though.

>> if
>> (Succeeded(IDispatch(varArr).QueryInterface(SWBemProperty,SProp))) and
>> Assigned(SProp) then
>
> I think you can replace that line with this:
>
> if Supports(IDispatch(varArr), SWBemProperty, SProp) then
>

Did not get around on doing that yet.....

>> on E: Exception do
>> begin
>> writeln('Oeps');
>
> Write a little more than that. Include the exception's type name and
> message, too.

Ghehe.... offcourse I will ;)


>> Enum:= CosWbemLocator.Create.ConnectServer('','root
>> \cimv2','','','','',0,nil).ExecQuery(
>> 'Select * from
>> Win32_PerfFormattedData_TermService_TerminalServices',
>> 'WQL', wbemFlagBidirectional, nil)._NewEnum as IEnumVariant;
>
> That's quite a lot to be performing all in one statement. I suggest
> breaking it up into multiple statemens. Say, one method call per line.
>

Broke that up in 3 constructors (? is that their name?)


Anyway..... on with the problem.
What I am wondering about now is if ADsEnumeratNext is the right function to
us. Going to google for some more examples from people doing about the same I
am doing.

Peter

JK

unread,
May 7, 2007, 5:32:59 PM5/7/07
to
p.ka...@atlascollege.nl wrote:
>

> uses
> Windows,
> Controls,
> Variants,
> SysUtils,
> ActiveX,
> WbemScripting_TLB;
>
...

> At first I got a complaint that I needed to call CoInitialize(), so I
> did.

This thread probably already has dried weeks ago. As a late tip, this
tiny ComInit Unit had solved the problem for good.

------------------------------------------
unit ComInit;

interface

uses
ActiveX;

implementation

initialization
// CoInitializeEx(nil,COINIT_MULTITHREADED);
CoInitialize(nil);

finalization
CoUninitialize;
end.

------------------------------------------

You just add this Unit as the _FIRST_ unit in your Uses list, and then
forget it.

It will then take care of itself and also automatically solves all the
COM Initialization/Finalization problems.

JK

0 new messages