create a wrapped managed dll that can be used from pure unmanagedcode

B

bonk

Hello,

I am trying to create a dll that internally uses managed types but
exposes a plain unmanaged interface. All the managed stuff shall be
"wrapped out of sight". So that I would be able to use that dll from
pure unmanaged code (for example inherit from classes in that dll). Is
something like that possible. I heared something called ManWarp tried
that approach.

If it is possible, how can I do that. Maybe there is a small litttle
sample project around ?
 
A

Atul

You cannot use Managed classes from pure managed code unless they are
exposed to unmanaged code via COM interop

----------------
-Atul, Sky Software http://www.ssware.com
Shell MegaPack For .Net & ActiveX
Windows Explorer GUI Controls
&
Quick-Launch Like Appbars, MSN/Office2003 Style Popups,
System Tray Icons and Shortcuts/Internet Shortcuts
 
W

William DePalo [MVP VC++]

bonk said:
I am trying to create a dll that internally uses managed types but exposes
a plain unmanaged interface. All the managed stuff shall be "wrapped out
of sight". So that I would be able to use that dll from pure unmanaged
code (for example inherit from classes in that dll). Is something like
that possible. I heared something called ManWarp tried that approach.

If it is possible, how can I do that. Maybe there is a small litttle
sample project around ?

Yes, it's possible. The short answer to your question is "Use 'It just
Works'" (aka IJW)

See here

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/manunmancode.asp

and here

http://msdn.microsoft.com/msdnmag/issues/04/05/visualc2005/default.aspx

for the details.

In case you don't know Managed C++ (aka MC++ soon to be renamed C++/CLI) is
the only CLS compliant language out of MS that allows you to mix managed and
unmanaged code in the same executable component or even the same module. So
what you can do is write unmanaged (native) code which calls into managed
code.

You can use

#pragma managed

and

#pragma unmanaged

delimiters to mark the stretches of code that are native and those that are
not. When a native function calls into a managed one, the compiler handles
the thunking necessary to make the transition.

That said, there are "issues" with mixed-mode DLLs that you must be aware
of. See here for the details.

http://msdn.microsoft.com/library/d...tsfrompureintermediatelanguagetomixedmode.asp

If you can wait the two weeks till VS2005 is released, and if you can target
..Net v2.0 exclusively, you may have an easier time of it.

Regards,
Will
 
B

bonk

William said:
Yes, it's possible. The short answer to your question is "Use 'It just
Works'" (aka IJW)

See here

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/manunmancode.asp

and here

http://msdn.microsoft.com/msdnmag/issues/04/05/visualc2005/default.aspx

for the details.

In case you don't know Managed C++ (aka MC++ soon to be renamed C++/CLI) is
the only CLS compliant language out of MS that allows you to mix managed and
unmanaged code in the same executable component or even the same module. So
what you can do is write unmanaged (native) code which calls into managed
code.

You can use

#pragma managed

and

#pragma unmanaged

delimiters to mark the stretches of code that are native and those that are
not. When a native function calls into a managed one, the compiler handles
the thunking necessary to make the transition.

That said, there are "issues" with mixed-mode DLLs that you must be aware
of. See here for the details.

http://msdn.microsoft.com/library/d...tsfrompureintermediatelanguagetomixedmode.asp

If you can wait the two weeks till VS2005 is released, and if you can target
.Net v2.0 exclusively, you may have an easier time of it.

Regards,
Will
I like your answer better than the One from Atul. Thx. I will look into
it right away. Yes we can target .net 2.0 exclusively and yes we will
use vs 2005. I am working with c++/CLI and vs 2005 beta 2 right now.

Will,

