Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / Interop / October 2004

Tip: Looking for answers? Try searching our database.

GetEnumerator fails in automation run with error "Member not found

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
andyb90gaithersburg - 08 Oct 2004 20:33 GMT
Our .exe application can be controlled by an external program using OLE
Automation.  We have a sample C# .exe program (using c# VisualStudio.NET
2003) that launches our application (via automation) and makes various
successful automation calls.

The one call that is causing a failure is when an object is calling the
GetEnumerator() method in the following line:
System.Collections.IEnumerator objNodes =
(System.Collections.IEnumerator)oWrkNodes.GetEnumerator();

The error we are getting is: An unhandled exception of type
'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll
Additional information: Member not found.

When I call GetEnumerator for a Microsoft Office Word interop that has a
collection, all works ok.  This suggests that our OLE Automation service is
not supporting the proper response to interop.  Can you tell me what call (in
the OLE Automation server) maps to GetEnumerator?

Or might this be a problem with the interface language specification of the
call?

Thanks,

Andyb90Gaithersburg
Robert Jordan - 08 Oct 2004 21:25 GMT
Hi,

> Our .exe application can be controlled by an external program using OLE
> Automation.  We have a sample C# .exe program (using c# VisualStudio.NET
[quoted text clipped - 14 lines]
> not supporting the proper response to interop.  Can you tell me what call (in
> the OLE Automation server) maps to GetEnumerator?

Only methods marked with DISPID_NEWENUM (-4) will be converted
to GetEnumerator by TlbImp.

The method must be defined like (IDL):

[id(-4)]
HRESULT Method([out, retval] IEnumVARIANT **pRet);

or

[id(-4), propget]
HRESULT Method([out, retval] IEnumVARIANT **pRet);

The method name is irrelevant. IUnknown can be used too, AFAIK:

[id(-4)]
HRESULT Method([out, retval] IUnknown **pRet);

bye
Rob
andyb90gaithersburg - 09 Oct 2004 04:59 GMT
Hi Robert,

Thanks for your help.

It seems like we have the ID set correctly.  Here's the code we have:
       [propget, restricted, id(-4)]    // -4 is DISPID_NEWENUM. Must be
propget.
       IUnknown * _NewEnum();

In Visual Studio, the GetEnumerator method is recognized as existing at
compile time (no errors).  The problem we get happens at run-time.  Would
this situation be a symptom of the IDL being improper?

> Hi,
>
[quoted text clipped - 37 lines]
> bye
> Rob
Robert Jordan - 10 Oct 2004 10:56 GMT
Hi,

> Thanks for your help.
>
[quoted text clipped - 6 lines]
> compile time (no errors).  The problem we get happens at run-time.  Would
> this situation be a symptom of the IDL being improper?

That's not an IDL problem, since TlbImp already converted
the method/property to IEnumerable.GetEnumerator().

I guess that the object whose IUnknown is returned by _NewEnum
doesn't implement IEnumVARIANT, which is required.

bye
Rob
andyb90gaithersburg - 11 Oct 2004 16:59 GMT
Hi Robert,

Your assessment makes good sense.  But in looking through the code, I don't
seem to see the issue.  Following is a step through of the code.

