.NET

.NET

Reply
Distinguished Contributor
alex_b
Posts: 405
Registered: ‎08-15-2003
Message 11 of 24 (451 Views)

Re: Retrieve Command names from ARX file

11-07-2012 11:59 AM in reply to: khoa.ho

Khoa,

 

A crazy thought:

What if we implemented our own acedRegCmds->addCommand(), then call acrxEntryPoint() in our arx, somehow causing it to call our addCommand().

Once our method is called, it sees the command name string in the call parameters.

As for the acrxGetApiVersion(), it seems its return type is int.

 

alex

Mentor
khoa.ho
Posts: 224
Registered: ‎09-15-2011
Message 12 of 24 (436 Views)

Re: Retrieve Command names from ARX file

11-07-2012 11:19 PM in reply to: alex_b

Alex,

 

ARX file is compiled by ObjectARX SDK so it has all dependent references inside. Those dependencies also have their other nested dependencies, and they are wiring each other to live in the same AutoCAD installation directory. When we call a method inside an ARX file, all related methods (in other native DLLs) will get called accordingly. So a .NET app that uses an ARX file should reference the world of AutoCAD native assemblies.

 

.NET plug-in has commands exposed as .NET attributes (CommandMethodAttribute), so it will be easier to get those commands using .NET reflection (with help from Mono.Cecil library).

 

See an example of registering commands in ARX (C++ code):

AcRx::AppRetCode
    acrxEntryPoint(AcRx::AppMsgCode msg, void* appId)
{
    switch (msg) {
    case AcRx::kInitAppMsg:
        acrxDynamicLinker->unlockApplication(appId);
        acrxDynamicLinker->registerAppMDIAware(appId);
        gpEdReac = new AsdkNODEdReactor();
        acedRegCmds->addCommand(_T("ASDK_CLONENOD_TEST"),
            _T("ASDK_NODSETUP"),
            _T("NODSETUP"),
            ACRX_CMD_TRANSPARENT,
            setup);
        break;
    case AcRx::kUnloadAppMsg:
        clearReactor();
        acedRegCmds->removeGroup(_T("ASDK_CLONENOD_TEST"));
        delete gpEdReac;
    }
    return AcRx::kRetOK;
}

 

ARX plug-in defines a command as code content of acrxEntryPoint method, without exposing outside to retrieve. So unless we can decompile C++ code to read those commands, we need to call acrxEntryPoint method to ask it to run acedRegCmds->addCommand(). This addCommand method will store defined commands somewhere. From there, we can retrieve those commands that we need to find.

 

acedRegCmds is a macro of the class AcEdCommandStack with method addCommand. AcEdCommandStack comes from accmd.h which I don’t know exactly its DLL file.

 

A solution that I can think of is to create some “fake” native DLL files with names copied from AutoCAD real DLL files. Those “fake” DLL files will be implemented only some needed functions with non-actions, except addCommand will return a list of added commands, and we retrieve them from there using DllImport in .NET code.

 

Because ARX is C++ code so it should call another C++ code from its code. “Fake” or real AutoCAD native DLL files are still needed to reference in this ARX file. If we can decompile or binary-read ARX file to extract raw command strings, we can skip C++ calls and ignore all above explanations.

 

I try to make it clear and logic to solve this challenge topic. Not sure to find the final code but at least we can learn something of the complicated world of managed and unmanaged code living together in AutoCAD.

 

-Khoa

Distinguished Contributor
alex_b
Posts: 405
Registered: ‎08-15-2003
Message 13 of 24 (425 Views)

Re: Retrieve Command names from ARX file

11-08-2012 05:20 AM in reply to: khoa.ho

Khoa

 

This is looking more and more like hacking (in the legit sense of the word, of course) :smileyhappy:

To the point:

I looked inside a few .h files and noticed that:

- the command strings are stored in a dictionary maintained by Acad, therefore, I'm not sure how we can look at it without a full Acad running.

