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

0 Members and 1 Guest are viewing this topic.

pkohut

  • Guest
Writable "read only system variables"
« on: August 25, 2009, 02:16:16 AM »
Outlined below is a method to have read/write access to read only system variables.  In particular the system variables TDCREATE, TDUCREATE, TDUPATE, TDUUPDATE, TDUSRTIMER, and TDINDWG are going to be modifiable.  Also, the target platform is AutoCAD 2004, other versions will probably work but plan on tweaking the code a little bit as the name mangling of functions may be different from release to release.  Let me also stress that you probably don’t want the code below for programs that are distributed outside your domain of control, unless you plan to test and support every flavor and vertical of AutoCAD.
 
To keep from getting buried in details I’ve got to assume the targeted audience understands C++ calling conventions, C++ name mangling and how to un-mangle them (at least to the level of using Dependency Walker).  Understanding of VTables would also be helpful if you’re planned to take the project further, but not necessary for the article.  Good, I think the article just got shorter by 3 pages.
 
For those that are hungry here is the final piece of code.
Code: [Select]
// Define callbacks for functions
typedef AcDbDate (CALLBACK* TDDINDWG)(void);
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);
void SetDwgDate(void)
{
HMODULE hMod = GetModuleHandle("acdb16.dll");
SET_TDD_INDWG setTddInDwg = (SET_TDD_INDWG) GetProcAddress(hMod, "?setTddInDwg@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
SET_TDD_USR_TIMER setTddUsrTimer = (SET_TDD_USR_TIMER) GetProcAddress(hMod, "?setTddUsrTimer@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
SET_TDL_CREATE setTdlCreate = (SET_TDL_CREATE) GetProcAddress(hMod, "?setTdlCreate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
SET_TDL_UPDATE setTdlUpdate = (SET_TDL_UPDATE) GetProcAddress(hMod, "?setTdlUpdate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
SET_TDU_CREATE setTduCreate = (SET_TDU_CREATE) GetProcAddress(hMod, "?setTduCreate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");
SET_TDU_UPDATE setTduUpdate = (SET_TDU_UPDATE) GetProcAddress(hMod, "?setTduUpdate@AcDbHeader@@QAE?AW4ErrorStatus@Acad@@ABVAcDbDate@@@Z");

SLOW_DB_HEADER slowDbHeader = (SLOW_DB_HEADER) GetProcAddress(hMod, "?slowDbHeader@AcDbStub@@ABEPAVAcDbHeader@@XZ");
SLOW_DB_HEADER * pSlowDbHeader = NULL;

Acad::ErrorStatus es;
AcDbDate dbDate;

// set _thiscall to 0
_asm mov ecx, 0
_asm call slowDbHeader
// store the returned instance of AcDbHeader
_asm mov pSlowDbHeader, eax

// set _thiscall to the instance of AcDbHeaer and call
// setTdlCreate like any other function.
_asm mov ecx, pSlowDbHeader
es = setTdlCreate(dbDate);

_asm mov ecx, pSlowDbHeader
es = setTduCreate(dbDate);
_asm mov ecx, pSlowDbHeader
es = setTdlUpdate(dbDate);
_asm mov ecx, pSlowDbHeader
es = setTduUpdate(dbDate);
_asm mov ecx, pSlowDbHeader
es = setTddInDwg(dbDate);
_asm mov ecx, pSlowDbHeader
es = setTddUsrTimer(dbDate);
}

Of special interest are the functions pointers we are retrieving via the C++ mangled names.  Here’s the function signatures unmangled.
Quote
enum Acad::ErrorStatus AcDbHeader::setTddInDwg(class AcDbDate const &)
enum Acad::ErrorStatus AcDbHeader::setTddUsrTimer(class AcDbDate const &)
enum Acad::ErrorStatus AcDbHeader::setTdlCreate(class AcDbDate const &)
enum Acad::ErrorStatus AcDbHeader::setTdlUpdate(class AcDbDate const &)
enum Acad::ErrorStatus AcDbHeader::setTduCreate(class AcDbDate const &)
enum Acad::ErrorStatus AcDbHeader::setTduUpdate(class AcDbDate const &)
By themselves the function pointers are useless because they are members of AcDbHeader, which is also undocumented, and we need an instance to AcDbHeader.  The function
Code: [Select]
class AcDbHeader * AcDbStub::slowDbHeader(void)is used to get the instance pointer to AcDbHeader.  The rest of the code just sets up some registers and makes calls to the AcDbHeader member functions.

AcDbHeader, AcDbStub, and _thiscall
To understand how to pull all this together we need understand a few details.  First, how is AcDbHeader implemented?  We can make a couple guesses, either AcDbDatabase is derived fom AcDbHeader, or AcDbHeader is a member of AcDbDatabase.  That question is easily answered by the following statements:
  • 1.  AcDbDatabase “is a” AcDbHeader – (not likely) = FALSE
  • 2.  AcDbDatabase “has a” AcDbHeader  - (this makes more sense, but turns out not to be true)
Even though item 2 makes sense, we don’t find any getters/setters in AcDbDatabase for AcDbHeader.  Instead the only getter we find is slowDbHeader(void), which is a member function of AcDbStub.  The ARX documentation has this to say about AcDbStub in the AcDbObjectId description:
Quote
The second part is a "stub" object (class AcDbStub) that always resides in memory and acts as the access point for the DRO.
Quote
an AcDbObjectId object is a container for the address of a DRO's stub. As such, it is an extremely important object because it contains the only session-persistent locator for the DRO
What this is really saying is that AcDbStub is a compostion of AcDbObjectId, more specifically AcDbObjectId “strongly owns a” AcDbStub.  Look at the ARX header file “dbid.h” for the class definition of AcDbObjectId and you see the relationship.

With this bit of info it looks as if we can get a pointer to AcDbHeader just by dereferencing AcDbObjectId and calling slowDbHeader().  The code for that is:
Code: [Select]
void * pStub = (void *&) * acdbHostApplicationServices()->workingDatabase()->textStyleTableId();
_asm mov ecx, pStub
_asm call slowDbHeader
_asm mov pAcDbHeader, eax
But it causes an exception, I didn’t investigate the reason because and a quick disassembly of slowDbHeader shows that it excepts 0 as the thiscall pointer (register ecx).  Setting up the slowDbHeader call is then done like so.
Code: [Select]
_asm mov ecx, 0
_asm call slowDbHeader
_asm mov pAcDbHeader, eax

and pAcDbHeader is now pointing to a valid instance of AcDbHeader. Making the AcDbHeader::setTd* calls is now easy, just make sure the ecx register contains the instance pointer of pAcDbHeader.

Summary
So there you have it, a way to change drawing time properties that were previously read only.  On the 32 bit system the calling convention is _thiscall, which is just like _stdcall except the ecx register holds the instance pointer of a class.  On 64 bit Windows the calling convention is _fastcall, and the code will need modification to handle it.  As long as the stack and registers are set up properly any of the AcDbHeader functions can be called.  Even with just the few functions used here you can see that the code can quickly become a maintance nightmare and error prone.
In the next article I'll wrap the AcDbHeader function we played with in a more portable and typesafe manner.

Paul

It's Alive!

  • BricsCAD
  • Needs a day job
  • Posts: 7041
  • AKA Daniel
Re: Writable "read only system variables"
« Reply #1 on: August 25, 2009, 02:27:55 AM »

I'm still absorbing this, but just as a note, x64 doesn't support inline asm, I think one would need to use intrinsics.

pkohut

  • Guest
Re: Writable "read only system variables"
« Reply #2 on: August 25, 2009, 03:05:35 AM »

I'm still absorbing this, but just as a note, x64 doesn't support inline asm, I think one would need to use intrinsics.


Thanks,
I won't be able to test a 2010 64 bit version for a couple weeks.  I could though test inline asm on a straight
windows exe.

It's Alive!

  • BricsCAD
  • Needs a day job
  • Posts: 7041
  • AKA Daniel
Re: Writable "read only system variables"
« Reply #3 on: August 25, 2009, 03:23:21 AM »
I wonder how hard it would be to p/invoke those from .NET?

pkohut

  • Guest
Re: Writable "read only system variables"
« Reply #4 on: August 25, 2009, 03:34:36 AM »
Part 2 http://www.theswamp.org/index.php?topic=30016.0 wraps the raw code
into typesafe template functions.