On a related issue (the reason we're using GetEnumerator).  In unmanaged
C++, we have access to the GetItem( index ) method and can get members of the
collection by index.  The GetItem method doesn't seem to be visible in C# as
a method, but the GetEnumerator is visible...so we are trying to use
GetEnumerator to index through the collection's objects.  Do you know why
GetItem is not available in .NET, and GetEnumerator is (and GetItem is
available in unmanaged C++ and GetEnumerator is not available)?

Thanks!

Andy

------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
Enumerator definition in header file:
STDMETHOD_(IUnknown *, get__NewEnum)(THIS) PURE;

------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
Implementation of Enumerator:
/*
* CNodes::get_NewEnum
*
* Purpose:
*  Returns an enumerator (IEnumVARIANT) for the items curently in the
collection.
*  The NewEnum property is restricted and so is invisible to users of an
*  automation controller's scripting language. Automation controllers that
support
*  a 'For Each' statement to iterate through the elements of a collection
will use
*  the enumerator returned by NewEnum. The enumerator creates a snapshot of
the
*  the current state of the collection.
*
*/
STDMETHODIMP_(LPUNKNOWN)
CNodes::get__NewEnum(void)
{
   return m_psa->GetNewEnum( &m_except );
}

------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
Implementation of m_psa->GetNewEnum():
LPDISPATCH XSafeArray::GetNewEnum( XExcept *px ) const
{
   PCEnumVariant penum = NULL;;
//    LPUNKNOWN punkEnumVariant = NULL;
   LPDISPATCH punkEnumVariant = NULL;
   HRESULT hr;

   // Create new enumerator for items currently in collection and QI for
IUnknown

   hr = CEnumVariant::Create( m_psa, m_cElements, &penum);
   if (FAILED(hr))
       goto error;        
   hr = penum->QueryInterface(IID_IUnknown, (VOID FAR*
FAR*)&punkEnumVariant);    
   if (FAILED(hr))
   {
       delete penum;
       goto error;
   }
   return punkEnumVariant;

error:
   if (punkEnumVariant)
       punkEnumVariant->Release();
   // Signal CLinks::Invoke to raise exception.

   if ( px )
   {
     px->excepinfo.scode = GetScode(hr);
     px->excepinfo.bstrDescription = xSysAllocString("Enumerator creation
failed.");
     px->bRaiseException = TRUE;
   }
   return NULL;
}

------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
Implementation of CEnumVariant():
/*
* CEnumVariant::Create
*
* Purpose:
*  Creates an instance of the IEnumVARIANT enumerator object and
initializes it.
*
* Parameters:
*  psa        Safe array containing items to be enumerated.
*  cElements  Number of items to be enumerated.
*  ppenumvariant    Returns enumerator object.
*
* Return Value:
*  HRESULT
*
*/
HRESULT
CEnumVariant::Create(SAFEARRAY FAR* psa, ULONG cElements, PCEnumVariant FAR*
ppenumvariant)
{  
   HRESULT hr;
   long lLBound;
                     
   *ppenumvariant = NULL;
   
   PCEnumVariant penumvariant = new CEnumVariant();
   if (penumvariant == NULL)
       goto error;
       
   penumvariant->m_cRef = 0;
   
   // Copy elements into safe array that is used in enumerator
implemenatation and
   // initialize state of enumerator.
   hr = SafeArrayGetLBound(psa, 1, &lLBound);
   if (FAILED(hr))
       goto error;
   penumvariant->m_cElements = cElements;    
   penumvariant->m_lLBound = lLBound;
   penumvariant->m_lCurrent = lLBound;                  
   hr = SafeArrayCopy(psa, &penumvariant->m_psa);
   if (FAILED(hr))
      goto error;
   
   *ppenumvariant = penumvariant;
   return NOERROR;
   
error:
   if (penumvariant == NULL)
       return ResultFromScode(E_OUTOFMEMORY);  
                             
   if (penumvariant->m_psa)
       SafeArrayDestroy(penumvariant->m_psa);  
   penumvariant->m_psa = NULL;    
   delete penumvariant;
   return hr;
}

/*
* CEnumVariant::CEnumVariant
*
* Purpose:
*  Constructor for CEnumVariant object. Initializes members to NULL.
*
*/
CEnumVariant::CEnumVariant()
{    
   m_psa = NULL;

   ObjectCreated( _CENUMVARIANT );
}

/*
* CEnumVariant::~CEnumVariant
*
* Purpose:
*  Destructor for CEnumVariant object.
*
*/
CEnumVariant::~CEnumVariant()
{                  
   if (m_psa) SafeArrayDestroy(m_psa);

   ObjectDeleted( _CENUMVARIANT );
}

/*
* CEnumVariant::QueryInterface, AddRef, Release
*
* Purpose:
*  Implements IUnknown::QueryInterface, AddRef, Release
*
*/
STDMETHODIMP
CEnumVariant::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{  
   *ppv = NULL;
       
   if (iid == IID_IUnknown || iid == IID_IEnumVARIANT)
       *ppv = this;    
   else return ResultFromScode(E_NOINTERFACE);

   AddRef();
   return NOERROR;    
}

STDMETHODIMP_(ULONG)
CEnumVariant::AddRef(void)
{
   return ++m_cRef;  // AddRef Application Object if enumerator will
outlive application object
}

STDMETHODIMP_(ULONG)
CEnumVariant::Release(void)
{
   if(--m_cRef == 0)
   {
       delete this;
       return 0;
   }
   return m_cRef;
}

/*
* CEnumVariant::Next
*
* Purpose:
*  Retrieves the next cElements elements. Implements IEnumVARIANT::Next.
*
*/
STDMETHODIMP
CEnumVariant::Next(ULONG cElements, VARIANT FAR* pvar, ULONG FAR*
pcElementFetched)
{
   HRESULT hr;
   ULONG l;
   long l1;
   ULONG l2;
   
   if (pcElementFetched != NULL)
       *pcElementFetched = 0;
   
   for (l=0; l<cElements; l++)
       VariantInit(&pvar[l]);
       
   if (m_lCurrent >= (m_lLBound+m_cElements) )
   {
     // reached the end

     return ResultFromScode(S_FALSE);
   }
   else
   {
     // Retrieve the next cElements elements.
     for (l1=m_lCurrent, l2=0;
          l1<(long)(m_lLBound+m_cElements) && l2<cElements;
          l1++, l2++)
     {
        hr = SafeArrayGetElement(m_psa, &l1, &pvar[l2]);
        if (FAILED(hr))
            goto error;
     }
     // Set count of elements retrieved
     if (pcElementFetched != NULL)
         *pcElementFetched = l2;
     m_lCurrent = l1;

     // jgm 12/15/99
     // THIS WAS TAKEN (by dan the consultant) FROM ONE OF
     // THE OLE SAMPLES - not sure what the CORRECT behavior is.
     //
     // if user wants to get groups of 10 and there are 12
     // in the collection - return TRUE first time then FALSE?

     return  (l2 < cElements) ? ResultFromScode(S_FALSE) : NOERROR;
   }

error:
   for (l=0; l<cElements; l++)
       VariantClear(&pvar[l]);
   return hr;    
}

/*
* CEnumVariant::Skip
*
* Purpose:
*  Skips the next cElements elements. Implements IEnumVARIANT::Skip.
*
*/
STDMETHODIMP
CEnumVariant::Skip(ULONG cElements)
{  
   m_lCurrent += cElements;
   if (m_lCurrent > (long)(m_lLBound+m_cElements))
   {
       m_lCurrent =  m_lLBound+m_cElements;
       return ResultFromScode(S_FALSE);
   }
   else return NOERROR;
}

/*
* CEnumVariant::Reset
*
* Purpose:
*  Resets the current element in the enumerator to the beginning.
Implements IEnumVARIANT::Reset.
*
*/
STDMETHODIMP
CEnumVariant::Reset()
{
   m_lCurrent = m_lLBound;
   return NOERROR;
}

/*
* CEnumVariant::Clone
*
* Purpose:
*  Creates a copy of the current enumeration state. Implements
IEnumVARIANT::Clone.
*
*/
STDMETHODIMP
CEnumVariant::Clone(IEnumVARIANT FAR* FAR* ppenum)
{
   PCEnumVariant penum = NULL;
   HRESULT hr;
   
   *ppenum = NULL;
   
   hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
   if (FAILED(hr))
       goto error;        
   penum->AddRef();
   penum->m_lCurrent = m_lCurrent;
   
   *ppenum = penum;        
   return NOERROR;
   
error:
   if (penum)
       penum->Release();
   return hr;        
}

#pragma warn +par

------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
Robert Jordan - 12 Oct 2004 22:55 GMT
Hi,

> Your assessment makes good sense.  But in looking through the code, I don't
> seem to see the issue.  Following is a step through of the code.

The code looks ok, but I'm not sure whether CNodes::get__NewEnum()
has to return an IUnknown (like implemented) or if it has
to return an IEnumVARIANT pointer.

> On a related issue (the reason we're using GetEnumerator).  In unmanaged
> C++, we have access to the GetItem( index ) method and can get members of the
[quoted text clipped - 3 lines]
> GetItem is not available in .NET, and GetEnumerator is (and GetItem is
> available in unmanaged C++ and GetEnumerator is not available)?

GetItems becomes the C#-indexer. You can access the elements of
your example with oWrkNodes[int].

bye
Rob

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.