Calling ObjectARX functions from a .NET application

One of the really compelling features of .NET is its ability to call "legacy" unmanaged C++ APIs. I say "legacy", but we use this facility regularly to call APIs that are far from being considered defunct (the C++ version of ObjectARX is alive and kicking, believe me! :-).

Autodesk understands that our development partners have invested many years in application development, and can't afford to throw that investment away to support the latest & greatest (and sometimes "flavor of the month") programming technology. For example, over the years we've made sure it was possible to create a VB or VBA user-interface for an existing LISP application or now a .NET user-interface for an ObjectARX application. Sometimes we expose our own interoperability functions to help with this (such as LISP functions to call ActiveX DLLs), and in other cases we advise people on how best to leverage standard Microsoft platform technologies.

So... how do you call an ObjectARX function from VB.NET? The answer is Platform Invoke (or P/Invoke for short). Microsoft has not exposed the full functionality of the Win32 API through the .NET Framework - just as Autodesk has not exposed all of ObjectARX through AutoCAD's Managed API - but P/Invoke helps you get around this.

First, some background on what ObjectARX really is, and how P/Invoke can help us.

ObjectARX is a set of APIs that are exported from DLLs or EXEs. Most exported functions get "decorated" or "mangled" during compilation, unless there is a specific compiler directive not to (this is the case for all the old ADS functions, for instance - they are declared as extern "C" and are therefore not mangled). The compiler assigns a unique name based on the function signature, which makes sense: it is quite legal in C++ to have two functions with the same name, but not with identical arguments and return values. The decorated name includes the full function name inside it, which is why the below technique for finding the correct export works.

[ Note: this technique works well for C-style functions, or C++ static functions. It will not work on instance members (methods of classes), as it is not possible to instantiate an unmanaged object of the class that defines the class from managed code. If you need to expose a class method to managed code, you will need to write & expose some native C++ code that instantiates the class, calls the method and returns the result. ]

To demonstrate the procedure we're going to work through the steps needed to call acedGetUserFavoritesDir() from C# and VB.NET. This function is declared in the ObjectARX headers as:

extern Adesk::Boolean acedGetUserFavoritesDir( ACHAR* szFavoritesDir );

According to the ObjectARX Reference, "this function provides access to the Windows Favorites directory of the current user."

Step 1 - Identify the location of the export.

Fenton Webb, from DevTech EMEA, provided this handy batch file he uses for just this purpose:

[ Copy and paste this into a file named "findapi.bat", which you then place this into your AutoCAD application folder. You will need to run findapi from a command prompt which knows where to find dumpbin.exe - the Visual Studio Command Prompts created on installing VS will help you with this. ]

@echo off
if "%1" == "" goto usage
:normal
for %%i IN (*.exe *.dll *.arx *.dbx *.ocx *.ddf) DO dumpbin /exports %%i | findstr "%%i %1"
goto end
:usage
echo findapi "function name"
:end

You can redirect the output into a text file, of course, for example:

C:\Program Files\AutoCAD 2007>findapi acedGetUserFavoritesDir > results.txt

It'll take some time to work, as this batch file chunks through all the DLLs, EXEs, etc. in the AutoCAD application folder to find the results (it doesn't stop when it finds one, either - this enhancement is left as an exercise for the reader ;-).

Opening the text file will allow you to see where the acedGetUserFavoritesDir() function is exported:

[ from the results for AutoCAD 2007 ]

Dump of file acad.exe
        436  1B0 004B4DC0 ?acedGetUserFavoritesDir@@YAHPA_W@Z

A word of warning: the decorated names for functions accepting/returning strings changed between AutoCAD 2006 and 2007, because we are now using Unicode for string definition. Here is the previous declaration for 2004/2005/2006 (which was probably valid for as long as the function was defined, back in AutoCAD 2000i, if I recall correctly):

[ from the results for AutoCAD 2006 ]

Dump of file acad.exe
        357  161 00335140 ?acedGetUserFavoritesDir@@YAHPAD@Z

