BeginInvoke on a delegate

  • Thread starter Thread starter Bob Altman
  • Start date Start date
B

Bob Altman

I'm trying to call a function on a background thread in a C++/CLI (VS 2005)
application compiled with /clr. I store a delegate reference in a module-level
gcroot. When I call BeginInvoke on the delegate I get a RemotingException. The
inner exception says "Pointer types cannot be passed in a remote call". A code
example that demonstrates the problem appears below.

Am I even wiring up the delegate/BeginInvoke/EndInvoke stuff correctly? The
documentation is pretty sketchy.

TIA - Bob

----- Code example -----

#include <gcroot.h>

using namespace System;

// Class with no members
class MyClass {
};

// Forward references
void BackgroundWorker(MyClass& myClass);
void _Callback(IAsyncResult ^ar);

// Module-level data
delegate void BackgroundWorkerDelegType(MyClass& myClass);

static gcroot<BackgroundWorkerDelegType^>
m_BackgroundWorkerDeleg
= gcnew BackgroundWorkerDelegType(&BackgroundWorker);

static gcroot<AsyncCallback^>
m_callbackDeleg
= gcnew AsyncCallback(&_Callback);

// Main program
int main(array<System::String ^> ^args)
{
MyClass myClass;
// RemotingException: Pointer types cannot be passed in a remote call
m_BackgroundWorkerDeleg->BeginInvoke(myClass, m_callbackDeleg, nullptr);
return 0;
}

void BackgroundWorker(MyClass& myClass)
{
Console::WriteLine("In BackgroundWorker");
}

// Routine called when the asynchronous call completes
void _Callback(IAsyncResult ^ar)
{
m_BackgroundWorkerDeleg->EndInvoke(ar);
}
 
I simplified the sample app by removing all of the gcroot stuff. The resultant
app still gets the same error. I'm obviously doing something fundamentally
wrong, but the docs make no mention of how to use BeginInvoke with a C++
delegate.

----- New sample app ------

#include <gcroot.h>

using namespace System;

// Class with no members
class MyClass {
};

// Forward references
void BackgroundWorker(MyClass& myClass);
void Callback(IAsyncResult ^ar);

// Module-level data
delegate void BackgroundWorkerDelegType(MyClass& myClass);

// Main program
int main(array<System::String ^> ^args)
{
MyClass myClass;
BackgroundWorkerDelegType^ d = gcnew
BackgroundWorkerDelegType(&BackgroundWorker);
AsyncCallback^ cb = gcnew AsyncCallback(&Callback);
// RemotingException: Pointer types cannot be passed in a remote call
d->BeginInvoke(myClass, cb, d);
return 0;
}

void BackgroundWorker(MyClass& myClass)
{
Console::WriteLine("In BackgroundWorker");
}

// Routine called when the asynchronous call completes
void Callback(IAsyncResult ^ar)
{
BackgroundWorkerDelegType^ d =
static_cast<BackgroundWorkerDelegType^>(ar->AsyncState);
d->EndInvoke(ar);
}
 
