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 / November 2005

Tip: Looking for answers? Try searching our database.

Access violation calling C# interface from C++

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Tony Towers - 31 Oct 2005 19:45 GMT
My company has been given a set of COM interfaces ( as .idl and .tlb
files) by a customer, which represents the interfaces to a system
they are in the process of developing.  One of the interfaces
included is outgoing (i.e., implemented by us), and will be used to
notify our code of changes. The interface is immutable.

The customer code will not be available for some time, so I am
implementing a test harness to enable us to test the interface from
our side. The test harness is written in C++, as is the customer's
system. Our own code is written in C#.

There are three COM interfaces involved: ITLS, which is used to
create instances of other COM objects; IEntity, from which
notifications are sent; and INotification, which receives them.

INotification exposes a method EntityChanged, which takes two [in]
parameters, an IEntity* and a BSTR containing the changes to the
entity.

ITLS exposes a method "CreateEntity", which takes an optional
INotification* parameter, and returns an instance of IEntity. It
also calls EntityChanged on the INotification*.

I am having great trouble in getting the outgoing interface to work
correctly. I have got it working in the CreateEntity method, but not
otherwise. In order to simulate the customer system, I want to use
timeSetEvent to periodically notify our code. Unfortunately when the
outgoing interface's notify method is called, I get the error
"Unhandled exception at 0x001401e5 in TestHarness.exe: 0xC0000005:
Access violation writing location 0x002d5178".

I am not overly attached to using timeSetEvent - any other method of
generating periodic notifications will do. The only things that
can't change are the COM interfaces and the languages used.

Does anybody have any ideas?

Apologies for the length of this article.

The idl file we have been given is (in part):

[
   uuid(...),
   oleautomation,
   helpstring("ITLS Interface"),
   pointer_default(unique)
]
interface ITLS : IUnknown
{
   [helpstring("method CreateEntity")] HRESULT
CreateEntity([in]INotification* pNotification, [out, retval]IEntity**
ppEntity);
};

[
   uuid(...),
   oleautomation,
   helpstring("ITLS.IEntity Interface"),
   pointer_default(unique)
]
interface IEntity : IUnknown
{
   [helpstring("method Retrieve")] HRESULT Retrieve([out,retval]BSTR*
pXMLStream);
}

[
   uuid(...),
   oleautomation,
   helpstring("ITLS.INotification Interface")
]
interface INotification : IUnknown
{
   [helpstring("method EntityChanged")] HRESULT
EntityChanged([in]IEntity* entity, [in]BSTR changes);
};

>From this I created an ATL project, and have this class:

class ATL_NO_VTABLE CHarnessServer :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CHarnessServer, &CLSID_HarnessServer>,
    public IDispatchImpl<IHarnessServer, &IID_IHarnessServer,
&LIBID_COInS, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDispatchImpl<ITLS, &__uuidof(ITLS), &LIBID_TLS, /* wMajor = */
1, /* wMinor = */ 0>,
    public IDispatchImpl<IEntity, &__uuidof(IEntity), &LIBID_TLS, /*
wMajor = */ 1, /* wMinor = */ 0>,
{
private:
    static ResultGenerator::IResultGenerator* iug;

    void DomNotify();

public:
    static void CALLBACK NotifyDomain(UINT, UINT, DWORD_PTR, DWORD_PTR,
DWORD_PTR);
    MMRESULT timerID;

    CHarnessServer() {
        mNotification = NULL;
        timerID = -1;
    }

    ~CHarnessServer() {
        if (timerID >= 0) {
            timeKillEvent(timerID);
        }
    }

    DECLARE_REGISTRY_RESOURCEID(IDR_HARNESSSERVER)

    BEGIN_COM_MAP(CHarnessServer)
        COM_INTERFACE_ENTRY(IHarnessServer)
        COM_INTERFACE_ENTRY(ITLS)
        COM_INTERFACE_ENTRY(IEntity)
    END_COM_MAP()

    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct()
    {
        return S_OK;
    }

    void FinalRelease()
    {
    }

private:

    //IEntity variables
    INotification* mNotification;

public:

    // ITLS Methods
    STDMETHOD(CreateEntity)(INotification* pNotification, IEntity**
ppEntity);

    // IEntity Methods
    STDMETHOD(Retrieve)(BSTR* pXMLStream);
};

OBJECT_ENTRY_AUTO(__uuidof(HarnessServer), CHarnessServer)

typedef CComObject<CHarnessServer> CComHarnessServer;

The C++ code to create an entity is:

HRESULT STDMETHODCALLTYPE CHarnessServer::CreateEntity(INotification*
pNotification, IEntity** ppEntity)
{
    if (ppEntity == NULL)
    {
        return E_INVALIDARG;
    }

    CComHarnessServer* entity;
    CComHarnessServer::CreateInstance(&entity);
    entity->mNotification = pNotification;

    if (pNotification != NULL) {
        if (iug == NULL) {
            HRESULT hr = CoCreateInstance(ResultGenerator::CLSID_Generator,
NULL, CLSCTX_INPROC_SERVER,
                ResultGenerator::IID_IResultGenerator, (void**) &this->iug);
        }
        entity->EntNotify();
        timeSetEvent(5000, 500, CHarnessServer::NotifyEntity, (DWORD_PTR)
entity, TIME_PERIODIC);
    }

    *ppEntity = static_cast<IEntity *>(entity);
    return S_OK;
}

