Problem with COM binding

Aug 16, 2009 at 12:11 PM

 

There seems to be a problem with invoking dynamic automation methods of COM objects. The first invocation is performed without issues, subsequent calls throw errors. Consider the following python code:

import System

com_obj = System.Runtime.InteropServices.Marshal.BindToMoniker("winmgmts:\\\\.\\root\\cimv2")
foo = com_obj.ExecQuery("Select * from Win32_NetworkAdapter where (MACAddress is not null)")
for x in foo:
	print x.Name()

 
In this case, x is of type SWbemObject (http://msdn.microsoft.com/en-us/library/aa393741(VS.85).aspx). Name is a dynamic method exposed by this object.
The first iteration executes without errors and gives the correct result. The second iteration yields the following result:

 

Marvell Yukon 88E8053 PCI-E Gigabit Ethernet Controller
Traceback (most recent call last):
  File "test.py", line 6, in test.py
EnvironmentError: System.Runtime.InteropServices.COMException (0x80004005): Unsp
ecified error
   at Microsoft.Scripting.ComRuntimeHelpers.CheckThrowException(Int32 hresult, E
xcepInfo& excepInfo, UInt32 argErr, String message) in E:\projects\dlr\DLR_Main\
Src\Runtime\Microsoft.Dynamic\ComRuntimeHelpers.cs:line 61
   at CallSite.Target(Closure , CallSite , Object )
   at CallSite.Target(Closure , CallSite , CodeContext , Object )
   at __main__(Closure )
   at IronPython.Compiler.RuntimeScriptCode.InvokeTarget(LambdaExpression code,
Scope scope) in E:\projects\dlr\DLR_Main\Src\Languages\IronPython\IronPython\Com
piler\RuntimeScriptCode.cs:line 71
   at IronPython.Compiler.RuntimeScriptCode.Run(Scope scope) in E:\projects\dlr\
DLR_Main\Src\Languages\IronPython\IronPython\Compiler\RuntimeScriptCode.cs:line
64
   at IronPython.Hosting.PythonCommandLine.RunFileWorker(String fileName) in E:\
projects\dlr\DLR_Main\Src\Languages\IronPython\IronPython\Hosting\PythonCommandL
ine.cs:line 488
   at IronPython.Hosting.PythonCommandLine.RunFile(String fileName) in E:\projec
ts\dlr\DLR_Main\Src\Languages\IronPython\IronPython\Hosting\PythonCommandLine.cs
:line 460

 

 

Equivalent vbscript executes without errors:

 

dim foo

set foo = getobject("winmgmts:\\.\root\cimv2")

set f = foo.ExecQuery("Select * from Win32_NetworkAdapter where (MACAddress is not null)")

for each a in f
WScript.Echo a.Name
next

I can reproduce this error with my own DLR based language. Please provide feedback on how to handle this error, thanks for any replies in advance.

 

import System
com_obj = System.Runtime.InteropServices.Marshal.BindToMoniker("winmgmts:\\\\.\\root\\cimv2")
foo = com_obj.ExecQuery("Select * from Win32_NetworkAdapter where (MACAddress is not null)")
for x in foo:
print x.Name(
Aug 17, 2009 at 12:26 PM
Edited Aug 17, 2009 at 12:30 PM

There may be something wrong with how dynamic methods are cached within the IDispatchMetaObject and IDispatchComObject classes. In IDispatchMetaObject, dynamic members are obtained with TryGetMemberMethodExplicit. The expression is restricted to com objects of the same type with the function IDispatchRestriction. If I change this restriction to an instance restriction and disable the caching in TryGetMemberMethodExplicit, the above snippet works correctly. 

Please look into this issue. I noticed no developer actually reviews any issues in the issue tracker, hopefully somebody checks this one.

Aug 18, 2009 at 12:13 AM

Thanks for reporting this!

I'm looking into it, but I agree, it seems like a caching problem. The following VB.NET snippet works correctly:


    Sub Main()
        Dim com_obj As Object = System.Runtime.InteropServices.Marshal.BindToMoniker("winmgmts:\\.\root\cimv2")
        Dim foo As Object = com_obj.ExecQuery("Select * from Win32_NetworkAdapter where (MACAddress is not null)")
        For Each x In foo
            Console.WriteLine(x.Name)
        Next
    End Sub

 

Aug 19, 2009 at 11:37 PM

It seems like the COM object is buggy. It requires calling GetIDsOfNames on each "x", even though the rows are of the same type and the dispid isn't changing. We're following up with the owners of the COM object to figure out if there are a lot of other COM objects that work like that.

In the meantime if you need a work around, this should do it:

import System
com_obj = System.Runtime.InteropServices.Marshal.BindToMoniker("winmgmts:\\\\.\\root\\cimv2")
foo = com_obj.ExecQuery("Select * from Win32_NetworkAdapter where (MACAddress is not null)")
for x in foo:
    print x.GetType().InvokeMember("Name", System.Reflection.BindingFlags.GetProperty, None, x, None)

 

Aug 20, 2009 at 8:34 AM
Edited Aug 20, 2009 at 8:56 AM

Thanks for looking into this. I knew GetIDsOfNames had something to do with it (my little 'fix' disables some caching so that this function is called all the time), but I thought such function wouldn't have any side-effects. 

I understand the COM object is buggy, but the fact remains that the DLR is obviously acting different from the vb.net 'com binder'. In my own dlr-based language, a lot of COM is used and it would be nice if the behaviour would be 100% consistent with VB.NET. Telling my users that the COM object is buggy wouldn't result in much understanding, since the object works just fine in vbscript/vb.net etc. 

Coordinator
Aug 20, 2009 at 4:28 PM
We regress our binding semantics against VB's COM tests. However, VB.NET does no caching for late binding. VB.NET recomputes the binding every time you execute a member fetch or invocation. Hence, they unnecessarily call GetIDs... and search for members every time. VB wants to make their COM binding faster, and when they do, buggy objects like this one will have the same behavior. I don't know what VB's exact plans are. Of course, they'll need to consider a back compat mode (Option ComDispatchCaching Off ?) given their COM history in case this sort of accidental correctness breaks VB users.
Bill

From: WvS [notifications@codeplex.com]
Sent: Thursday, August 20, 2009 1:34 AM
To: Bill Chiles
Subject: Re: Problem with COM binding [dlr:65848]

From: WvS

Thanks for looking into this. I knew GetIDsOfNames had something to do with it (my little 'fix' disables some caching so that this function is called all the time), but I thought such function wouldn't have any side-effects.

I understand the COM object is buggy, but the fact remains that the DLR is obviously acting different from the vb.net 'com binder'. In my own dlr-based language, a lot of COM is used and it would be nice if the behaviour would be 100% consistent with VB.NET.