Author Topic: Writable "read only system variables" (packaged)  (Read 2235 times)

0 Members and 1 Guest are viewing this topic.

pkohut

  • Guest
Writable "read only system variables" (packaged)
« on: August 25, 2009, 03:03:16 AM »
Ok, the code for the prior topic about writable "read only system variables", was a bit raw and
a maintance nightmare.  Here I've packaged up the code to be more portable and less prone
to bugs.  The method outlined doesn't use classes because that was just causing me fits with
the THISCALL calling convention, stuffing the "this" pointer for "MyAcDbHeader" onto the
ecx register automatically.

The goal of the code is to make calling member functions of AcDbHeader in a typesafe and
semi portable manner.  Most of the code is for the compiler, allowing it to catch
syntax errors and type mismatches at compile time.  The template functions do most of the real
work and the compiler optimizes these very nicely for release builds.

Again I present the complete code first:
Code: [Select]
// MyAcDbHeader.h
#pragma once


#if defined(_M_IX86)
// code is stricly for 32bit x86
namespace MyAcDbHeader {

// AutoCAD AcDbHeader typedefs.  Get these from the undecorated names
typedef Acad::ErrorStatus (CALLBACK* SET_TDD_INDWG) (AcDbDate const &);
typedef Acad::ErrorStatus (CALLBACK* SET_TDD_USR_TIMER) (AcDbDate const &);
typedef Acad::ErrorStatus (CALLBACK* SET_TDL_CREATE) (AcDbDate const &);
typedef Acad::ErrorStatus (CALLBACK* SET_TDL_UPDATE) (AcDbDate const &);
typedef Acad::ErrorStatus (CALLBACK* SET_TDU_CREATE) (AcDbDate const &);
typedef Acad::ErrorStatus (CALLBACK* SET_TDU_UPDATE) (AcDbDate const &);
typedef void * (CALLBACK* SLOW_DB_HEADER) (void);

//// defined to test different template definitions
//typedef double (CALLBACK* DIM_SCALE) (void);
//typedef Acad::ErrorStatus (CALLBACK* GET_DIMSTYLE_CHILE_DATA) (AcRxClass const *, AcDbDimStyleTableRecord * &, AcDbObjectId &);


// pointers to AutoCAD AcDbHeader function pointers.
// Before using these function they first need to be initialized by calling
// InitAcDbHeader, otherwise they are set to NULL and will GPF.
extern SET_TDD_INDWG setTddInDwg;
extern SET_TDD_USR_TIMER setTddUsrTimer;
extern SET_TDL_CREATE setTdlCreate;
extern SET_TDL_UPDATE setTdlUpdate;
extern SET_TDU_CREATE setTduCreate;
extern SET_TDU_UPDATE setTduUpdate;
extern SLOW_DB_HEADER slowDbHeader;
extern SLOW_DB_HEADER * pAcDbHeader;

//// declared to test different template definitions
//extern DIM_SCALE dimScale;
//extern GET_DIMSTYLE_CHILE_DATA getDimStyleChildData;


// InitAcDbHeader, must be called prior to using any other function pointers.
// Makes call to SetAcDbHeaderInstance before returning.
void InitAcDbHeader(AcDbObjectId stubId = AcDbObjectId::kNull);

// SetAcDbHeaderInstanceFrom retieves the AcDbHeader from AutoCAD by
// dereferencing stubId to _thiscall (ecx) prior to calling slowDbHeader.
// If stubId == AcDbObjectId::kNull then _thiscall (ecx) is set to 0 prior
// to calling slowDbHeader.
void SetAcDbHeaderInstanceFrom(AcDbObjectId stubId);



// set 1 byte alignment
#pragma pack(push, 1)

// define typesafe template for AcDbHeader functions that have
// no parameters.
// T is the return type
// T1 is the type of the callee
template<class T, class T1>
T func(T1 callee)
{
_asm mov ecx, pAcDbHeader
T rVal = callee();
return rVal;
}

// define typesafe template for AcDbHeader functions that have
// 1 parameter.
// T is the return type
// T1 is the type of the callee
// T2 is the type for parameter 1
template<class T, class T1, class T2>
T func(T1 callee, T2 const & inObj)
{
_asm mov ecx, pAcDbHeader
T rVal = callee(inObj);
return rVal;
}

// define typesafe template for AcDbHeader functions that have
// 3 parameters.
// T is the return type
// T1 is the type of the callee
// T2 is the type for parameter 1, T3 for parameter 2, T4 for parameter 3
template<class T, class T1, class T2, class T3, class T4>
T func(T1 callee, T2 const * inPtr, T3 *& refPtr, T4 & outObj)
{
_asm mov ecx, pAcDbHeader
T rVal = callee(inPtr, refPtr, outObj);
return rVal;
}

#pragma pack(pop)

}
#endif

