TheSwamp
Code Red => .NET => Topic started by: MickD on September 13, 2009, 09:34:28 PM
-
It's been a while since I played with mixed/managed C++ and I tried to avoid it by using native arx to handle user input etc. but now I need to do things in a better fashion to make my dll more of a library instead of an application addon.
How would 'you' pass objects like acad pointers such as db's, entities and object id arrays.
Any tips/clues/tricks/samples would be great.
Thanks,
Mick.
-
for example, this compiles -
void MyApp::MyLib::Utils::SetDb(Database* db)
{
if(db != NULL)
{
_pDb = (reinterpret_cast<AcDbDatabase*>(&(db)));
}
}
but so does this -
void MyApp::MyLib::Utils::SetDb(Database* db)
{
if(db != NULL)
{
_pDb = (reinterpret_cast<AcDbDatabase*>(&(db->UnmanagedObject)));
}
}
and is this way of passing objects the most elegant??
-
does this compile?
void MyApp::MyLib::Utils::SetDb(Database^ db)
{
if(db != nullptr)
{
_pDb = reinterpret_cast<AcDbDatabase*>(db->UnmanagedObject);
}
}
-
hmmm, changed the properties to /clr instead of /clr oldsyntax and I now have a swag of '*' errors in my other code, but that part seemed to compile :)
there's also a swag of deprecated errors, may need to set this one up again from scratch...
Do you have a favourite tutorial on this stuff Daniel I could brush up on?
-
Ah sorry, your using the old syntax. which would you prefer using?
What version of Acad are you targeting?
-
2k7 and up, I guess the newer syntax may make it easier to see what's managed and what's not (??)
-
I would use the new coding style. I don't know of too many tutorials covering this,
but I have a lot of samples wrapping native code I can send to you. I'm not sure what your end goal is.
Are you wrapping native stuff? who is the consumer of your library, .NET or native?
-
I'm wrapping native stuff for use in .net rather than using P/Invoke. I have my class/s all sorted out for the mixed/managed part, just need to handle the marshalling of the objects to be dealt with between .net and my m/m dll.
I'll pm you with further info Daniel if needed but I thought it would be helpful for others as well. For instance, if you have some methods that would benefit from the use of native code to speed things up this type of back and forth protocol is very handy.
It's been a while and my C++/CLI is very basic and I still suffer from straight C tendencies but I'll have to deal with those if I want to get this done properly :)
-
...I thought it would be helpful for others as well..
Absolutely! let me dig for a few samples
-
Lets where to start, Of course basic types (int, double...) we don't need to anything special.
here are a couple of samples doing strings and arrays, note I prefer using the CString for the strings and Marshal::Copy for arrays
static void ArxMdg_doit1(void)
{
CString cstr = _T("To Managed and back");
System::String ^str = gcnew System::String(cstr);
acutPrintf(CString(str));
}
static void ArxMdg_doit2(void)
{
TCHAR *tchar = _T("To Managed and back");
System::String ^str = gcnew System::String(tchar);
pin_ptr<const TCHAR> pstr = PtrToStringChars(str);
acutPrintf(pstr);
}
static void ArxMdg_doit3(void)
{
double npt[3] = {10.0,100.0, 1000.0};
array<double> ^mpt = gcnew array<double>(3);
System::Runtime::InteropServices::Marshal::Copy
( System::IntPtr(&npt[0]),mpt, 0, mpt->Length);
acutPrintf(_T("%g, %g ,%g"),mpt[0], mpt[1], mpt[2]);
}
static void ArxMdg_doit4(void)
{
array<double> ^mpt = gcnew array<double>{10.0,100.0, 1000.0};
pin_ptr<const double> npt = &mpt[0];
acutPrintf(_T("%g, %g ,%g"),npt[0], npt[1], npt[2]);
}
-
Next, let's play with value classes, objects that we want to use on the stack.
.h
#pragma once
#pragma managed(push, off)
class CNative
{
LONG_PTR m_num;
public:
CNative(LONG_PTR val);
~CNative(void);
bool isNull(void);
bool isGreaterThan(LONG_PTR val);
bool isLuckySeven(void);
LONG_PTR Num() const { return m_num; }
void Num(LONG_PTR val) { m_num = val; }
};
#pragma managed(pop)
.cpp
#include "StdAfx.h"
#include "Native.h"
CNative::CNative(LONG_PTR val):m_num(val){}
CNative::~CNative(void){}
bool CNative::isNull( void )
{
return this->m_num == NULL;
}
bool CNative::isGreaterThan( LONG_PTR val )
{
return this->m_num > val;
}
bool CNative::isLuckySeven( void )
{
return this->m_num == 7L;
}
now the managed wrapper
.h
#pragma once
#include "Native.h"
using namespace System;
#define UNMNGD_CLASS(X) (*reinterpret_cast<CNative*>(&(X)))
namespace ArcMdg
{
public value class CManaged
{
LONG_PTR m_num;
public:
CManaged(LONG_PTR val);
CManaged(const CNative *native);
bool isGreaterThan(LONG_PTR val)
{
return UNMNGD_CLASS(*this).isGreaterThan(val);
}
property bool isNull
{
bool get() { return UNMNGD_CLASS(*this).isNull() ; }
}
property bool isLuckySeven
{
bool get() { return UNMNGD_CLASS(*this).isLuckySeven() ; }
}
virtual System::String^ ToString() override;
};
}
.cpp
#include "StdAfx.h"
#include "Managed.h"
namespace ArcMdg
{
CManaged::CManaged(LONG_PTR val) : m_num(val)
{
}
CManaged::CManaged( const CNative *native )
{
m_num = native->Num();
}
System::String^ CManaged::ToString()
{
return String::Format("{0}", m_num);
}
}
-
and the test
static void ArxMdg_doit5(void)
{
CNative n(7);
ArcMdg::CManaged m(&n);
if(m.isLuckySeven)
acutPrintf(_T("\nisLuckySeven"));
if(m.isGreaterThan(2))
acutPrintf(_T("\nisGreaterThan 2"));
acutPrintf(_T("\nand the value is %s "), CString(m.ToString()));
}
-
Thanks Dan, let me soak that in for a bit. :)
-
Ok, I think I've got the managed to native ok, I'm still stuck on the native to managed though, I'd like to pass back managed objects so it's seemless on the .net side.
here's what I have so far but the native to managed methods don't work but should give an idea of what I want to achieve.
// mixman.h
#pragma once
using namespace System;
using namespace Autodesk::AutoCAD::ApplicationServices;
using namespace Autodesk::AutoCAD::DatabaseServices;
using namespace Autodesk::AutoCAD::Runtime;
// conversions from managed to native:
#define GETDB(database) (*reinterpret_cast<AcDbDatabase*>(&(database)))
#define GETIDARRAY(objidarray) (*reinterpret_cast<AcDbObjectIdArray*>(&(objidarray)))
// conversion functions from native to managed:
inline Database SetMgdDatabase ( AcDbDatabase* pDb)
{
Database db = gcnew Database(pDb);
return db;
}
inline ObjectIdCollection SetIdCollection(AcDbObjectIdArray* nativeArray)
{
ObjectIdCollection idcoll = gcnew ObjectIdCollection(nativeArray);
return idcoll;
}
-
I think you need to use DisposableWrapper::Create. Most all AutoCAD's managed classes derive from this.
Try these
inline Database^ SetMgdDatabase ( AcDbDatabase* pDb)
{
return (Database^)DisposableWrapper::
Create(Database::typeid, IntPtr(pDb),false);
}
inline ObjectIdCollection^ SetIdCollection(AcDbObjectIdArray* nativeArray)
{
return (ObjectIdCollection^)DisposableWrapper::
Create(ObjectIdCollection::typeid, IntPtr(nativeArray),false);
}
-
Thanks Daniel, I was just looking into that, all this managed lingo is a bit cryptic at times :)
-
Anytime, I had not posted a ref class wrapper yet, but I think those are much easier.
the biggest issue is memory management, I suppose that's why Autodesk derived most all their wrappers from DisposableWrapper (and you can too).
As far as performance goes, I recommend putting all your native code in a .lib library, then make your managed wrappers.
-
Ok, I'm not having much luck here with ObjectIds.
I want to pass back and forth object ids, I have tried ObjectIdCollection-> AcDbObjectIdArray without much luck and erronous reults at best.
What would be the best way to pass an ObjectId[] to C++ and then pass an AcDbObjectIdArray* back to .net??
thanks.
-
I've made a work around for now by iterating though the collection and adding each id to an AcDbObjectId array with the GETOBJECTID macro, seems to work so far.
I'd say I'll need to do the reverse to send it back.
-
how about something like this
static void ToIdArray
(Autodesk::AutoCAD::DatabaseServices::ObjectIdCollection ^col , AcDbObjectIdArray &ids)
{
ids.append(*(AcDbObjectIdArray*)col->UnmanagedObject.ToPointer());
}
static Autodesk::AutoCAD::DatabaseServices::ObjectIdCollection
ToIdCol (AcDbObjectIdArray &ids)
{
Autodesk::AutoCAD::Runtime::DisposableWrapper::
Create(Autodesk::AutoCAD::DatabaseServices::ObjectIdCollection::typeid,
IntPtr(ids.asArrayPtr()),false);
}
-
I'm just using the mdginterop.h macro in a loop like this -
_ids->append (GETOBJECTID(col[i]))
and it's working ok.
What you have there may explain why I was getting over a million object added :roll: :)
I'll give it a go when I get time as it's more elegant, I've got other fish to fry atm.
thanks again Daniel.
-
lots of fun :laugh:
-
Hey Dan,
how do I change an ObjectIdCollection to an ObjectIdCollection^ ??
Basically this is what I need -
ObjectIdCollection^ DCS3D::HlrEngine::Engine::GetVisibleLines()
{
ObjectIdCollection^ ids = //convert AcDbObjectIdArray to ObjectIdCollection^// ;
return ids;
}
These funny new symbols are confusiing in their use :roll: :)
-
Sorry Mick, the first example I gave you of converting an AcDbObjectIdArray to ObjectIdCollection was wrong :-o
try this one :laugh:
static Autodesk::AutoCAD::DatabaseServices::ObjectIdCollection ^ToIdCol (AcDbObjectIdArray &ids)
{
return (ObjectIdCollection^)Autodesk::AutoCAD::Runtime::DisposableWrapper::
Create(Autodesk::AutoCAD::DatabaseServices::ObjectIdCollection::typeid,
IntPtr(ids.asArrayPtr()),false);
}
-
Thanks Dan, that compiled fine, I'll do some testing and get back with the results, cheers.
-
hmm, the output from the C# side is jibberish...
here's what I call from C# -
//engine is the C++ wrapper class.
ObjectIdCollection visIds = engine.GetVisibleLines();
foreach (ObjectId id in visIds)
{
ed.WriteMessage(id.ToString() + " ");
}
here's what I have in my wrapper C++ class -
// helper function to convert native to managed id collections
ObjectIdCollection^ DCS3D::HlrEngine::Engine::ToIdCol (AcDbObjectIdArray *ids)
{
return (ObjectIdCollection^)Autodesk::AutoCAD::Runtime::DisposableWrapper::
Create(Autodesk::AutoCAD::DatabaseServices::ObjectIdCollection::typeid,
IntPtr(ids->asArrayPtr()),false);
}
ObjectIdCollection^ DCS3D::HlrEngine::Engine::GetVisibleLines()
{
ObjectIdCollection^ ids = ToIdCol(_idsVis); // = AcDbObjectIdArray *_idsVis
return ids;
}
I'm missing something simple but can't put my finger on it...
-
I'm holding my breath, 'cause I know what this is for :)
-
I gave away too many clues!
yep, almost there Kerry, this is the last hurdle (I hope) ;)
-
Geez,
sorry about that, it seems if you create the AcDbObjectIdArray on the stack, it will blowup. DisposableWrapper::Create, wants the object to be in the heap.. :mrgreen:
try this out
//interop
Autodesk::AutoCAD::DatabaseServices::ObjectIdCollection ^ToIdCol (const AcDbObjectIdArray &ids)
{
AcDbObjectIdArray *ptr = new AcDbObjectIdArray();
ptr->append(ids);
return dynamic_cast<ObjectIdCollection^>(DisposableWrapper::
Create(ObjectIdCollection::typeid,(IntPtr)ptr,true));
}
-
the test
C++
ObjectIdCollection^ Wrapper::getPointIds( void )
{
ads_name ssname;
ObjectIdCollection^ col = nullptr;
if(acedSSGet(NULL,NULL,NULL,NULL,ssname) != RTNORM)
return nullptr;
AcDbObjectIdArray ids;
if(acedGetCurrentSelectionSet(ids) != eOk)
{
acutPrintf(_T("\nfail"));
acedSSFree(ssname);
return nullptr;
}
col = ToIdCol(ids);
acedSSFree(ssname);
return col;
}
C#
[CommandMethod("doit")]
static public void doit()
{
foreach(ObjectId id in MdgMick.Wrapper.getPointIds())
{
AcAp.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("{0}", id);
}
}
-
Thanks Daniel, I wont get a chance to test for a day or so as I have other commitments but it looks good.
I'm squeesing this coding in between jobs so I really appreciate your help here, I feel a bit guilty for not putting in the study learning the clr stuff but I really only need these few pieces to get me through so again, thanks heaps, I owe you a beer or three :)
cheers.
-
Thanks Daniel, works a treat now, cheers :)
Kerry, I'll send you something soon ;)
-
Take your hurry. (translated : in your own time ... I used to have an Irish neighbour when I was a kid.)
Thanks Mick.
-
Thanks Daniel, works a treat now, cheers :)
Great! please send beer or black licorice to:
Lane 99 PuMing Rd. Bldg #26 Apt#1403
Shanghai, China, 200120
JK :-D
-
Never mind, I just found a small box of Good&Plenty, an extremely rare item in this part of the world.. ah bliss :love:
-
I'll get some Darell Lee in to a bag :)
.. and some Rocklea Road ??
-
I'll get some Darell Lee in to a bag :)
.. and some Rocklea Road ??
Ah YES! Please send me the bill too :wink:
-
The carton is packed and addressed ... in the mail tomorrow.
there is a bonus of some earwax flavoured jelly beans to try :)
.. will take a couple of days to reach you.
In appreciation !
-
Wow! Awesome! Thank you!
I will reciprocate with goodies I have found over here! :kewl:
-
Hi,Daniel
How to covert a managed ResultBuffer object to an unmanaged resbuf?
How to covert an unmanaged resbuf object to a managed ResultBuffer?
-
Pretty much the same way, if wrapper derives from Autodesk.AutoCAD.Runtime.DisposableWrapper, you can use the methods Create and get_UnmanagedObject
resbuf *pRb = reinterpret_cast<resbuf*>(resultBuffer->UnmanagedObject.ToPointer());
//and
ResultBuffer ^resultBuffer =
dynamic_cast<ResultBuffer^>(DisposableWrapper::Create(ResultBuffer::typeid,(IntPtr)pRb,false));
-
Hi Dan,
I've been pulling my hair out wondering why I can't get this finished :(
then I started looking at what I was passing around, I removed all the managed params and it worked fine so I looked a bit closer at what the conversion macros provided in the sdk were doing and I was getting mixed results.
Anyway, I've decided to do these conversions myself so I started with the snippet above but it errors out with 'UnManagedObject is not a member of .....blah blah'
Here's what I have
AcGePoint3d* p1 = reinterpret_cast<AcGePoint3d*>(pnt1->UnmanagedObject.ToPointer());
where pnt1 is passed in as Point3d^
the macro from adsk is -
#define GETPOINT3D(point3d) (*reinterpret_cast<AcGePoint3d*>(&(point3d)))
If I pass it a straight Point3d it spits out 0,0,0 regardless of point data, if I use a Point3d^ it spits out something like 33568899003 (a memory address or garbage??).
So, how is the best way to convert it to native, thanks.
-
Doh!!
I changed it to Point3d (without the ^) in my params and the arx macros works fine.
Sheesh, some consistency would be nice :roll:
-
:lol: well I made these for you
void Wrapper::NativeToManaged()
{
AcGePoint3d npnt(1,1,1);//stack
AcGePoint3d *pnpnt = new AcGePoint3d(2,2,2);//heap
Point3d mpnt1 = ToPoint3d( npnt );
acutPrintf(_T("\n%g,%g,%g"),mpnt1.X,mpnt1.Y,mpnt1.Z);
Point3d mpnt2 = ToPoint3d( *pnpnt );
acutPrintf(_T("\n%g,%g,%g"),mpnt2.X,mpnt2.Y,mpnt2.Z);
Point3d ^pmpnt1 = %ToPoint3d( npnt );
acutPrintf(_T("\n%g,%g,%g"),pmpnt1->X,pmpnt1->Y,pmpnt1->Z);
Point3d ^pmpnt2 = %ToPoint3d( *pnpnt );
acutPrintf(_T("\n%g,%g,%g"),pmpnt2->X,pmpnt2->Y,pmpnt2->Z);
delete pnpnt;
}
void Wrapper::ManagedToNative()
{
Point3d mpnt(3,3,3); //stack
Point3d ^pmpnt = gcnew Point3d(4,4,4);//heap
AcGePoint3d npnt1 = GETPOINT3D( mpnt );
acutPrintf(_T("\n%g,%g,%g"),npnt1.x,npnt1.y,npnt1.z);
AcGePoint3d npnt2 = GETPOINT3D( (Point3d)*pmpnt );
acutPrintf(_T("\n%g,%g,%g"),npnt2.x,npnt2.y,npnt2.z);
AcGePoint3d *pnpnt1 = &GETPOINT3D( mpnt );
acutPrintf(_T("\n%g,%g,%g"),pnpnt1->x,pnpnt1->y,pnpnt1->z);
AcGePoint3d *pnpnt2 = &GETPOINT3D( (Point3d)*pmpnt );
acutPrintf(_T("\n%g,%g,%g"),pnpnt2->x,pnpnt2->y,pnpnt2->z);
}
-
I would try to keep those value types on the stack so you're not running into pinning pointers and stuff.
-
woot! Thanks Dan.
Just having trouble with the database now, I'm passing in a Database^, this is what I have -
#define GetDb(database) (reinterpret_cast<AcDbDatabase*>(&(database)))
but no worky
this mixed/managed stuff has sooo many subtle tricks to watch out for, I'm almost there though!
-
The casting only works for managed value types. for ref types you need to do something like
AcDbDatabase *pnDb = pmDb->UnmanagedObject.ToPointer();
//and
Database ^pmDb = dynamic_cast<Database^>(DisposableWrapper::Create(Database::typeid,(IntPtr)pnDb,false));
-
hmm, I think I'm getting the db ok but as I'm creating a new one in C# I think it's open for write and I'm trying to add abjects to it in native code, I'll dig a bit deeper and let you know, thankis again :)
-
Cool! May your pointers all point to the same instance :lol:
-
woot!
Thanks Daniel, working fine now with a small adjustment, cheers.
pDb = (AcDbDatabase*)dbout->UnmanagedObject.ToPointer();