there is one thing I still cannot understand. It has to do with how to
wrap the managed stuff away in that dll. You say I would "write
unmanaged (native) code which calls into managed code" and some of those
unmanaged classes that call into managed code will be exposed using
__declspec(dllexport) correct? If so those unmanaged wrapper classes
will need to hold some private members to the managed classes
(gcroot <ManagedType^> m_mythingy) and I will need to declare it as such
in the header. When I want to use the dll (with its lib) from another
plain unmanaged project I will need to include the header for it and it
will now have gcroot <ManagedType^> m_mythingy; somewhere in the class
declaration. Wich is not possible since my project does not have /clr.
How am I supposed to wrap that away ?
 
T

Tamas Demjen

bonk said:
When I want to use the dll (with its lib) from another
plain unmanaged project I will need to include the header for it and it
will now have gcroot <ManagedType^> m_mythingy; somewhere in the class
declaration. Wich is not possible since my project does not have /clr.
How am I supposed to wrap that away ?

The same way as you would implement plugins: abstract virtual classes.
The client will call your DLL via an interface, which consists of a
bunch of virtual functions:

class MyInterface
{
public:
virtual void DoThis() = 0;
virtual void DoThat() = 0;
};

Inside the mixed-mode DLL you will inherit from this interface:

#pragma unmanaged
class MyImpl : public MyInterface
{
public:
MyImpl() : m_mythingy(gcnew [...])
~MyImpl() { delete m_mythingy; }

virtual void DoThis() { m_mything->DoThis(); }
virtual void DoThat() { m_mything->DoThat(); }

private:
gcroot<ManagedType^> m_mythingy;
};

Now all you have to do is export a function that creates an instance of
your impl:

__declspec(dllexport) MyInterface* Create()
{
return new MyImpl();
}

As far as deleting the returned pointer, you can either export another
function called Delete:

__declspec(dllexport) void Create(MyInterface* pimpl)
{
delete pimpl;
}

or simply add

virtual void DeleteMe() = 0

to the interface and

virtual void DeleteMe()
{
delete this;
}

to the impl. Then your client code would just do this:

MyInterface* p = Create();
p->DoThis();
p->DoThat();
p->DeleteMe();

This is the concept behind it. You don't even export MyImpl, and
MyInterface is just a header file that both the DLL and the client will
understand, as it's native C++.

Tom
 
T

Tamas Demjen

Tamas said:
__declspec(dllexport) void Create(MyInterface* pimpl)
{
delete pimpl;
}

I meant to call this function Delete. I should have also declared a
virtual destructor for MyInterface, that's pretty important, I missed
that originally.

I noticed that you also wanted to implement inheritance at the client
side. The problem is that you can't inherit from MyImpl, because it's
not visible to the client. So you have to add one more level of indirection:

class MyClass
{
public:
MyClass() : pimpl(Create()) { }
virtual ~MyClass() { pimpl->DeleteMe(); }
void DoThis() { pimpl->DoThis(); }
virtual void DoThat() { pimpl->DoThat(); }
private:
MyInterface* pimpl;
};

I chose to declare DoThis non-virtual and DoThat virtual. Note that in
MyInterface you are forced to declare everything virtual, whether you
actually want polymorphism or not -- that's the nature of the abstract
class-based plugin architecture. However, in MyClass, you should only
declare functions virtual is you actually want polymorphism. MyClass is
the base that's the native wrapper around the ManagedType. MyInterface
and MyImpl shouldn't even be used directly, they're just introduced as
an inter-module communication interface, which marshals the calls
between native and managed.

If you need to pass complicated managed types to ManagedType, such as
String^ or cli::array<Whatever^>, then you have to marshal those on your
own, item by item. Alternatively, if you want to make a native STL
container available to .NET without copying/marshaling it, you can wrap
it into IEnumerable. The other way around, if you want to make a .NET
container available to native C++, you can use the exact same technique
that we used for wrapping MyManaged into MyClass.

The down side is that it requires a lot of typing, a lot of tedious,
manual labor. Also, every call goes through two levels of abstraction,
so it adds a minimal overhead to your code. I can imagine that P/Invoke
would add roughly the same amount of overhead too.