The timeSetEvent callback is:

void CALLBACK CHarnessServer::NotifyEntity(UINT uTimerID, UINT uMsg,
DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) {
    CHarnessServer* entity = (CHarnessServer*) dwUser;
    entity->EntNotify();
}

void CHarnessServer::EntNotify() {
    if (mNotification != NULL) {
        BSTR res = this->iug->Generate();
        mNotification->EntityChanged(this, res);
    }
}

The C# class that implements INotification is:

public class EntityNotifier : INotification {
   public EntityNotifier() {
       Console.WriteLine("Constructed EntityNotifier\n");
   }

   ~EntityNotifier() {
       Console.WriteLine("Destroying EntityNotifier\n");
   }

   public void EntityChanged(IEntity entity, string changes) {
       Console.WriteLine(changes);
   }
}

And, finally, the C# code used to instantiate the entity is:

Type t = Type.GetTypeFromProgID("ITLTestHarness.HarnessServer.1",
itlsServer, true);
ITLS tls = (ITLS) Activator.CreateInstance(t);

// Instantiate the TLS Entity Notifier.
EntityNotifier entityNotifier = new EntityNotifier();

// Create an Entity Object instance.
IEntity tlsEntity = tls.CreateDomain(entityNotifier);

Thanks in advance,
Tony Towers.
Igor Tandetnik - 31 Oct 2005 20:06 GMT
> class ATL_NO_VTABLE CHarnessServer :
> INotification* mNotification;

I would recommend changing that to

CComPtr<INotification> mNotification;

You completely disregard reference counting in your code. This would
help get at least some of it right.

> The C++ code to create an entity is:
>
[quoted text clipped - 3 lines]
> CComHarnessServer* entity;
> CComHarnessServer::CreateInstance(&entity);

Note that CComObject::CreateInstance creates an object with reference
count of zero. It is advisable to AddRef it immediately after creation,
otherwise it is in a somewhat fragile state where a stray AddRef/Release
pair leads to a premature destruction.

> entity->mNotification = pNotification;

Case in point: when keeping an [in] interface pointer beyond the
duration of the call, you must AddRef it. You didn't. By making
mNotification a smart pointer, it would happen automatically.

> timeSetEvent(5000, 500, CHarnessServer::NotifyEntity, (DWORD_PTR)
> entity, TIME_PERIODIC);

This strikes me as a bad idea. How do you know, when the timer callback
arrives, that the pointer is still valid? It can be destroyed at any
moment. You need to keep it AddRef'ed while the timer is still alive,
and remember it somewhere so you can Release it after you stop the
timer.

> *ppEntity = static_cast<IEntity *>(entity);

You must AddRef an [out] interface pointer before returning.
Signature

With best wishes,
   Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925

sat320@yahoo.co.uk - 01 Nov 2005 11:01 GMT
> > class ATL_NO_VTABLE CHarnessServer :
> > INotification* mNotification;
[quoted text clipped - 5 lines]
> You completely disregard reference counting in your code. This would
> help get at least some of it right.

This is my first attempt at COM programming; thanks for the advice.
I'll have to read up on reference counting.

With the changes you suggested the test harness now works - thank-you
very much. I still need to clean up the code (and make sure all those
references get released at the proper time), but I'm getting there.

> > timeSetEvent(5000, 500, CHarnessServer::NotifyEntity, (DWORD_PTR)
> > entity, TIME_PERIODIC);
[quoted text clipped - 4 lines]
> and remember it somewhere so you can Release it after you stop the
> timer.

OK, in order to keep track of allocated objects I'm thinking of
implementing a linked list. I can store the object pointer in a
list element, and when it gets Released I can clear the pointer
in the list element and unlink it. By passing the list element
as the user data I can check whether the object pointer is still
valid before using it when the timer fires.

> > *ppEntity = static_cast<IEntity *>(entity);
>
> You must AddRef an [out] interface pointer before returning.

(*ppEntity)->AddRef()?

Is that in addition to the AddRef when "entity" was allocated?

Thanks again for your help,
Kind Regards,
Tony Towers.
Tony Towers - 01 Nov 2005 12:35 GMT
> > > *ppEntity = static_cast<IEntity *>(entity);
> >
[quoted text clipped - 3 lines]
>
> Is that in addition to the AddRef when "entity" was allocated?

Answering myself from the documentation: "if you are passing a copy of
a pointer back from a function, you must call IUnknown::AddRef on that
pointer."

Tony Towers.
Igor Tandetnik - 01 Nov 2005 14:03 GMT
>> You must AddRef an [out] interface pointer before returning.
>
> (*ppEntity)->AddRef()?
>
> Is that in addition to the AddRef when "entity" was allocated?

If you AddRef there, you don't need to AddRef again here, as long as you
don't cache your own reference to the object.
Signature

With best wishes,
   Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925

Tony Towers - 01 Nov 2005 17:37 GMT
> >> You must AddRef an [out] interface pointer before returning.
> >
[quoted text clipped - 4 lines]
> If you AddRef there, you don't need to AddRef again here, as long as you
> don't cache your own reference to the object.

Great, thanks very much for your help. Everything is now working well.

Kind regards,
Tony Towers.

Rate this thread:







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.