Calling a managed function from native code

B

Bob Altman

I need some education on something that is apparently so basic that it's not
described anywhere in the MSDN docs (that I can find).

Here is the underlying problem I am trying to solve: I have two existing
products. One is a managed code product that includes a singleton (exposed by
..Net remoting) written in VB.Net. The other is an unmanaged DLL written in C++.
I need to add some functions to the native DLL that call into the managed
singleton.

I've managed to figure out a few of the basics involved in this sort of
native-to-managed interop, but I'm confused on several points. Here's what I
think I know:

1. I can control which of my C++ source files compile to native or managed code
by fiddling with the /clr option (and a bunch of other options that conflict
with /clr) for the managed code files.

2. I can directly call a managed function from unmanaged code by way of C++/CLI
magic and things that go thunk in the night...

3. I can (or can't?) hold a reference to managed objects in my unmanaged code.
Here's where things start getting very fuzzy for me. I looked at the docs for
gcroot, which "wraps GCHandle, to hold a CLR object reference in unmanaged
memory". That sounds promising, but the template parameter used to declare a
gcroot reference looks like a managed object reference. I have no idea how to
reference such a thing from my native code, which doesn't know anything about
managed namespaces. To further confuse me, the example code looks like this:

#pragma managed
class StringWrapper {
private:
gcroot<String^> s;

public:
void PrintString() {...}
};

#pragma unmanaged
int main() {
// Create a StringWrapper instance on the unmanaged stack???
StringWrapper s;
s.PrintString();
}

As near as I can tell, the gcroot and its internal GCHandle are there for no
reason other than to prevent the GC from destroying the managed string object
because, in this example, the only reference to the managed string comes from
the unmanaged code that is holding a reference to StringWrapper, which, in turn,
is holding the String object. Is this correct?

The part of this that confuses the heck out of me is this: How is it that the
unmanaged code can create a presumably managed StringWrapper class instance?
Does the C++/CLI magic let me create managed objects on the unmanaged stack even
though I can't hold a reference to one created on the heap?

TIA - Bob
 
M

Mark Salsbery [MVP]

Bob Altman said:
I need some education on something that is apparently so basic that it's
not described anywhere in the MSDN docs (that I can find).

Here is the underlying problem I am trying to solve: I have two existing
products. One is a managed code product that includes a singleton
(exposed by .Net remoting) written in VB.Net. The other is an unmanaged
DLL written in C++. I need to add some functions to the native DLL that
call into the managed singleton.

I've managed to figure out a few of the basics involved in this sort of
native-to-managed interop, but I'm confused on several points. Here's
what I think I know:

1. I can control which of my C++ source files compile to native or managed
code by fiddling with the /clr option (and a bunch of other options that
conflict with /clr) for the managed code files.

2. I can directly call a managed function from unmanaged code by way of
C++/CLI magic and things that go thunk in the night...

3. I can (or can't?) hold a reference to managed objects in my unmanaged
code. Here's where things start getting very fuzzy for me. I looked at
the docs for gcroot, which "wraps GCHandle, to hold a CLR object reference
in unmanaged memory". That sounds promising, but the template parameter
used to declare a gcroot reference looks like a managed object reference.
I have no idea how to reference such a thing from my native code, which
doesn't know anything about managed namespaces. To further confuse me,
the example code looks like this:

#pragma managed
class StringWrapper {
private:
gcroot<String^> s;

public:
void PrintString() {...}
};

#pragma unmanaged
int main() {
// Create a StringWrapper instance on the unmanaged stack???
StringWrapper s;
s.PrintString();
}

As near as I can tell, the gcroot and its internal GCHandle are there for
no reason other than to prevent the GC from destroying the managed string
object because, in this example, the only reference to the managed string
comes from the unmanaged code that is holding a reference to
StringWrapper, which, in turn, is holding the String object. Is this
correct?

The part of this that confuses the heck out of me is this: How is it that
the unmanaged code can create a presumably managed StringWrapper class
instance? Does the C++/CLI magic let me create managed objects on the
unmanaged stack even though I can't hold a reference to one created on the
heap?


It seems you have it all correct. Take a look at the source code for the
gcroot template. It's a simple wrapper for the
System::Runtime::InteropServices::GCHandle structure.

Since C++ allows you to mix native and managed code, you are allowed to use
managed objects from unmanaged code - that's the magic only C++/CLR provides
(no other languages allow this). Any of your C++ code that is compiled with
/clr can create managed objects and use these objects, even from unmanaged
class code.

The only problem is garbage collection. If you hold a managed handle in
native code, and that's the only reference, the garbage collector has no
idea you still have the handle. The GC only knows about managed references.
That's where System::Runtime::InteropServices::GCHandle comes in, by
allocating a handle in a way the GC knows there's still a reference to a
managed object somewhere, so the object shouldn't be collected. When the
gcroot object goes out of scope, its GCHandle will be released so the GC is
free to collect it.

You don't have to fiddle with the /clr switch at the file level - just set
it for the entire project. For all code you want to stay native, use
#pragma unmanaged (you just need this once at the top of each source file.
Then for any code that needs to be compiled managed (any code which is going
to do anything managed, like use a gcroot handle to make a managed call) you
can use #pragma managed/#pragma unmanaged around the code, as shown in your
example code.

Don't overthink it....C++/CLR makes it really easy to use .NET, and in a
familiar C++ syntax :) It's very powerful IMO.

Mark
 
T

Tamas Demjen

Bob said:
3. I can (or can't?) hold a reference to managed objects in my unmanaged code.
Here's where things start getting very fuzzy for me. I looked at the docs for
gcroot, which "wraps GCHandle, to hold a CLR object reference in unmanaged
memory". That sounds promising, but the template parameter used to declare a
gcroot reference looks like a managed object reference. I have no idea how to
reference such a thing from my native code, which doesn't know anything about
managed namespaces.

I will give your some references.

Official Microsoft article (look at "Hiding the Managed Stuff"):
http://msdn.microsoft.com/msdnmag/issues/05/04/C/

A very interesting discussion from the past:
http://tinyurl.com/8r9bs

To summarize, you can simply substitute gcroot with void* in unmanaged
code, because they have the same size. This is considered correct code
and not a hack:

#ifdef _MANAGED
# define GCHANDLE(T) gcroot<T>
#else
# define GCHANDLE(T) void*
#endif

class StringWrapper
{
private:
GCHANDLE(String^) pimpl;

public:
void PrintString() {...}
};

Tom
 
T

Tamas Demjen

Bob said:
The part of this that confuses the heck out of me is this: How is it that the
unmanaged code can create a presumably managed StringWrapper class instance?
Does the C++/CLI magic let me create managed objects on the unmanaged stack even
though I can't hold a reference to one created on the heap?

System::String is a ref class, a garbage collected handle. The caret
symbol expresses that as well: String^. Its data is allocated on the
managed heap.

StringWrapper is not garbage collected, and the lack of the caret symbol
indicates that. The code behind it may be managed, but it's more like a
value class. The data associated with it is not managed.

Tom
 
B

Ben Voigt [C++ MVP]

3. I can (or can't?) hold a reference to managed objects in my unmanaged
code. Here's where things start getting very fuzzy for me. I looked at
the docs for gcroot, which "wraps GCHandle, to hold a CLR object reference
in unmanaged memory". That sounds promising, but the template parameter
used to declare a gcroot reference looks like a managed object reference.
I have no idea how to reference such a thing from my native code, which
doesn't know anything about managed namespaces. To further confuse me,
the example code looks like this:

Ahh, but code compiled with /clr does know about managed namespaces, whether
it is native code or part of a managed class. Native classes defined in a
compile unit using /clr are also fully compatible with native code.

So it looks something like this:

Purely native code, maybe in a static library or such, defines and consumes
a native interface (virtual functions or POD layout).
C++ code compiled with /clr implements the native interface in a native
class or struct, implementation can reference managed classes, also
implements managed classes (ref class/value class) and can reference native
objects and functions.
Code from any .NET language creates and consumes managed classes.

Each layer can only talk to those immediately adjacent, this is why C++/CLI
is often used to create a managed wrapper for native C++ libraries.
 
B

Bob Altman

StringWrapper is not garbage collected, and the lack of the caret symbol
indicates that. The code behind it may be managed, but it's more like a value
class. The data associated with it is not managed.

Tom

Ok, at least now I think I'm starting to understand what I don't understand :)

In my existing native DLL application, I'm very concerned with performance and
determinism (it's part of a real-time airplane simulator). The routines in the
DLL are called by real-time programs written in a variety of languages (FORTRAN,
C/C++, Ada), all of which know how to call into a native Windows DLL. We try to
avoid managed code in the real-time part of things partly because of performance
concerns but mostly because we don't want things like the GC running in the
background if we can help it.

I'm very sensitive to where in my C++ code I'm potentially crossing the boundary
between running native code and running managed code. Now you've introduced
another concept that I need to wrap my brain around: C++ code that may be
running on top of the CLR (meaning that it must be JIT compiled the first time
it's accessed) but which is isn't garbage collected (as you point out, things
that behave like value classes).

So, let me ask if the following statements are all correct:

1. If I compile with /clr and don't include any #pragma directives then the code
compiles to IL which is JIT compiled and runs on top of the CLR.

2. I export routines from my DLL using a .def file. If I include the name of a
function that was compiled with /clr in my .def file then C++ creates what
appears to the outside world to be a native function. But native code that
calls into that function goes through a relatively expensive process called
"thunking" to run the managed code.

3. (Now I start getting to the crux of my original question...) If I compile a
function with /clr, and preceed the function with #pragma unmanaged, then the
code compiles to native code. No IL, no CLR, no JIT compiler, just good old
fashioned machine code.

4. But, native code compiled with /clr can do things that (equally native) code
compiled without /clr can not. It can statically allocate new managed class
instances on the native stack. (VB lacks the concept of having a class instance
without someone having used the New operator to allocate the instance on the
managed heap.) Presumably, this means that I can do something like this:

#pragma unmanaged
void test() {
// Not sure what I can do with a managed string,
// but I can allocate one...
System::String s;
}

5. Also, native code compiled with /clr (and #pragma unmanaged) knows about
managed namespaces. This is necessary when specifying a managed type as a
template parameter (for gcroot<>, for example). This also lets you call
managed, static functions directly from the unmanaged code.

6. I can call the static GCHandle.Alloc method from unmanaged code to
instantiate a managed class on the managed heap and to get back a GCHandle
object that I can treat like a void*. That is, I can make calls on the managed
object by casting the GCHandle to a pointer to the managed class...

7. Or, I can use gcroot, which is sexy because it has a template parameter that
lets it behave like a strongly typed reference to the managed object.
 
T

Tamas Demjen

Bob said:
Now you've introduced C++ code that may be
running on top of the CLR (meaning that it must be JIT compiled the first time
it's accessed) but which is isn't garbage collected (as you point out, things
that behave like value classes).

Yes. I'm not sure if any other .NET language is capable of compiling
such code, though. Certainly not C# and VB.NET. It's a C++/CLI thing.
1. If I compile with /clr and don't include any #pragma directives then the code
compiles to IL which is JIT compiled and runs on top of the CLR.

Correct. With #pragma managed (default with /clr), even std::vector,
std::string, boost::shared_ptr compile into unmanaged data, but JIT-ed code.
2. I export routines from my DLL using a .def file. If I include the name of a
function that was compiled with /clr in my .def file then C++ creates what
appears to the outside world to be a native function. But native code that
calls into that function goes through a relatively expensive process called
"thunking" to run the managed code.

Correct. A mixed mode DLL can export native C-style functions. This is
very useful when you have to call fully managed .NET libraries from a
native C++ application. Thunking is not nearly as expensive as a web
service. Just make sure you don't switch context in the middle of a loop
that runs a million iterations. I'm sure WinForms itself thunks into
Win32 at a lower level.
3. (Now I start getting to the crux of my original question...) If I compile a
function with /clr, and preceed the function with #pragma unmanaged, then the
code compiles to native code. No IL, no CLR, no JIT compiler, just good old
fashioned machine code.

Correct. #pragma unmanaged is really native, no GC, not JIT.
4. But, native code compiled with /clr can do things that (equally native) code
compiled without /clr can not.

Yes, it can thunk into managed code.
It can statically allocate new managed class instances on the native stack.

Only if it's a C++ class. Not full CLR classes:

#pragma managed

class MC { };
ref class RC { };
value class VC { };

#pragma unmanaged

void test()
{
MC mc; // OK
RC rc; // error
VC vc; // error
}

The error is: "managed type of function cannot be used in an unmanaged
function."
#pragma unmanaged
void test() {
// Not sure what I can do with a managed string,
// but I can allocate one...
System::String s;
}

No, not even in #pragma managed. You can't do that, String is not
disposable, it doesn't have a destructor, it doesn't have a Dispose
method. String is a special case that can't be used with stack syntax,
it must be gcnew'ed. That's because String is immutalbe -- one an
instance is create, you can't modify it. Let's say, when you append to a
String, a whole new object is created. Strings must be garbage
collected, it's not a resource.
5. Also, native code compiled with /clr (and #pragma unmanaged) knows about
managed namespaces. This is necessary when specifying a managed type as a
template parameter (for gcroot<>, for example). This also lets you call
managed, static functions directly from the unmanaged code.

I doubt that static functions make a difference. Full CLR methods are
not available from #pragma unmanaged, regardless if they're static or not:

#pragma managed

ref struct SC
{
static void Do() { }
};

#pragma unmanaged

void test()
{
SC::Do(); // error
}
6. I can call the static GCHandle.Alloc method from unmanaged code to
instantiate a managed class on the managed heap and to get back a GCHandle
object that I can treat like a void*. That is, I can make calls on the managed
object by casting the GCHandle to a pointer to the managed class...

#pragma unmanaged
void test()
{
System::Runtime::InteropServices::GCHandle::Alloc(nullptr);
}

I think the gcroot template is defined inside #pragma managed, that's
why it can do such tricks.
7. Or, I can use gcroot, which is sexy because it has a template parameter that
lets it behave like a strongly typed reference to the managed object.

You can use gcroot from #pragma unmanaged for the same reason you can
use any other class:

#pragma managed

class C { };

#pragma unmanaged

void test()
{
C c;
}

Tom
 
T

Tamas Demjen

Tamas said:
#pragma unmanaged
void test()
{
System::Runtime::InteropServices::GCHandle::Alloc(nullptr);
}

I meant this is incorrect, I'm getting a compiler error. You can't do
that from unmanaged.

Tom
 
B

Bob Altman

It can statically allocate new managed class instances on the native stack.
Only if it's a C++ class. Not full CLR classes:
I doubt that static functions make a difference. Full CLR methods are not
available from #pragma unmanaged, regardless if they're static or not:

So, getting back to my real-world problem: I have a "full CLR" class that I
need to call into. (Actually, it's a singleton exposed via remoting.) If I
somehow manage to get a reference to this object stored into a
gcroot<MyManagedClass> variable, can I call directly into my managed class from
unamanaged code through the gcroot reference? Or do I need to write a managed
C++ wrapper that calls into my managed class, and call the managed C++ wrapper
from native C++?
 
W

William DePalo [MVP VC++]

Bob Altman said:
So, getting back to my real-world problem: I have a "full CLR" class that
I need to call into. (Actually, it's a singleton exposed via remoting.)
If I somehow manage to get a reference to this object stored into a
gcroot<MyManagedClass> variable, can I call directly into my managed class
from unamanaged code through the gcroot reference?
No.

Or do I need to write a managed C++ wrapper that calls into my managed
class, and call the managed C++ wrapper from native C++?

Yes. Paul DiLascia shows how to that in a fairly general way here:

http://msdn.microsoft.com/msdnmag/issues/05/04/C/

I was far lazier in the thread that Tamas quoted and showed how to do it in
a specific case.

By the way, another option is to "register" your assembly with the REGASM
utility so that it masquerades as a COM component and then to "import" the
type library in native code to use it more or less "normally".

I've got a video on the MSDN site that explains you to do that - I'll look
up the link if you think it'll help.

Regards,
Will
 
B

Bob Altman

I want to extend my sincere thanks to all the good folks who contributed to
this thread! It's a shame that this whole native/managed/interop thing is
so poorly documented.

Bob Altman
 
J

Jeffrey Tan[MSFT]

Hi Bob,

Glad to see you have got a lot of good replies from the experts here.

One thing I wanted to point out in your first post is that:
StringWrapper is a native class(although defined in managed context). A
managed reference class would be declared with 'ref class'. Wrapping a
native class definition with #pragma managed does not transform it into a
managed reference class definition.

Another tip-off that StringWrapper is a native class is the fact that you
can use it from within the #pragma unmanaged section, which you wouldn't be
able to do if it were a managed reference class. As a result, the
stack-based StringWrapper instance behaves as any regular old C++
stack-based object.

If you add "ref" to StringWrapper and use it in unmanaged "main", you will
get a lot of errors, including this one:
error C3821: 'StringWrapper': managed type or function cannot be used in an
unmanaged function

Additionally, you can use "ref" version of StringWrapper in a managed
method like this:
StringWrapper s;
s.PrintString();

This is actually a syntax sugar for deterministic disposition. It will be
translated into:
#pragma unmanaged
int main() {
// Create a StringWrapper instance on the unmanaged stack???
StringWrapper ^s = gcNew StringWrapper();
s->PrintString();
//using C#-like syntax (rather than C++ syntax) in the next line
if (s is IDisposable^) (s as IDisposable^)->Dispose(); //dispose s as
it goes out of scope
}

By the way, weird (/unexpected) things can happen with #pragma
managed/unmanaged. It's best to put your unmanaged stuff in one file and
your managed stuff in another by turning "/clr" on/off at file level.

Regarding the documentation issue, I would recommend you to submit
feedback/suggestion to the .Net doc team in the site below:
https://connect.microsoft.com/VisualStudio

If you have concern with specific single MSDN page, you may use the links
at the end of the page topic to submit to the Microsoft. The email goes
into an automated feedback system in Microsoft and ends up attached to a
doc bug internally which is assigned to the writer who owns the topic.
Again, this can take a little time to process but eventually it gets there.

Hope this helps.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
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.
 
B

Bob Altman

One thing I wanted to point out in your first post is that:
StringWrapper is a native class(although defined in managed context). A
managed reference class would be declared with 'ref class'. Wrapping a
native class definition with #pragma managed does not transform it into a
managed reference class definition.

Another tip-off that StringWrapper is a native class is the fact that you
can use it from within the #pragma unmanaged section, which you wouldn't
be
able to do if it were a managed reference class. As a result, the
stack-based StringWrapper instance behaves as any regular old C++
stack-based object.

Ahhh... so that's what the mysterious "ref" qualifier does! That wasn't at
all clear from the docs. Yes, until I read your comment (above) I was still
confused on that point. This is exactly the kind of basic information that
is totally lacking in the Microsoft docs. (And I strongly suspect that most
programmers are hazy or totally ignorant of this because "it just works".)
Additionally, you can use "ref" version of StringWrapper in a managed
method like this:
StringWrapper s;
s.PrintString();

This is actually a syntax sugar for deterministic disposition. It will be
translated into:
#pragma unmanaged
int main() {
// Create a StringWrapper instance on the unmanaged stack???
StringWrapper ^s = gcNew StringWrapper();
s->PrintString();
//using C#-like syntax (rather than C++ syntax) in the next line
if (s is IDisposable^) (s as IDisposable^)->Dispose(); //dispose s as
it goes out of scope
}

That makes sense but... if StringWrapper is a native class (that is, it's
implementation is represented by native code and not by IL) then how can we
create an instance of it on the managed heap (via gcNew)?
By the way, weird (/unexpected) things can happen with #pragma
managed/unmanaged. It's best to put your unmanaged stuff in one file and
your managed stuff in another by turning "/clr" on/off at file level.

Thanks, I'll heed that advice. I have several legacy C++ DLL projects
compiled without /clr, and I need to add a small number of managed source
files to each. It's a bit of a pain to fiddle with the 6 or so compiler
settings for each managed source file I add to an otherwise native project.
It would really cool if I could copy all of the settings from one file and
apply them to a different file. Alternately, it would be cool if I could
find a template that I can add to my project that adds a managed source code
file to an unmanaged project with reasonable compiler settings for that
file.
Regarding the documentation issue, I would recommend you to submit
feedback/suggestion to the .Net doc team in the site below:
https://connect.microsoft.com/VisualStudio

Yeah, I've been there, done that for numerous, reproducable VB bugs. Most
of my bug reports wound up as "yeah, we can reproduce it, but we're not
gonna fix it". After the fourth or fifth one of those I pretty much gave
up. Looking back on it, I think that they really meant to say "we're not
going to fix it directly, but we're introducing a new way of doing the same
thing that won't have the same problem." Or not. Who knows? At first I
thought it was cool that MS was giving developers in the trenches like me a
way to get feedback more or less directly to the product developers, but
that enthusiasm on my part faded quickly due to my perception that spending
time to research, reproduce, and post bug reports is more often than not a
waste of my time. (Hmmm... I think you touched a nerve! It felt good
getting that off my chest, even if it was just an opportunity to annoy a
bunch of C++ geeks with my ranting and raving and not the folks who really
need to hear it. And, yes, I will spend some time annoying the MS Connect
folks with documentation feedback. I guess I'm just a glutton for
punishment.)
 
J

Jeffrey Tan[MSFT]

Hi Bob,

Thanks for your feedback.

Oh, there may be some misunderstand in my reply. When "ref" is added to the
StringWrapper class definition, StringWrapper is not a native class, it is
a managed one.

In this scenario, the following

StringWrapper s;
s.PrintString();

can be translated

int main() {
// Create a StringWrapper instance on the unmanaged stack???
StringWrapper ^s = gcNew StringWrapper();
s->PrintString();
//using C#-like syntax (rather than C++ syntax) in the next line
if (s is IDisposable^) (s as IDisposable^)->Dispose(); //dispose s as
it goes out of scope
}

C++/CLI doc in MSDN has said that only C++ classes/structures marked with
"ref" keyword will be treated as managed types. Without using "ref"
keyword, all the classes/structures will be treated as native types
semantically. Note, although these classes are treated as native *data*,
the *code* in these classes will still be compiled into IL code if "/clr"
is used. So "ref" controls the managed/native feature of the data, while
"/clr" or "#pragma managed" controls the managed/native feature of the
generated code. At least, that how I understand it.

Regarding the deterministic disposition model, it is an important feature
of C++/CLI. Stanley B. Lippman has provided good introduction for these
features in the article below:
"Hello, C++/CLI"
http://msdn.microsoft.com/msdnmag/issues/06/00/PureC/default.aspx

To toggle the /clr setting automatically for some files, I suspect we can
write a macro to automate the task, however, I am not an expert on the IDE.

Yes, the product dev team has different view of a problem with us. They
normally focuses the product reliability, security, lifecycle and release
time etc... To get best support from Microsoft, Microsoft CSS phone support
is definitely the best place, since the CSS engineer will place the most
focus/effort on customer problem. :)

Hope it helps.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
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.
 
B

Bob Altman

Additionally, you can use "ref" version of StringWrapper in a managed
Oh, there may be some misunderstand in my reply. When "ref" is added to
the
StringWrapper class definition, StringWrapper is not a native class, it is
a managed one.

Oh, sorry about that. I missed the small detail that you added "ref" to the
StringWrapper class definition.
Note, although these classes are treated as native *data*,
the *code* in these classes will still be compiled into IL code if "/clr"
is used. So "ref" controls the managed/native feature of the data, while
"/clr" or "#pragma managed" controls the managed/native feature of the
generated code. At least, that how I understand it.

The one (critically important) thing that has come into focus for me through
this discussion is the fact that C++/CLI is fundamentally different than
other languages that target the .Net CLR, especially when the /clr compiler
option is used. Let's see if I've got this reasonably correct:

1. When compiled with /clr:safe, C++/CLI creates the same managed code and
data as VB or C#. With this setting, C++/CLI loses its magical "C++
interop" features, meaning that, like VB or C#, C++/CLI cannot directly
access unmanaged code or data types without going through the usual P/Invoke
claptrap. It also means that, like VB or C#, code and data created using
this option lives entirely in managed assemblies and are inaccessible to
native callers.

2. When compiled with /clr:pure, C++/CLI compiles to IL hosted in an app
domain, but it gains the ability to directly deal with native types and to
directly access both the native stack and the native heap. Managed types
(declared as "ref class", "ref struct", "value class" or "value struct")
live on the managed stack (managed value types) or the managed heap (managed
reference types). All other C++ types live on the native stack or the
native heap. This setting allows C++/CLI to directly call native functions
using "implicit P/Invoke", which only works when all of the arguments
supplied to the native function call are native types. If any arguments are
managed types (e.g.. passing a System::String to a const char* argument)
then explicit P/Invoke (using the [DllImport] attribute) is required.
Native code cannot call functions compiled with /clr:pure for the same
reason that native code cannot call VB or C# functions.

3. When compiled with /clr, C++/CLI operates in "C++ interop" mode. By
default, this setting creates managed code that runs in some sort of runtime
environment that involves the CLR but not an app domain. (I'd love to find
some documentation that explains this in detail!) I say "by default"
because the "#pragma unmanaged" directive allows C++/CLI to create good old
native code.

Functions exposed by a DLL created with /clr can be directly called from
native code thanks to the fact that the compiler exposes native entry
points, which contain code that "thunks" into the managed functions. (I
assume that there is some way for managed code to directly call managed
function in a DLL created with /clr, but I have no idea how that works or
what hoops something like VB, which creates verifiable code by default,
needs to jump through to make this happen.) As with /clr:pure, code created
with /clr can directly call native functions using "implicit P/Invoke",
which only works when all of the arguments supplied to the native function
call are native types. Otherwise, explicit P/Invoke (using the [DllImport]
attribute) is required.

As with /clr:pure, /clr allows C++ to define and consume managed types
(introduced with the "ref" or "value" attributes) or native C++ types.

Managed object references (referred to as "handles") cannot be stored in
native memory, primarily because the garbage collector knows nothing about
native memory and thus could destroy or move managed objects whose only
reference is from native memory. The native cgroot template class provides
a convenient way to overcome this limitation.

Global (file scope) variables are stored in native memory. Thus, C++/CLI
does not allow global variables to contain handles to managed objects.
Similarly, handles to managed objects cannot be stored in a static variable
at file scope or in a function that is not a member of a managed class.
 
J

Jeffrey Tan[MSFT]

Hi Bob,

Thanks for your feedback.

Yes, most of your understanding is correct and C++/CLI is a bit complex due
to the native/managed code/type mix design. However, it is this design that
combines the power of .Net and C++ seamlessly.

I wanted to answer your question of managed code calls into the /clr C++
managed assembly. This is called double thunk problem in C++/CLI. As you
described, C++/CLI assembly exposes both the managed and native entry
points for the client, however, for legacy reason, the native entry point
is always chose for the client. So when the managed client calls into the
/clr assembly, the native entry point is used, so managed->native
transition occurs. While the actual implementation code is placed in the
managed entry point, another native-> managed transition occurs. So there
are two thunks in during the calling, which is not a good performance
behavior.

In C++/CLI, you can use __clrcall to specify whether to emit an unmanaged
entry point on a method-by-method basis. There are still other solutions
though.

The best resource I can pointed you are some MSDN articles and blogs:
"Write Faster Code with the Modern Language Features of Visual C++ 2005"
http://msdn.microsoft.com/msdnmag/issues/04/05/visualc2005/default.aspx
"Power Your App with the Programming Model and Compiler Optimizations of
Visual C++"
http://msdn.microsoft.com/msdnmag/issues/05/01/COptimizations/
"Visual C++ Internals and Practices"
http://blogs.msdn.com/branbray/
"Pure and Verifiable Code"
http://msdn2.microsoft.com/en-us/library/85344whh.aspx


For C++ interop, there a series of C++ interop walkthough articles in the
MSDN, I always use them as reference during coding:
"Using C++ Interop (Implicit PInvoke) "
http://msdn2.microsoft.com/en-us/library/2x8kf7zx(VS.80).aspx

Hope it helps.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
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.
 
B

Bob Altman

Thanks Jeffrey. Those links are really helpful! I guess I'll have to start
digging through my old MSDN back issues.

And, once again, thanks to you and all the good folks who took the time to
post replies to this thread. My department in my little corner of Boeing in
Long Beach, California builds real-time airplane simulators. Over the years
I've designed a simulation architecture that uses managed code for all of
the non-real-time configuration and control stuff and unmanaged C++ (and a
*lot* of legacy FORTRAN and Ada stuff) for the parts that run in real-time.
Calling into unmanaged code from managed code via P/Invoke is
straightforward and well documented, but when I first started this project
at the dawn of the .Net age it wasn't feasible to go the other direction and
call into managed code from unmanaged code. (We communicate between the
managed and unmanaged worlds via shared memory variables. It's clunky but
it work.) By the time VS 2003 came out, our architecture was fairly mature
and, although I had read the odd magazine article about IJW and C++ Interop,
I never dug into it. Now I'm adding some features to our architecture where
it would really be helpful if the unmanaged stuff could just call directly
into the managed stuff, so I bit the bullet and dived right in. But being a
little anal (ok, a whole lot anal) and very concerned about performance, I
wanted to understand what I was doing starting with the underlying mechanics
of what the platform is doing for (or to) me.

Now that I have a clue how this stuff is glued together, it really is about
as easy as folks say it is!

Bob

"Jeffrey Tan[MSFT]" said:
Hi Bob,

Thanks for your feedback.

Yes, most of your understanding is correct and C++/CLI is a bit complex
due
to the native/managed code/type mix design. However, it is this design
that
combines the power of .Net and C++ seamlessly.

I wanted to answer your question of managed code calls into the /clr C++
managed assembly. This is called double thunk problem in C++/CLI. As you
described, C++/CLI assembly exposes both the managed and native entry
points for the client, however, for legacy reason, the native entry point
is always chose for the client. So when the managed client calls into the
/clr assembly, the native entry point is used, so managed->native
transition occurs. While the actual implementation code is placed in the
managed entry point, another native-> managed transition occurs. So there
are two thunks in during the calling, which is not a good performance
behavior.

In C++/CLI, you can use __clrcall to specify whether to emit an unmanaged
entry point on a method-by-method basis. There are still other solutions
though.

The best resource I can pointed you are some MSDN articles and blogs:
"Write Faster Code with the Modern Language Features of Visual C++ 2005"
http://msdn.microsoft.com/msdnmag/issues/04/05/visualc2005/default.aspx
"Power Your App with the Programming Model and Compiler Optimizations of
Visual C++"
http://msdn.microsoft.com/msdnmag/issues/05/01/COptimizations/
"Visual C++ Internals and Practices"
http://blogs.msdn.com/branbray/
"Pure and Verifiable Code"
http://msdn2.microsoft.com/en-us/library/85344whh.aspx


For C++ interop, there a series of C++ interop walkthough articles in the
MSDN, I always use them as reference during coding:
"Using C++ Interop (Implicit PInvoke) "
http://msdn2.microsoft.com/en-us/library/2x8kf7zx(VS.80).aspx

Hope it helps.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
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.
 
J

Jeffrey Tan[MSFT]

Hi Bob,

Glad to see our replies can help you.

Yes, C++/CLI is a big improvement from managed C++. Not only is it a first
class language in .Net now, but also it achieves good performance and
convenient during interop with native code. If you search "C++ interop
performance" keywords in google, you will get more information about its
performance.

I really love the convenient of C++/CLI. For almost all the smaller tools I
wrote recently, I always use the C++/CLI. With it, I easily wrote the tool
GUI with the .Net Framework(Winform) and coded the logic engine in a
separate pure native C++ code. Between them, I use the C++ interop to
communication and callback. It is really cool! However, I still did not
have much time to learn it completely. But I really love the magic it does.

Thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top