- inside acrxEntryPoint() there can be any calls made, not just the traditional ones, therefore we might be forced to load all Acad native assemblies. If we have to do that, it may be simpler to load our arx into a running instance of Acad and use the OARX API with COM to retrieve the command stack contents (it will work of course only for installed versions of Acad, defeating part of the program purpose which is to display a list of defined commands together with the respective Acad versions they were compiled for, ideally without need for Acad to be even installed).

 

It seems that the simplest? way could be disassembling/binary reading the arx and retrieving the command name strings, but I haven't a clue how to do that. Do you know of any library a la Mono.Cecil which can do that for C++ code?

 

Now about acrxGetApiVersion(), things are better. It rerurns an int as:

 

0x000E0000 for V14

0x000F0000 for V2000-2002

0x00100000 for V2004-2006

0x00110000 for V2007-2009

0x00120000 for V2010-2012

 

I changed the delegate method type to int and the code you posted works OK.

It seems that acrxGetApiVersion() does not make any calls to methods in native dll's.

 

Thanks a lot for your efforts

 

alex

 

Moderator
Alexander.Rivilis
Posts: 1,450
Registered: ‎04-09-2008
Message 14 of 24 (417 Views)

Re: Retrieve Command names from ARX file

11-08-2012 05:46 AM in reply to: alex_b

This is looking more and more like hacking (in the legit sense of the word, of course) :smileyhappy:

+1000


It seems that the simplest? way could be disassembling/binary reading the arx and retrieving the command name strings, but I haven't a clue how to do that. Do you know of any library a la Mono.Cecil which can do that for C++ code?

1) What about command name is encrypted (protection) or reading from other file/files (localization)?
2) What about command is registering not inside acrxEntryPoint() function? It is also possible.

3) What about arx-registered lisp-function (with help of acedDefun() function)?


Now about acrxGetApiVersion(), things are better. It rerurns an int as:

 

0x000E0000 for V14

0x000F0000 for V2000-2002

0x00100000 for V2004-2006

0x00110000 for V2007-2009

0x00120000 for V2010-2012
0x00130000 for V2013

 


But arx-application can be or x86 or x64. With external x86 .NET-application you can load only x86 arx-application, not x64 arx-application

 

 


Пожалуйста не забывайте про Утвердить в качестве решения! Утвердить в качестве решения и Give Kudos!Баллы
Please remember to Accept Solution! Accept as Solution and Give Kudos!Kudos

Distinguished Contributor
alex_b
Posts: 405
Registered: ‎08-15-2003
Message 15 of 24 (404 Views)

Re: Retrieve Command names from ARX file

11-08-2012 07:14 AM in reply to: Alexander.Rivilis

Alexander

 

I am aware that there are limitations.

Some of them are probably solvable:

 

>>1) What about command name is encrypted (protection) or reading from other file/files (localization)?

 

Probably either not solvable or not worth the effort to break emcriptions etc. If the people who wrote the plug-in have reasons to be secretive, let them.


>>2) What about command is registering not inside acrxEntryPoint() function? It is also possible.

>>3) What about arx-registered lisp-function (with help of acedDefun() function)?

 

If the main solution is by disassembling or binary reading the arx, then we'll be able to find all the calls to addCommand() and to acedDefun() and retrieve the string arguments (hopefuly not encrypted).

 

As for acrxGetApiVersion(), it's true it may be x64. After solving the problem for x86, I'll give some thought to extending it to x64. By then, we'll maybe have x128.

 

Thanks

 

alex

Distinguished Contributor
alex_b
Posts: 405
Registered: ‎08-15-2003
Message 16 of 24 (398 Views)

Re: Retrieve Command names from ARX file

11-08-2012 07:30 AM in reply to: khoa.ho

Khoa

 

>>acedRegCmds is a macro of the class AcEdCommandStack with method addCommand. AcEdCommandStack comes from accmd.h which I don’t know exactly its DLL file.

 

It may be not in a dll but in Acad.exe In fact, I think it is in acad.exe

 

alex