Tom
 
W

William DePalo [MVP VC++]

bonk said:
I like your answer better than the One from Atul.

We aim to please. :)

You are welcome.
I will look into it right away. Yes we can target .net 2.0 exclusively and
yes we will use vs 2005. I am working with c++/CLI and vs 2005 beta 2
right now.

Then you are a few steps ahead of me. I hear that the mixed-mode interop
story is significantly better with C++/CLI and .Net 2.0 but I have had no
time to experiment with it.
there is one thing I still cannot understand. It has to do with how to
wrap the managed stuff away in that dll. You say I would "write unmanaged
(native) code which calls into managed code" and some of those unmanaged
classes that call into managed code will be exposed using
__declspec(dllexport) correct?

This is always problematic. Exporting a class based interface from a DLL
where you explicitly export the methods by name is usually only possible in
an environment where all DLL clients use the same compiler. I see that Tom
has already proposed one approach.
If so those unmanaged wrapper classes will need to hold some private
members to the managed classes
(gcroot <ManagedType^> m_mythingy) and I will need to declare it as such
in the header. When I want to use the dll (with its lib) from another
plain unmanaged project I will need to include the header for it and it
will now have gcroot <ManagedType^> m_mythingy; somewhere in the class
declaration. Wich is not possible since my project does not have /clr. How
am I supposed to wrap that away ?

Well, my MVP buds may slap me on the wrist for this, but one thing you can
do is eschew the gcroot template class, use the managed class GCHandle
(garabage collector handle) directly - which is what gcroot does - and cast
that handle to an integer. Of course, all type information is "lost" when
you do that, and later when you cast from integer back to managed type you
better cast correctly or _very_ bad things will happen. :)

I should tell you that I started to put this reply together before Tom
posted his, had dinner, and saw Tom's reply. Feel free to ignore it. But
since it took me more than 30 minutes to test and debug, I thought I'd post
it in its entirety. :)

First, I used this C# class to create an assemby: ClassLibrary1

//------------------------------------------------------------
using System;
using System.Runtime.InteropServices;

namespace ClassLibrary1
{
public class Class1
{
public Class1()
{
}

public void SayHello()
{
Console.WriteLine("C# says hello!");
}
}
}
//------------------------------------------------------------

Then I created a DLL which defines a unmanaged class that proxies the
managed class: ClassLibrary1Shim

Note that I took the expedient approach of exporting an unmanaged procedural
function. Exporting the shim class is left as an exercise to the interested
reader. :) What I've done here is admittedly not very elegant, but it is
very simple. The implemention of every method in the shim class starts out
as unmanaged code and uses IJW to complete the job in managed code. Note
that the shim class stores the garbage collector's handle to the instance of
the managed class as an integer. OK, it's a hack.

Note that you must follow the rules here to build a mixed-mode DLL.

http://msdn.microsoft.com/library/d...tsfrompureintermediatelanguagetomixedmode.asp

//------------------------------------------------------------
using <mscorlib.dll>

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace ClassLibrary1;

#pragma managed

int Allocator()
{
GCHandle gch;
Class1 __gc *cl1;

cl1 = new ClassLibrary1::Class1();
gch = GCHandle::Alloc(cl1);
return GCHandle::blush:p_Explicit(gch).ToInt32();
}

void Deallocator(int h)
{
GCHandle gch;

gch = GCHandle::blush:p_Explicit(h);
gch.Free();
}

void SayHello(int h)
{
GCHandle gch;
Class1 __gc *cl1;

gch = GCHandle::blush:p_Explicit(h);
cl1 = dynamic_cast<Class1 *>( gch.get_Target() );

cl1->SayHello();
}

#pragma unmanaged

class NativeClassLibrary1
{
private:

int h;

public:

NativeClassLibrary1();
~NativeClassLibrary1();
void SayHello();
};

NativeClassLibrary1::NativeClassLibrary1()
{
h = ::Allocator();
}