It occurs to me that the problem probably has to do with the fact that the
routine I am trying to call with BeginInvoke takes an argument that is a
reference to a native object. Delegates, being part of the CLR, probably
don't know how do deal with native objects. So, how can I pass a native
object (in my actual application it's an STL map) to a routine that I want
to execute on a background (thread pool) thread?

And, if my guess (above) is correct, why does is happily compile? You'd
think that the compiler would complain when it sees me trying to create a
delegate with a native object reference as an argument.

Or maybe I'm still totally clueless and totally off base...
 
Hi Bob,
Your analysis is correct. The problem here is that the native type object
"myClass" is passed as a parameter to BeginInvoke. There are two ways to
work around this issue, one is changing your MyClass type from native to
managed, and the other one is using a wrapper managed class to wrap your
native type object. For example:
=====================================
class MyClass {
public:
int Age;
string Name;
};

template <typename T>
ref struct MyWrapperClass
{
MyWrapperClass() : m_ptr(0) {}
MyWrapperClass(T* ptr) : m_ptr(ptr) {}
MyWrapperClass(MyWrapperClass<T>% right) : m_ptr(right.Release())
{
}


T* operator->()
{
return m_ptr;
}

T* Get()
{
return m_ptr;
}

T* Release()
{
T* released = m_ptr;
m_ptr = 0;
return released;
}

private:
T* m_ptr;
};


// Forward references
void BackgroundWorker(MyWrapperClass<MyClass> myWrapperCls);
void Callback(IAsyncResult ^ar);

// Module-level data
delegate void BackgroundWorkerDelegType(MyWrapperClass<MyClass>
myWrapperCls);

// Main program
int main(array<System::String ^> ^args)
{
MyClass myClass;
myClass.Age = 26;
myClass.Name="Bill";

MyWrapperClass<MyClass> myWrapperCls(&myClass);
BackgroundWorkerDelegType^ d = gcnew
BackgroundWorkerDelegType(BackgroundWorker);
AsyncCallback^ cb = gcnew AsyncCallback(Callback);
d->BeginInvoke(myWrapperCls, cb, d);

Console::Read();
return 0;
}

void BackgroundWorker(MyWrapperClass<MyClass> myWrapperCls)
{
Console::WriteLine("In BackgroundWorker");
Console::WriteLine(myWrapperCls->Age);
Console::WriteLine(myWrapperCls->Name.c_str());
}

// Routine called when the asynchronous call completes
void Callback(IAsyncResult ^ar)
{
BackgroundWorkerDelegType^ d =
static_cast<BackgroundWorkerDelegType^>(ar->AsyncState);
d->EndInvoke(ar);
}
================================================

For mix using native and managed types, you may also refer to this article:
Best Practices for Writing Efficient and Reliable Code with C++/CLI
http://msdn.microsoft.com/en-us/library/aa730837(VS.80).aspx

Hope this helps. If you have any other questions or concerns, please feel
free to let me know.

Best regards,
Charles Wang
Microsoft Online Community Support
===========================================================
Delighting our customers is our #1 priority. We welcome your
comments and suggestions about how we can improve the
support we provide to you. Please feel free to let my manager
know what you think of the level of service provided. You can
send feedback directly to my manager at: (e-mail address removed).
===========================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for
non-urgent issues where an initial response from the community
or a Microsoft Support Engineer within 1 business day is acceptable.
Please note that each follow up response may take approximately
2 business days as the support professional working with you may
need further investigation to reach the most efficient resolution.
The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by
contacting Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
============================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
=========================================================
 
Thanks Charles, that is exactly what I'm looking for!

I looked at the article you referenced in your reply
(http://msdn.microsoft.com/en-us/library/aa730837(VS.80).aspx) and that
author provides a more complete implementation of your managed wrapper class
(shown below). I have a couple of questions about his code:

1. Why does the destructor call "delete m_ptr"? This wrapper didn't
allocate that memory. In my application, I have a module-level STL map that
I pass as an argument to some function. That function needs to wrap the map
reference in this managed wrapper so that it can pass it as an argument to a
managed function (in my case [delegate].BeginInvoke). But I certainly don't
want the managed wrapper to try to delete my map when the wrapper's
destructor is called!

2. What's the "!AutoPtr()" thing all about?

TIA - Bob

----- Code -----

template <typename T>
ref struct AutoPtr
{
AutoPtr() : m_ptr(0) {}
AutoPtr(T* ptr) : m_ptr(ptr) {}
AutoPtr(AutoPtr<T>% right) : m_ptr(right.Release()) {}

~AutoPtr()
{
delete m_ptr;
m_ptr = 0;
}
!AutoPtr()
{
ASSERT(0 == m_ptr);
delete m_ptr;
}
T* operator->()
{
ASSERT(0 != m_ptr);
return m_ptr;
}

T* Get()
{
return m_ptr;
}
T* Release()
{
T* released = m_ptr;
m_ptr = 0;
return released;
}
void Reset()
{
Reset(0);
}
void Reset(T* ptr)
{
if (ptr != m_ptr)
{
delete m_ptr;
m_ptr = ptr;
}
}

private:
T* m_ptr;
};
 
Bob said:
I looked at the article you referenced in your reply
(http://msdn.microsoft.com/en-us/library/aa730837(VS.80).aspx) and that
author provides a more complete implementation of your managed wrapper
class (shown below). I have a couple of questions about his code:

1. Why does the destructor call "delete m_ptr"? This wrapper didn't
allocate that memory.

Those are the semantics of the class -- passing it a pointer means it'll
take ownership of the allocated memory. The STL's auto_ptr works the same
way. If that's not what you want, you are of course free not to do it.
However, take care that in doing this, it's possible for the managed class
to hold a dangling pointer if you free the object while it's still
referenced from managed code. This, as you can imagine, is very bad.
2. What's the "!AutoPtr()" thing all about?
That's the managed finalizer, not to be confused with the destructor. When
the object is garbage collected, the finalizer will be run. You typically
don't need this because C++ encourages you to manage objects explicitly, so
the destructor would take care of releasing resources.
 
Jeroen Mostert said:
Those are the semantics of the class -- passing it a pointer means it'll take
ownership of the allocated memory. The STL's auto_ptr works the same way.

Aha! That also explains why the copy constructor modifies the "right" object by
clearing its pointer to the native data. That's what the author meant by
"transfer of ownership semantics".

But that still leaves the question of whether or not it's even correct to call
delete on the pointer. What if the native object was allocated on the stack,
e.g.

void MySub() {
MyNativeClass c;
MyOtherSub(c);
}

void MyOtherSub(MyNativeClass& c) {
// At this point we have no idea whether
// c was allocated on the stack or the heap
AutoPtr<MyNativeClass> p(&c);
}

Assuming I got the syntax at least kind of correct, what will happen when the
AutoPtr goes out of scope and its destructor tries to delete c?
 
Bob said:
Aha! That also explains why the copy constructor modifies the "right" object by
clearing its pointer to the native data. That's what the author meant by
"transfer of ownership semantics".

But that still leaves the question of whether or not it's even correct to call
delete on the pointer. What if the native object was allocated on the stack,
e.g.

void MySub() {
MyNativeClass c;
MyOtherSub(c);
}

void MyOtherSub(MyNativeClass& c) {
// At this point we have no idea whether
// c was allocated on the stack or the heap
AutoPtr<MyNativeClass> p(&c);
}
It hurts when you move your arm that way, doesn't it? :-)
Assuming I got the syntax at least kind of correct, what will happen when the
AutoPtr goes out of scope and its destructor tries to delete c?
I'm pretty sure demons will fly out of your nose, since that invokes
undefined behavior.

The reason the class is named AutoPtr is to evoke images of the STL's
auto_ptr, which *also* isn't used this way. Indeed, there's no point at all
to using auto_ptr on objects you didn't construct yourself (as MyOtherSub is
doing here).

You can still write MyOtherSub this way, with some care:

void MyOtherSub(MyNativeClass& c) {
AutoPtr<MyNativeClass> p(new MyNativeClass(c));
}

But of course copying the object isn't always desirable or even possible.

If you want to pass a stack-allocated objects, don't use smart pointers. On
the other hand, smart pointers are a pretty good approach to avoiding memory
leaks for objects you *can't* stack-allocate.
 
It hurts when you move your arm that way, doesn't it? :-)

Yeah. My doctor told me not to do that any more.
If you want to pass a stack-allocated objects, don't use smart pointers. On
the other hand, smart pointers are a pretty good approach to avoiding memory
leaks for objects you *can't* stack-allocate.

Oh... I was so focused on figuring out how to wrap a native reference (or, as it
turns out, a native pointer) in a managed wrapper that I missed the fact that
the solution was really intended to solve other problems as well, and, as such,
has implicit usage restrictions placed on it.

Thanks a million Charles and Jeroen!

Bob
 
Charles Wang said:
Hi Bob,
Your analysis is correct. The problem here is that the native type
object "myClass" is passed as a parameter to BeginInvoke. There are
two ways to work around this issue, one is changing your MyClass type
from native to managed, and the other one is using a wrapper managed
class to wrap your native type object. For example:

In case writing a new ref class is overkill, consider just using
System::IntPtr.
 
Back
Top