Moderator
Alexander.Rivilis
Posts: 1,450
Registered: ‎04-09-2008
Message 17 of 24 (395 Views)

Re: Retrieve Command names from ARX file

11-08-2012 07:38 AM in reply to: alex_b

addCommand is virtual method.


Пожалуйста не забывайте про Утвердить в качестве решения! Утвердить в качестве решения и Give Kudos!Баллы
Please remember to Accept Solution! Accept as Solution and Give Kudos!Kudos

Mentor
khoa.ho
Posts: 224
Registered: ‎09-15-2011
Message 18 of 24 (381 Views)

Re: Retrieve Command names from ARX file

11-08-2012 10:33 PM in reply to: alex_b

Alex,

 

I rewrite the code to make it clean and logic for future improvement, with break-down methods using IntPtr and Delegate. Therefore, the main code looks easier to read and is reusable.

 

The good thing is acrxGetApiVersion does not call any external C++ functions and it works to return a number that presents an AutoCAD version. I only tested on AutoCAD 2009 and 2013 with the return numbers are 1114112 and 1245184 accordingly. So you will check again the new code in your machine.

 

The not good thing as we know is acrxEntryPoint calls many C++ unsafe methods from many external native assembly files in the AutoCAD installation directory, and I also don’t know the IntPtr appId to pass into this method. So this function call will cause “Unsafe Invoke Internal method” errors. Even I tested to copy the .exe file to the AutoCAD folder.

 

To build this console app running as 32 or 64 bits, on Visual Studio Configuration Manager, switch to x86 or x64 and compile each cases. 32-bit x86 app does not read AutoCAD 64 bit ARX files. So it is better to change the platform to build x86 or x64 on VS.

 

See my latest code, with code refactor and without fix to find AutoCAD command names:

using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace AcadReflection
{
    class Program
    {
        #region Import external native DLL functions
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr LoadLibrary(string name);

        /// <summary>
        /// To load the dll - dllFilePath dosen't have to be const - so I can read path from registry
        /// </summary>
        /// <param name="dllFilePath">file path with file name</param>
        /// <param name="hFile">use IntPtr.Zero</param>
        /// <param name="dwFlags">What will happend during loading dll
        /// <para>DONT_RESOLVE_DLL_REFERENCES = 0x00000001</para>
        /// <para>LOAD_LIBRARY_AS_DATAFILE = 0x00000002</para>
        /// <para>LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008</para>
        /// <para>LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010</para>
        /// </param>
        /// <returns>Pointer to loaded Dll</returns>
        [DllImport("kernel32")]
        private static extern IntPtr LoadLibraryEx(string dllFilePath, IntPtr hFile, uint dwFlags);

        /// <summary>
        /// To get function pointer from loaded dll 
        /// </summary>
        /// <param name="dllPointer">Pointer to Dll witch was returned from LoadLibraryEx</param>
        /// <param name="functionName">Function name with you want to call</param>
        /// <returns>Pointer to function</returns>
        [DllImport("kernel32", CharSet = CharSet.Ansi)]
        public extern static IntPtr GetProcAddress(IntPtr dllPointer, string functionName);
        #endregion

        private delegate IntPtr ApiVersionMethod();
        private delegate IntPtr EntryPointMethod(int msg, IntPtr appId);

        static void Main(string[] args)
        {
            // Pass in a single file or many files -> use loop
            string assemblyFile = @"C:\Program Files\Autodesk\AutoCAD 2013\acblock.arx";

            ReadNativeAssembly(assemblyFile);
        }

        static void ReadNativeAssembly(string assemblyFile)
        {
            try
            {
                // Load a native DLL file
                IntPtr assemblyPointer = GetAssemblyPointer(assemblyFile);

                // Load pointers of given method names
                // Pointer to get AutoCAD version number
                IntPtr apiVersionPointer = GetAssemblyMethodPointer(assemblyPointer, "acrxGetApiVersion");
                // Pointer to get method that assigns AutoCAD command names
                IntPtr entryPointPointer = GetAssemblyMethodPointer(assemblyPointer, "acrxEntryPoint");

                // Get references of methods from found method pointers
                Delegate apiVersionMethod = GetMethodFromPointer(apiVersionPointer, typeof(ApiVersionMethod));
                Delegate entryPointMethod = GetMethodFromPointer(entryPointPointer, typeof(EntryPointMethod));

                try
                {
                    var version = apiVersionMethod.DynamicInvoke();
                    WriteMessage("AutoCAD version number: " + version);

                    // Error comes here when trying to invoke unsafe methods from AutoCAD native assemblies
                    var entryPoint = entryPointMethod.DynamicInvoke(1, IntPtr.Zero);
                }
                catch (TargetInvocationException ex)
                {
                    WriteMessage(ex);
                }
            }
            catch (Exception ex)
            {
                WriteMessage(ex);
            }
        }

        #region Pointer and Delegate helper
        private static IntPtr GetAssemblyPointer(string assemblyFile)
        {
            try
            {
                // Load a native DLL file without loading dependencies
                return LoadLibraryEx(assemblyFile, IntPtr.Zero, 0x00000001);
            }
            catch (Exception ex)
            {
                WriteMessage(ex);
                return IntPtr.Zero;
            }
        }

        private static IntPtr GetAssemblyMethodPointer(IntPtr assemblyPointer, string methodName)
        {
            return GetProcAddress(assemblyPointer, methodName);
        }

        private static Delegate GetMethodFromPointer(IntPtr methodPointer, Type methodType)
        {
            return Marshal.GetDelegateForFunctionPointer(methodPointer, methodType);
        }
        #endregion

        #region Write to console
        private static void WriteMessage(string message)
        {
            Console.WriteLine(message);
        }

        private static void WriteMessage(Exception ex)
        {
            Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
        }

        private static void WriteMessage(TargetInvocationException ex)
        {
            Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
        }
        #endregion
    }
}

 