NativeClassLibrary1::~NativeClassLibrary1()
{
::Deallocator(h);
}

void NativeClassLibrary1::SayHello()
{
::SayHello(h);
}

void __stdcall TheUmanagedExportFunction()
{
NativeClassLibrary1 ncl1;

ncl1.SayHello();
}
//------------------------------------------------------------

I used this module defintion file to build the DLL above.


//------------------------------------------------------------
LIBRARY ClassLibrary1Shim
EXPORTS

TheUmanagedExportFunction

//------------------------------------------------------------

And this is the native executable I used to test the whole mess. Note that I
load the DLL manually only because I am terminally lazy with toy samples.

//------------------------------------------------------------
#include <windows.h>

int main()
{
FARPROC fp;
HINSTANCE h;

h = LoadLibrary("ClassLibrary1Shim.dll");
fp = GetProcAddress(h, "TheUmanagedExportFunction");

(*fp)();

return 0;
}
//------------------------------------------------------------

Finally, I don't know what to make of this, but everything works well except
when I try to enable mixed-mode debugging. Then the IDE crashes. :-(

Regards,
Will
 
T

Tamas Demjen

William said:
Well, my MVP buds may slap me on the wrist for this, but one thing you can
do is eschew the gcroot template class, use the managed class GCHandle
(garabage collector handle) directly - which is what gcroot does - and cast
that handle to an integer. Of course, all type information is "lost" when
you do that, and later when you cast from integer back to managed type you
better cast correctly or _very_ bad things will happen. :)

That's an interesting idea. What if you do this:

#ifdef BUILDING_DLL
#define EXPORT_DLL __declspec(dllexport)
#else
#define EXPORT_DLL __declspec(dllimport)
#endif
class EXPORT_DLL Unmanaged
{
private:
#ifdef _MANAGED
gcroot<Managed^> pimpl;
#else
void* not_your_business;
#endif
};
// untested code ^^^

After all, the compiler only needs to know the size of the object for
instantiation. Since the pimpl member is private, descendants can't
access it anyway. As long as a dummy variable of the same size is
reserved there, we should be fine, theoretically.

Well, it was not my idea originally, so... I know it can bite. For
example, what if Microsoft changes the implementation of gcroot? But a
static assertion would help there, just in case:

#include <boost/static_assert.hpp>
BOOST_STATIC_ASSERT(sizeof(gcroot<Managed^>) == sizeof(void*));
// untested code ^^^

Just an idea, I know it's a hack. Any comments? It's an interesting
discussion.

Tom
 
W

William DePalo [MVP VC++]

Tamas Demjen said:
That's an interesting idea. What if you do this:

#ifdef BUILDING_DLL
#define EXPORT_DLL __declspec(dllexport)
#else
#define EXPORT_DLL __declspec(dllimport)
#endif
class EXPORT_DLL Unmanaged
{
private:
#ifdef _MANAGED
gcroot<Managed^> pimpl;
#else
void* not_your_business;
#endif
};
// untested code ^^^

After all, the compiler only needs to know the size of the object for
instantiation. Since the pimpl member is private, descendants can't access
it anyway. As long as a dummy variable of the same size is reserved there,
we should be fine, theoretically.

Right. I think your idea is in line with Paul DiLascia's:

http://msdn.microsoft.com/msdnmag/issues/05/04/C/
Well, it was not my idea originally, so... I know it can bite.

Yup, but very little is guaranteed with software.
For example, what if Microsoft changes the implementation of gcroot? But a
static assertion would help there, just in case:

#include <boost/static_assert.hpp>
BOOST_STATIC_ASSERT(sizeof(gcroot<Managed^>) == sizeof(void*));
// untested code ^^^

Just an idea, I know it's a hack. Any comments?

"Hack" is my middle name <g>. I used an int. P.DiL used an intptr_t. You use
a void*. To an old C guy like me, any old 32 bits will do. <gd&r>.
Seriously, I wonder if GC handles get widened to 64 bits on a 64 bit
platform?
It's an interesting discussion.