Code: [Select]
// MyAcDbHeader.cpp
#include <StdAfx.h>
#include "MyAcDbHeader.h"

namespace MyAcDbHeader {

#if defined(_M_IX86)
// code is stricly for 32bit x86

// Define acad family from 2004 to 2006.  They should all have
// the same mangled AcDbHeader function names, but needs to be
// varified and coded accordingly.
// Calling convention is THISCALL, so register ECX needs to be
// set properly prior to making function calls.
#if defined(ARX2004)
#define ACAD2004_FAMILY 1
#elif defined(ARX2005)
#define ACAD2004_FAMILY 1
#elif defined(ARX2006)
#define ACAD2004_FAMILY 1
#endif

// 2007 through 2009 should also have the same mangled AcDbHeader function names
// Place family check code here, and Place mangled names in InitAcDbHeader.

// 2010 32 bit, same as 2007 family.
// 2010 64 bit, calling convention is FASTCALL, Guessing the _thiscall will need to be placed
// in RCX, but haven't really looked into it

// Declare instances of AcDbHeader functions.
SET_TDD_INDWG setTddInDwg = NULL;
SET_TDD_USR_TIMER setTddUsrTimer = NULL;
SET_TDL_CREATE setTdlCreate = NULL;
SET_TDL_UPDATE setTdlUpdate = NULL;
SET_TDU_CREATE setTduCreate = NULL;
SET_TDU_UPDATE setTduUpdate = NULL;
SLOW_DB_HEADER slowDbHeader = NULL;
SLOW_DB_HEADER * pAcDbHeader = NULL;

//// declared to test different template definitions
//DIM_SCALE dimScale = NULL;
//GET_DIMSTYLE_CHILE_DATA getDimStyleChildData = NULL;


void InitAcDbHeader(AcDbObjectId stubId) {
HMODULE hMod = GetModuleHandle("acdb16.dll");

#ifdef ACAD2004_FAMILY
// Get addresses of AcDbHeader functions, using mangled string names.
setTddInDwg = (SET_TDD_INDWG) GetProcAddress(hMod, "?setTddInDwg@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
setTddUsrTimer = (SET_TDD_USR_TIMER) GetProcAddress(hMod, "?setTddUsrTimer@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
setTdlCreate = (SET_TDL_CREATE) GetProcAddress(hMod, "?setTdlCreate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
setTdlUpdate = (SET_TDL_UPDATE) GetProcAddress(hMod, "?setTdlUpdate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
setTduCreate = (SET_TDU_CREATE) GetProcAddress(hMod, "?setTduCreate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
setTduUpdate = (SET_TDU_UPDATE) GetProcAddress(hMod, "?setTduUpdate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
slowDbHeader = (SLOW_DB_HEADER) GetProcAddress(hMod, "?slowDbHeader@AcDbStub@@ABEPAVAcDbHeader@@XZ");

//// a couple extra's to test different template definitions
//dimScale = (DIM_SCALE) GetProcAddress(hMod, "?dimscale@AcDbHeader@@QBENXZ");
//getDimStyleChildData = (GET_DIMSTYLE_CHILE_DATA) GetProcAddress(hMod, "?getDimstyleChildData@AcDbHeader@@QBE?AW4ErrorStatus@Acad@@PBVAcRxClass@@AAPAVAcDbDimStyleTableRecord@@AAVAcDbObjectId@@@Z");
#endif
SetAcDbHeaderInstanceFrom(stubId);
}


void SetAcDbHeaderInstanceFrom(AcDbObjectId stubId)
{
void * pStub = stubId.isValid() ? (void *&) *stubId : NULL;
_asm mov ecx, pStub
_asm call slowDbHeader
_asm mov pAcDbHeader, eax
}

#endif
}

Code: [Select]
// acrxEntryPoint.cpp
...
...
#include "MyAcDbHeader.h"
...
...

// - navSetDwgDate.setDwgDate command (do not rename)
static void navSetDwgDatesetDwgDate(void)
{
using namespace MyAcDbHeader;

InitAcDbHeader();

AcDbDate dt;
Acad::ErrorStatus es;
es = func<Acad::ErrorStatus, SET_TDD_INDWG, AcDbDate>(*setTddInDwg, dt);
es = func<Acad::ErrorStatus, SET_TDD_USR_TIMER, AcDbDate>(*setTddUsrTimer, dt);
es = func<Acad::ErrorStatus, SET_TDL_CREATE, AcDbDate>(*setTdlCreate, dt);
es = func<Acad::ErrorStatus, SET_TDL_UPDATE, AcDbDate>(*setTdlUpdate, dt);
es = func<Acad::ErrorStatus, SET_TDU_CREATE, AcDbDate>(*setTduCreate, dt);
es = func<Acad::ErrorStatus, SET_TDU_UPDATE, AcDbDate>(*setTduUpdate, dt);



// double d = func<double, DIM_SCALE>(*dimScale);

// AcRxClass * rxClass = NULL; // needs to point to an instance of dimension class descriptor
// AcDbDimStyleTableRecord * pRcd = NULL; // needs to point to instance of AcDbDimStyleTableRecord
// AcDbObjectId styleId; // if es == eOk then will == dimension style ID
// es = func<Acad::ErrorStatus, GET_DIMSTYLE_CHILE_DATA>(*getDimStyleChildData, rxClass, pRcd, styleId);
}

Template "func" (forgot to rename it to something better) is overriden with 3 different signatures.  2 of these are just test cases to make
sure overriding works (it does).  Hopefully the code is documented enough to understand.  If not myself or someone else here will
be happy to answer your questions.

Oh, there is no error trapping.  If there is an exception I want the program to GPF that away the code can be fixed.  Error handling
could be added, but if an exception occurs the state of the application and AutoCAD would be unknown, so I think it is best that it just faults.  :-)

Challenge
I'm not doing anything with the code, here have it.  So, the challenge is to add support for the other flavors of AutoCAD, maybe even figure out
how to wrap it in its own nice class.

TIA,
Paul


It's Alive!

  • BricsCAD
  • Needs a day job
  • Posts: 6941
  • AKA Daniel
Re: Writable "read only system variables" (packaged)
« Reply #1 on: August 25, 2009, 08:43:34 AM »
Pretty interesting stuff Paul.   8-)

owenwengerd

  • Bull Frog
  • Posts: 439
Re: Writable "read only system variables" (packaged)
« Reply #2 on: September 01, 2009, 12:47:10 PM »
It's possible to do this efficiently without using any inline assembly, and relying only on C++ pointers-to-member. See e.g. http://otb.manusoft.com/2009/03/objectarx-2010-dealing-with-missing.htm for an example of this approach.

pkohut

  • Guest
Re: Writable "read only system variables" (packaged)
« Reply #3 on: September 01, 2009, 03:30:56 PM »
Nice code Owen.  I was going to go a similar route but dismissed it on the grounds that I
didn't know if or what class AcDbHeader is derived from in order to stay in sync the the vtbl.
Since the vtables are built at compile time AcDbHeader would need to know, just as the
example on your page knows for AcGiFaceData.

Nice solution Owen, and works well if the class structure is known at compile time.  Otherwise
less graceful methods must be employed  :-)

Paul

owenwengerd

  • Bull Frog
  • Posts: 439
Re: Writable "read only system variables" (packaged)
« Reply #4 on: September 01, 2009, 03:48:04 PM »
Actually you can cheat and use the functions without knowing the original vtable structure. Just declare your own AcDbHeader class any way you want. Any virtual members that you add can be declared as __declspec(dllimport), and voila! The only limitation is that you can't pass your own AcDbheader object around to anyone that isn't using your AcDbheader declaration (in practice, that means you need to jump through some hoops to make sure that you're passing one of AutoCAD's AcDbHeader objects as the *this* pointer when you call the function).
« Last Edit: September 01, 2009, 04:03:35 PM by owenwengerd »

owenwengerd

  • Bull Frog
  • Posts: 439
Re: Writable "read only system variables" (packaged)
« Reply #5 on: September 01, 2009, 03:55:01 PM »
Oh, and just to clarify: the vtable trick in my post is only necessary when the function is *not* exported. As long as the function is exported, you don't need to know the vtable order.

pkohut

  • Guest
Re: Writable "read only system variables" (packaged)
« Reply #6 on: September 01, 2009, 03:57:06 PM »
 :-D oops, forget to get rid of the first paragraph.

Thanks, there's always more than one way to ...(insert metaphor here)...