C++/CLI, Unmanaged to Managed callback without using a bridge, or pinning pointers.

  • Thread starter Thread starter DaTurk
  • Start date Start date
D

DaTurk

Hi,

I have a rather interesting problem. I have a unmanged c++ class
which needs to communicate information to managed c++ via callbacks,
with a layer of c# on top of the managed c++ ultimatley retreiving the
data. Presently all of the c++ code is still in .NET 1.1, so we're
using a _nogc bridge class wrapped in a _gc c++ class in order to
facilitate this interop.

But we've converted everything not c++ to .NET 2.0 and would love to
convert the c++ as well, but I'm not sure how to overcome this
problem. My issues...

1.) C++/CLI does not support this _nogc bridge class idea.

2.)I would need to have this pointer around for the life of the
application, which means I would need to pin it, which is a
ridiculously bad idea considering it will be locked on the heap for
the duration.

So I'm not sure how to overcome this. Any help would be greatly
appreciated.
 
I have a rather interesting problem. I have a unmanged c++ class
which needs to communicate information to managed c++ via callbacks,
with a layer of c# on top of the managed c++ ultimatley retreiving the
data. Presently all of the c++ code is still in .NET 1.1, so we're
using a _nogc bridge class wrapped in a _gc c++ class in order to
facilitate this interop.

But we've converted everything not c++ to .NET 2.0 and would love to
convert the c++ as well, but I'm not sure how to overcome this
problem. My issues...

1.) C++/CLI does not support this _nogc bridge class idea.

2.)I would need to have this pointer around for the life of the
application, which means I would need to pin it, which is a
ridiculously bad idea considering it will be locked on the heap for
the duration.

Hi,

First of all, have you tried building your C++ code with /clr:oldSyntax?
That should enable you to port to .NET2.0 and VC2005 without doing anything,
save for a tweak here or there maybe.

I am not sure I understand (1) correctly. In C++/CLI you can still wrap a
managed class around a native class. Do you mean somehing else?

--

Kind regards,
Bruno van Dooren
(e-mail address removed)
Remove only "_nos_pam"
 
Hi,

First of all, have you tried building your C++ code with /clr:oldSyntax?
That should enable you to port to .NET2.0 and VC2005 without doing anything,
save for a tweak here or there maybe.

I am not sure I understand (1) correctly. In C++/CLI you can still wrap a
managed class around a native class. Do you mean somehing else?

--

Kind regards,
Bruno van Dooren
(e-mail address removed)
Remove only "_nos_pam"- Hide quoted text -

- Show quoted text -

Really? I was under the impression it wasn't supported. Would you
mind showing me an example with CLI syntax?
 
DaTurk said:
Really? I was under the impression it wasn't supported. Would you
mind showing me an example with CLI syntax?

http://tweakbits.com/UnmanagedToManagedCallback.cpp
http://tweakbits.com/ManagedToUnmanagedCallback.cpp

This is how you bridge managed and unmanaged code.

You should only pin an object for a very brief period of time, while
you're accessing or marshalling data. Under no circumstances should you
keep an object pinned for the entire lifetime of the application.

You can no longer put a nested unmanaged class inside a managed one, but
that's just syntax and style. From the technical standpoint, there's
practically nothing that you can't achieve without nested classes,
thanks to the public protected and internal access modifiers.

There is nothing in MC++ that C++/CLI can not do anymore (nesting is not
really a major feature, just a convenience). They just use a different
syntax. How did you solve pinning in your MC++ code? The rules are the
same in C++/CLI. How did you bridge unmanaged and managed classes? You
do exactly the same in C++/CLI, with a different syntax.

Tom
 
http://tweakbits.com/UnmanagedToMan.../tweakbits.com/ManagedToUnmanagedCallback.cpp

This is how you bridge managed and unmanaged code.

You should only pin an object for a very brief period of time, while
you're accessing or marshalling data. Under no circumstances should you
keep an object pinned for the entire lifetime of the application.

You can no longer put a nested unmanaged class inside a managed one, but
that's just syntax and style. From the technical standpoint, there's
practically nothing that you can't achieve without nested classes,
thanks to the public protected and internal access modifiers.

There is nothing in MC++ that C++/CLI can not do anymore (nesting is not
really a major feature, just a convenience). They just use a different
syntax. How did you solve pinning in your MC++ code? The rules are the
same in C++/CLI. How did you bridge unmanaged and managed classes? You
do exactly the same in C++/CLI, with a different syntax.

Tom

OK, I see. But here's my question.

static void __stdcall CallbackForwarder(void* param, int arg)
{
static_cast<Thunk*>(param)->receiver->HandleEvent(arg);
}
if this is a ,managed call. How is it the pointer not moving around.
How does it stay pinned? If I'm registering a managed delegate to an
unmanged class, how do I keep that pointer around for the life of the
application for all of the information flowinf back from unmanaged to
managed.

Thankd for that help, I really do appreciate it.
 
DaTurk said:
static void __stdcall CallbackForwarder(void* param, int arg)
{
static_cast<Thunk*>(param)->receiver->HandleEvent(arg);
}