I completely agree. I've learned more than a little through the years
reading some of the groups I follow. And what is really surprising is that
sometimes I gain a deeper understanding of a point I am trying to make in
trying to explain it to someone who has no idea of what I am trying to
convey.

Regards,
Will
 
T

Tamas Demjen

William said:
I used an int. P.DiL used an intptr_t. You use
a void*. To an old C guy like me, any old 32 bits will do. <gd&r>.
Seriously, I wonder if GC handles get widened to 64 bits on a 64 bit
platform?

That's exactly why void* (or any pointer, for that matter), is a better
choice than an int. I read that void* is 64 bits on Win64, while int is
only 32 bits. An int* would be perfect.

By the way, I just took a look at the implementation of gcroot, and it
has a single data member of type void*. That's the main reason I chose
void*, even without thinking of it. I just wanted to ensure that
sizeof(my_dummy_variable) = sizeof(gcroot<T^>).

It would be nice to try with a sample app.

Cheers,
Tom
 
W

William DePalo [MVP VC++]

Tamas Demjen said:
By the way, I just took a look at the implementation of gcroot, and it has
a single data member of type void*. That's the main reason I chose void*,
even without thinking of it. I just wanted to ensure that
sizeof(my_dummy_variable) = sizeof(gcroot<T^>).

I know.

But I can't get excited about such discussions because if you ask me they
always take place with knowledge of the future. By that I mean if void* or
intptr_t _is_ the proper choice it is only because it _is known_ that a GC
handle _is_ the size of a void* or initptr_t now and _will be_ in the
future.

Until you have the knowledge of the future the best you can do is to make
sure that what you choose works now. When I first used my little "int" hack
there was not a 64 bit platform to worry about. And I am all about the
here-and-now. :)
It would be nice to try with a sample app.

I'm not sure I get you. Di Lascia's ManWrap sample seems to be just that,
no?

Regards,
Will
 
B

bonk

Wow this is much more intput than I had expected. I am still chewing on
it. But let me get one thing straight:

#ifdef _MANAGED
gcroot<Managed^> pimpl;
#else
void* not_your_business;
#endif

I assume that if the class I want to export from my mixed mode dll has
to be compiled unmanaged. Correct ?
So I either will put #pragma UNMANAGED in the header (cpp ?) of that
class or I could specify the /CLR switch on a per file basis. Correct ?
Wouldn't #ifdef _MANAGED always evaluate to false? So why put that
#ifdef there in the first place ? I will never compile that part to
managed, will I? Or am I misunderstanding something here ?
 
T

Tamas Demjen

bonk said:
I assume that if the class I want to export from my mixed mode dll has
to be compiled unmanaged. Correct ?

The DLL where you export your class is a mixed mode one, and it has the
_MANAGED symbol defined, as it should. The code inside your wrapper DLL
must use gcroot, otherwise it won't work.

When you compile in mixed mode, your compiler understands the
gcroot<Managed^> expression. You should only disable that when your
compiler can't handle it, which is pure native mode. You never define
the _MANAGED symbol yourself, the compiler does it for you
automatically. It's just a way of detecting that the compiler is unable
to handle the gcroot template. The #ifdef takes care of it
automatically, you don't have to do anything, just export the class with
the #ifdef _MANAGED in it.

Just take a look at the link posted by Will:
http://msdn.microsoft.com/msdnmag/issues/05/04/C/

Search for these lines and read his explanation, it should clarify
everything:

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

Now that I know that someone has actually tried this and published it on
MSDN, it should be (or become) a generally accepted solution. I would
now follow Paul's convention and use the GCHANDLE macro, because this is
what everyone else can relate to. When I originally replied your post, I
honestly didn't know that there was already a published solution to
this, and I was trying to make my own.

Tom
 

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