This is simply because the function signature has changed from taking a char* to an ACHAR* (a datatype which now resolves to a "wide" or Unicode string in AutoCAD 2007). A change in the function signature results in a change in the decorated name. This is straightforward enough, but it is worth bearing in mind the potential migration issue - a heavy dependency on decorated function names can lead to substantial migration effort if widespread signature changes are made in a release (as with AutoCAD 2007's support of Unicode).

Another warning: you will find a number of other functions exported from the various DLLs/EXEs that do not have corresponding declarations in the ObjectARX headers. These functions - while exposed - are not supported. Which means that you may be able to work out how they can be called, but use them at your own risk (which can be substantial). Unsupported APIs are liable to change (or even disappear) without notice.

Now we've identified where and how the function is exposed, we can create a declaration of this function we can use in our code.

Step 2 - Declare the function correctly in your code.

This is going to be slightly different depending on the programming language you're using.

VB developers will be used to using "Declare" to set-up P/Invoke from their projects. This ends up being translated by the compiler into calls to DllImport, which is also used directly in C#.

These declarations should be made at the class level (not within an individual function definition).

VB.NET

Private Declare Auto Function acedGetUserFavoritesDir Lib "acad.exe" Alias "?acedGetUserFavoritesDir@@YAHPA_W@Z" (<MarshalAs(UnmanagedType.LPWStr)> ByVal sDir As StringBuilder) As Boolean

C#

[DllImport("acad.exe", EntryPoint = "?acedGetUserFavoritesDir@@YAHPA_W@Z", CharSet = CharSet.Auto)]
public static extern bool acedGetUserFavoritesDir([MarshalAs(UnmanagedType.LPWStr)] StringBuilder sDir);

Notes:

  1. It's worth specifying the character set as "Auto" - which is not the default setting. The compiler does a good job of working out whether to use Unicode or ANSI, so it's easiest to trust it to take care of this.
  2. You will need to use the MarshalAs(UnmanagedType.LPWStr) declaration for Unicode string variables in 2007. This is true whether using Strings or StringBuilders.
  3. Use a StringBuilder for an output string parameter, as standard Strings are considered immutable. Strings are fine for input parameters.

Step 3 - Use the function in your code

[ I've omited the standard using/import statements, as well as the class & function declarations, to improve readability. ]

VB.NET

Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor
Dim sDir As New StringBuilder(256)
Dim bRet As Boolean = acedGetUserFavoritesDir(sDir)
If bRet And sDir.Length > 0 Then
        ed.WriteMessage("Your favorites folder is: " + sDir.ToString)
End If

C#

Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
StringBuilder sDir = new StringBuilder(256);
bool bRet = acedGetUserFavoritesDir(sDir);
if (bRet && sDir.Length > 0)
        ed.WriteMessage("Your favorites folder is: " + sDir.ToString());

Note: we declare the StringBuilder variable (sDir) as being 256 characters long. AutoCAD expects us to provide a sufficiently long buffer for the data to be copied into it.

On my system both code snippets resulted in the following being sent to AutoCAD's command-line:

Your favorites folder is: C:\My Documents\Favorites

So that's it: you should now be able to call global ObjectARX functions from .NET. This technique can also be used to call your own functions exposed from DLLs... which is one way to allow you to create fancy UIs with .NET and leverage existing C++ code (there are others, such as exposing your own Managed API).

For additional information on using P/Invoke, particularly with Win32, here is a really great resource.

31 responses to “Calling ObjectARX functions from a .NET application”

  1. Fernando Malard Avatar
    Fernando Malard

    Hi Kean,
    Great article.
    Keep going!

  2. J. Daniel Smith Avatar
    J. Daniel Smith

    FxCop warns you about making P/Invoke functions "public", they should be "internal" instead--exposing a managed wrapper to other assemblies.

    I think there is a way to call instance methods on C++ objects through P/Invoke using __thiscall; it's not pretty though.

    Your article does a great job of explaining the technique, but I would suggest that for production work you might be better off exposing a managed wrapper with C++/CLI rather than making extensive use of P/Invoke with mangled names.

  3. Thanks for your comment - I hadn't checked this code with FxCop, so that's very good to know.

    And yes - clearly the best ways to get more of ObjectARX exposed through to .NET are to request Autodesk to expose it through our managed wrapper, followed by exposing it through your own. The technique described in this post can be useful, but comes with its own issues & risks.

  4. Hi Kean,
    The Hatch class of the current AutoCAD version does not provide the OriginPoint property.How to get/set the OriginPoint of the Hatch object using P/Invoke?
    Thanks in advance!

  5. Hi csharpbird,

    You can't use P/Invoke on instance members (see the note above on this). But the good news is that the Hatch Origin is exposed via COM, so you can use code such as this:

    using Autodesk.AutoCAD.Interop;
    using System.Runtime.InteropServices;

    // Assume you have the managed object in "obj"

    Hatch hat = obj as Hatch;
    if (hat != null)
    {
    Autodesk.AutoCAD.Interop.AcadApplication oAcad;
    oAcad =
    (AcadApplication)Marshal.GetActiveObject("AutoCAD.Application.17");

    Autodesk.AutoCAD.Interop.Common.AcadHatch oHat;
    object obj2 =
    oAcad.ActiveDocument.ObjectIdToObject(obj.ObjectId.OldId);

    // Method 1
    oHat = obj2 as Autodesk.AutoCAD.Interop.Common.AcadHatch;
    if (oHat != null)
    {
    double[] orig = (double[])oHat.Origin;
    ed.WriteMessage(
    "\nHere's the hatch origin: X: " +
    orig[0] + ", Y: " + orig[1]
    );
    }
    Marshal.ReleaseComObject(obj2);
    }

    This seems like a good topic to turn into a post (thanks!).

    Kean

  6. Tony Tanzillo Avatar

    "[ Note: this technique works well for C-style functions, or C++ static functions. It will not work on instance members (methods of classes), as it is not possible to instantiate an unmanaged object of the class that defines the class from managed code...."

    Actually, you can invoke non-static member functions of native objects that are exposed as managed wrappers through P/Invoke with the ThisCall calling convention.

    That works because the UnmanagedObject property of all wrapped objects (deriving from DisposableWrapper), is a pointer to the instance of the unmanged object that you need to invoke the non-static member through.

  7. How to write a Double value into a Text Document

  8. Sorry, Anonymous - that seems a little out of context. Are you developing with AutoCAD?

    Kean

  9. I am searching for a possibility to xref an extern dwg and to bind this xref. The managed API seems not to support it. The C++-API use the commands acedXrefAttach and acedXrefBind.
    I tried the folllowing for acedXrefAttach and get an unhandled exception:

    [DllImport("acad.exe", EntryPoint = "?acedXrefAttach@@YA?AW4ErrorStatus@Acad@@PB_W0PAVAcDbObjectId@@1PBVAcGePoint3d@@PBVAcGeScale3d@@PBN_NPAVAcDbDatabase@@0@Z", CharSet = CharSet.Auto)]
    public static extern int acedXrefAttach([MarshalAs(UnmanagedType.LPWStr)]string XrefPathname, [MarshalAs(UnmanagedType.LPWStr)]string XrefBlockname);

    I called the functions with:

    int res=acedXrefAttach(@"s:\test.dwg","test");

    The C++-header-definition is:
    Acad::ErrorStatus
    acedXrefAttach(const ACHAR* XrefPathname,
    const ACHAR* XrefBlockname,
    AcDbObjectId* pXrefBTRid = NULL,
    AcDbObjectId* pXrefRefid = NULL,
    const AcGePoint3d* pXrefInsertPt = NULL,
    const AcGeScale3d* pXrefScale = NULL,
    const double* pXrefRotateAngle = NULL,
    const bool bQuiet = true,
    AcDbDatabase* pHostDb = NULL,
    const wchar_t* wszPassword = NULL);

    If I use insufficient arguments, what are the managed types of AcDbObjectId* etc. ?

    When I use the SendStringToExecute or acedPostCommand (over DLLimport) for the xref-command, it does'nt work with scripts.

    Thank you for your help.
    /Bernhard

  10. Hi Bernhard,

    Database.AttachXref() should help, although internally it calls into acdbAttachXref() rather than acedXrefAttach(). It doesn't work in exactly the same way: you may need to manually add the BlockReference after calling the function.

    Regards,

    Kean

  11. And failing that, you could always call ModelSpace.AttachExternalReference() through COM.

    Regards,

    Kean

  12. Hi Kean,

    using Database.AttachXref() works fine. Also with scripting.
    It is also very fast. Over 100,000 inserts (BlockReferences) per second.
    So the use of a bind-function for converting the xref to a block
    (I could not find in the managed API) is not so important.

    Thank you!

    /Bernhard

  13. That's very fine! And if I would call the .net command from some arx files ?
    some kind of this. : "MyCommandArx" calls "mycmdNet"
    I've tried to perform this, with "acedinvoke" but doesn't run..

  14. Giuliano -

    This post should help.

    Kean

  15. Kean,

    I simply want to toggle between the text screen/window and the graphics screen/window. What is a good method to use? I thought of acedTextScr and acedGraphScr calls through p/invoke (as suggested by ObjectARX docs), but the FindAPI.bat above does not locate these functions.

  16. Mark,

    I just checked the exports from AutoCAD 2010's acad.exe via dumpbin, and could see acedTextScr():

    2768 AB9 006FAA97 acedTextScr

    It should be easy enough to P/Invoke this, I would think (although I've never actually done so, myself).

    Kean

  17. Kean,

    I solved my problem and I forgot to mention I am using AutoCAD MEP 2009. I typed the batch file above, instead of copy/paste, and had "%i" as the last argument in the for loop rather than "%1". My mistake. No wonder I was lost in the "sea of output"! Yes, these methods are very easy to implement, as they are not decorated. Here are my declarations:
    Private Declare Auto Function acedGraphScr Lib "acad.exe" Alias "acedGraphScr" () As Integer
    Private Declare Auto Function acedTextScr Lib "acad.exe" Alias "acedTextScr" () As Integer
    Thanks for all the information you provide!

  18. Kean,

    No seu caso você conhece o nome da função. E quando conhecemos apenas o "command line", como descobrir o nome da função e onde ela se encontra, ou até mesmo se ela é gerenciada.
    Grato.

  19. Kelcyo,

    I know the names of the functions I want to call from looking at the ObjectARX reference material (which you can download with the ObjectARX SDK).

    I would suggest doing the same.

    Regards,

    Kean

  20. Kean,

    Entendo sua orientação. Porem o ObjectARX SDK não abrange outros produtos verticais AutoCAD como o Civil 3D. Porém existe a função no produto mais não esta documentada nas APIs NET e COM. Por isso a intenção de usar P/Invoke. Sei que Civil 3D não é sua área, mais ajudaria indicando um caminho.
    Obrigado.

  21. The resources on our our various Developer Centers are the best places to start, e.g. autodesk.com/developcivil

    Kean

  22. can you code in c++ ,in objectarx? i think it probably more faster than use in .net

  23. And , in my project i will use it ,so i hope to get your help.

  24. Native C++ is indeed faster for lots of operations in AutoCAD than .NET (although not always measurably). That said, I prefer to code in .NET as it makes my posts much more accessible, and is much quicker for me to do.

    Kean

  25. Hi Kean,
    I'm looking for some information about ObjectARX and .NET, so I hope you can give me a little help.
    I need to develop a batch application (c#) that is able to open a DWG file and add some object (no UI required), can this be done with ObjectARX ? Is necessary to have Autocad installed in the same machine to do this ?

    Thank you very much for your answer.

  26. Hi Franco,

    If you have AutoCAD 2013 installed the best would be to use the Core Console (it works great with ScriptPro 2.0, in case).

    If using prior versions of AutoCAD, then you can run code as a batch process, but from within an AutoCAD session (it won't need to open the DWGs in the editor, but it will need AutoCAD running as far as I recall).

    If you don't want to rely on AutoCAD being installed on the target system then you should look at RealDWG, which gives you subsets of the .NET and ObjectARX APIs to work with in a standalone app.

    Regards,

    Kean

  27. Hi Kean,
    I'm reading your answer right now and I found it very exhaustive and useful.
    Thank you very much !!!

    Franco

  28. hi Kean.............
    I need your help on transformation between NAD27 and NAD83 using NTV2.gsb in .net . Description I am developing an application,
    i have coordinate pair in NAD27 to converted in NAD83 through .net c# program.I have reference

    <ul>" rel="nofollow noopener" target="_blank" title="http://wikihelp.autodesk.com/AutoCAD_Map_3D/enu/2013/Help/0001-ObjectAR0/0029-Converti29/0030-Converti30">http://wikihelp.autodesk.co...

    how to convert in objectarx ,If you can help me please comment..

  29. Hi Ashis,

    Sorry - I have no idea about coordinate transformations. I suggest posting to the Map 3D Developer discussion forum.

    Regards,

    Kean

  30. Hi Kean,
    Thanks for your reply ,
    OK I got that i can use objectarx function ,
    but while calling ade_projptforward (objectarx function) I got an error of access violation at 0x0000 , Do you have any idea about this.if you need code snippet i will give you ,I am waiting for your reply.....

  31. Hi Ashis,

    OK, I should have been more clear. Not only do I not know about this topic (I don't work with Map 3D at all), but I don't use this blog to provide support.

    If you need support, please contact ADN or post to the relevant discussion group.

    Thank you,

    Kean

Leave a Reply to Fernando Malard Cancel reply

Your email address will not be published. Required fields are marked *