This is a snippet from my
http://tweakbits.com/UnmanagedToManagedCallback.cpp sample.
if this is a ,managed call. How is it the pointer not moving around.
How does it stay pinned?

In the above example, Thunk is not a ref class, it's a native class. It
doesn't require pinning. Remember, * is a native pointer, ^ is a managed
handle.
If I'm registering a managed delegate to an
unmanged class

Then you need to look at my other sample,
http://tweakbits.com/ManagedToUnmanagedCallback.cpp
how do I keep that pointer around for the life of the
application

Once again:

UnmanagedToManagedCallback.cpp:
When your receiver is managed, it is gcroot-ed in the unmanaged sender
(the Thunk class, in my example). gcroot-ed classes are fully managed,
they don't require pinning. This is Microsoft's solution for embedding a
managed object inside an unmanaged one. The same gcroot template existed
back in the old MC++. There's no difference between MC++ and C++/CLI
here. The way it works is that the native class stores just a 32-bit (or
64-bit) handle to the managed object, but it's not an actual memory
address. When the object gets moved around, its handle value still stays
the same.

ManagedToUnmanagedCallback.cpp:
When your receiver is unmanaged, you don't have to pin that, since
unmanaged classes don't get moved.

Maybe you don't fully understand when pinning is required. You only pin
a managed object when you directly deal with its memory address. For
example:

array<int>^ arr = gcnew array<int>(10); // managed array
pin_ptr<int> p = &arr[0]; // pin it
int* native_ptr_to_managed = p; // unmanaged pointer to the array
// Source: http://msdn2.microsoft.com/en-us/library/1dz8byfh.aspx

Note how in this example you are accessing a managed object's memory
address directly via an unmanaged pointer. This requires pinning, or
else the memory address of the managed object might change.

The value of a managed handle never changes, so it can be stored inside
a native object without pinning:

ref class Managed { };

class Native
{
[...]
gcroot<Managed^> managed;
};

If "managed" moves around, its handle value remains the same.

Tom
 
DaTurk said:
static void __stdcall CallbackForwarder(void* param, int arg)
{
static_cast<Thunk*>(param)->receiver->HandleEvent(arg);
}

This is a snippet from myhttp://tweakbits.com/UnmanagedToManagedCallback.cppsample.
if this is a ,managed call. How is it the pointer not moving around.
How does it stay pinned?

In the above example, Thunk is not a ref class, it's a native class. It
doesn't require pinning. Remember, * is a native pointer, ^ is a managed
handle.
If I'm registering a managed delegate to an
unmanged class

Then you need to look at my other sample,http://tweakbits.com/ManagedToUnmanagedCallback.cpp
how do I keep that pointer around for the life of the
application

Once again:

UnmanagedToManagedCallback.cpp:
When your receiver is managed, it is gcroot-ed in the unmanaged sender
(the Thunk class, in my example). gcroot-ed classes are fully managed,
they don't require pinning. This is Microsoft's solution for embedding a
managed object inside an unmanaged one. The same gcroot template existed
back in the old MC++. There's no difference between MC++ and C++/CLI
here. The way it works is that the native class stores just a 32-bit (or
64-bit) handle to the managed object, but it's not an actual memory
address. When the object gets moved around, its handle value still stays
the same.

ManagedToUnmanagedCallback.cpp:
When your receiver is unmanaged, you don't have to pin that, since
unmanaged classes don't get moved.

Maybe you don't fully understand when pinning is required. You only pin
a managed object when you directly deal with its memory address. For
example:

array<int>^ arr = gcnew array<int>(10); // managed array
pin_ptr<int> p = &arr[0]; // pin it
int* native_ptr_to_managed = p; // unmanaged pointer to the array
// Source:http://msdn2.microsoft.com/en-us/library/1dz8byfh.aspx

Note how in this example you are accessing a managed object's memory
address directly via an unmanaged pointer. This requires pinning, or
else the memory address of the managed object might change.

The value of a managed handle never changes, so it can be stored inside
a native object without pinning:

ref class Managed { };

class Native
{
[...]
gcroot<Managed^> managed;

};

If "managed" moves around, its handle value remains the same.

Tom

OK, I put a post in the main forum, but I was curious if you knew. I
see what your saying. So I moved my native bridge class outside of
the managed class. Now to make it work, I have the bridge class
contain a gcrooted reference to my managed class. But I want it to be
static, but I get a couple linker errors. Apparently you can't have a
static gcrooted managed variable in a native class. Do you know of
any work around?
 
DaTurk said:
But I want it to be
static, but I get a couple linker errors. Apparently you can't have a
static gcrooted managed variable in a native class. Do you know of
any work around?

What's the exact error? Have you initialized the gcroot? You have to
initialize it in your .cpp file, like this:

// .h
ref class Managed { };

class Native
{
static gcroot<Managed^> managed;
};

// .cpp
gcroot<Managed^> Native::managed(gcnew Managed);

Tom
 
I'll fiddle with it, I was getting a linking error. I was using
something similar to this

msclr::auto_gcroot<ManagedReceiver^> receiver;

and as soon as I threw static in front it was not happy.

On another note, I've found a different solution to the problem and I
was hoping you could help explain exactly what is going on.