-Khoa

Distinguished Contributor
alex_b
Posts: 405
Registered: ‎08-15-2003
Message 19 of 24 (373 Views)

Re: Retrieve Command names from ARX file

11-09-2012 12:10 AM in reply to: khoa.ho

Khoa

 

About acrxGetApiVersion(), see my message 13 above: the nunber returned is the Acad major version left-shifted by 16 bits. There is no indication of the minor version, as it doesn't matter for the arx binary compatibility.

 

About acrxEntryPoint(), I'm not sure where we can go from here. Loading all the unsafe Acad methods seems too complicated and does not guarantee success.

Maybe the solution is binary reading the file, looking for the calls to addCommand(), finding their string arguments etc., but I know next to nothing about the innards of PE and how to find various offsets in a binary dll.

 

What do you think?

 

Thanks

 

alex

Moderator
Alexander.Rivilis
Posts: 1,450
Registered: ‎04-09-2008
Message 20 of 24 (368 Views)

Re: Retrieve Command names from ARX file

11-09-2012 12:41 AM in reply to: alex_b

...About acrxEntryPoint(), I'm not sure where we can go from here. Loading all the unsafe Acad methods seems too complicated and does not guarantee success.

Maybe the solution is binary reading the file, looking for the calls to addCommand(), finding their string arguments etc., but I know next to nothing about the innards of PE and how to find various offsets in a binary dll...


As I've wrote above addCommand() is a virtual method. That is why you can not find it in dll-file. The Export table has not method addCommand. You can check it with help of dumpbin.exe utility or with Dependency Walker


Пожалуйста не забывайте про Утвердить в качестве решения! Утвердить в качестве решения и Give Kudos!Баллы
Please remember to Accept Solution! Accept as Solution and Give Kudos!Kudos

Post to the Community

Have questions about Autodesk products? Ask the community.

New Post
Announcements
Do you have 60 seconds to spare? The Autodesk Community Team is revamping our site ranking system and we want your feedback! Please click here to launch the 5 question survey. As always your input is greatly appreciated.