So, my problem is that I have a native class that needs to
communicate, via callbacks, asynchronously to a managed C++/CLI class,
which then communicates to C#.

Originally with MC++ we were using the bridge class, and now that
we've moved, or are moving to CLI we need to find a different
approach. So while I was fiddling, I did this. I just included a
seperate function outside of the managed class in a seperate
namespace. And it uses the managed class static reference to itself
to invoke it's callbacks, and it works. But why has no one mentioned
this before? It's the best solution I;ve seen yet, what's wrong with
it? Here's some code.


namespace ManagedLib
{
[StructLayout(LayoutKind::Sequential, CharSet=CharSet::Ansi, Pack=8)]
public ref struct MStruct
{
public:
System::Int32 x;
System::Int32 y;

[MarshalAs(UnmanagedType::ByValTStr, SizeConst=50)]
System::String^ str;

virtual String^ ToString() override
{
return String::Format("X:{0} Y:{1} STR:
{2}",Convert::ToString(x),Convert::ToString(y),str);
}
};

public delegate void DataChangedEventHandler(MStruct^ item);

public ref class MClass
{
private:
UnmanagedLib::UClass* pUnmanagedClass_ ;

public:
MClass()
{
pUnmanagedClass_ = new UClass(); //unmanaged heap
//pUnmanagedClass_-
RegisterUnmanagedCallBack(&(_UClassBridge::UnmanagedBridgeCallback)); pUnmanagedClass_-
RegisterUnmanagedCallBack(&(UnmanagedCallbackMethods::UnmanagedBridgeCallback));
pMe_ = this;
}

~MClass(){
delete pUnmanagedClass_;
};

static MClass^ pMe_ = nullptr;
DataChangedEventHandler^ pManagedDelegate_;

void RunSimulation()
{
MStruct^ item = gcnew MStruct();
item->x = 1;
item->y = 2;
item->str = "Test";

for(int i = 0;i< 100000;i++)
{
item->x = i+1;

UStruct uItem;
Marshal::StructureToPtr(item, (System::IntPtr)&uItem, true);

pUnmanagedClass_->FireUnmanagedCallback(uItem);
Thread::Sleep(100);
}
}
};
}

namespace UnmanagedCallbackMethods
{
static void UnmanagedBridgeCallback(const UStruct item)
{
Console::WriteLine("UnmanagedBridgeCallback: Fired");

UStruct uItem = item;
ManagedLib::MStruct^ mItem = gcnew ManagedLib::MStruct();
Marshal::PtrToStructure((System::IntPtr)&uItem, mItem);

ManagedLib::MClass::pMe_->pManagedDelegate_->Invoke(mItem);
}
};

Thanks again for all the help.
 
DaTurk said:
But why has no one mentioned this before?

Because your solution only works with singleton classes. As soon as you
have two objects in the memory, it stops working. My original example
didn't have that restriction. It was generic enough to track an
unlimited number of objects.

Of course, use what fits your needs. Glad you've worked it out.

Tom
 
Because your solution only works with singleton classes. As soon as you
have two objects in the memory, it stops working. My original example
didn't have that restriction. It was generic enough to track an
unlimited number of objects.

Of course, use what fits your needs. Glad you've worked it out.

Tom

Ah, well I can live with that. We're connecting to C libraries at the
bottom, where all the callbacks are static! But how does this
solution work?
We have the native function which we manage with the native class, and
then because it has access to the managed class we're all good yes?
At this point we're in managed land, and because the managed pointer
is being mantained by the managed class we don't have to worry about
the address changing it because CLI takes care of pointing us in the
right direction again. Is this correct?
 
DaTurk said:
At this point we're in managed land, and because the managed pointer
is being mantained by the managed class we don't have to worry about
the address changing it because CLI takes care of pointing us in the
right direction again. Is this correct?

That's correct. A managed handle never gets invalidated due to the GC
moving stuff around in the memory. Even when it's used from unmanaged
code, it will work. That's because a managed handle is not a plain
direct memory address.

The only case when you have to worry about objects moving around is when
you get a native pointer to a managed object. For example, when you get
a char* to a System::String^, or an int* to a
cli::array<System::Int32>^. At the moment you start working with raw
memory addresses, you are detached from the garbage collector. That's
why you have to pin a managed object prior to obtaning its memory
address, and keep it pinned as long as the raw pointer is used. The
expression " Managed ^ " does not represent such a raw pointer.

Tom
 
That's correct. A managed handle never gets invalidated due to the GC
moving stuff around in the memory. Even when it's used from unmanaged
code, it will work. That's because a managed handle is not a plain
direct memory address.

The only case when you have to worry about objects moving around is when
you get a native pointer to a managed object. For example, when you get
a char* to a System::String^, or an int* to a
cli::array<System::Int32>^. At the moment you start working with raw
memory addresses, you are detached from the garbage collector. That's
why you have to pin a managed object prior to obtaning its memory
address, and keep it pinned as long as the raw pointer is used. The
expression " Managed ^ " does not represent such a raw pointer.

Tom

Well, thank you for all your help. It was really appreciated.
 
Back
Top