Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « June 2006 | Main | August 2006 »

    July 2006

    July 31, 2006

    Breathing fresh life into LISP applications with a modern GUI

    This recent entry on Jimmy Bergmark's JTB World Blog brought to my attention the fact that ObjectDCL is about to become an Open Source project. Chad Wanless, the father of ObjectDCL, was a very active ADN member for many years, but - according to this post on the ObjectARX discussion group - is now unable to spend time working on ObjectDCL due to a severe medical condition. In case Chad is reading this... Chad - all of us here at ADN wish you a speedy recovery and all the best for your future endeavours.

    Ignoring the background behind the decision to post the technology as Open Source, this is good news for developers with legacy codebases that include user interfaces implemented using DCL (Dialog Control Language).

    I did want to talk a little about what other options are available to developers of LISP applications that make use of DCL today. There are a couple of approaches to calling modules implementing new user interfaces for LISP apps, whether through COM or through .NET. I'll talk about both, but I will say that .NET is the most future-proof choice at this stage.

    Both techniques work on the principle that you redesign your user interface using VB6 or VB.NET/C#, and call through to these functions from LISP. Reality is often more complex - you may have more complex interactions from (for instance) particular buttons in your dialog - but these examples demonstrate what you can do to replace a fairly simple UI where you pass the initial variables into a function and receive the modified variables at the other end, once the user closes the dialog. You can also extend it to handle more complex situations, but there may be much more work needed - perhaps even use of AutoCAD's managed API from within the dialog code.

    COM: Using a VB ActiveX DLL from Visual LISP

    For an in-depth description of this technique, ADN members can find the information here. I apologise to those who are not able to access this content, but I don't want to dillute the issue by copying/pasting the whole article into this blog. Especially as this technique is describing the use of VB6, which is no longer at the forefront of Microsoft's development efforts.

    The approach is to create an ActiveX DLL project in VB6, which is simply a COM module implementing code that can be referenced and called using a ProgID. AutoCAD's COM Automation interface exposes a method called GetInterfaceObject from the Application object that simply calls the equivalent of CreateObject on the ProgID passed in, but within AutoCAD's memory space. Once you've loaded a module using GetInterfaceObject, not only can you then call code displaying fancy VB-generated UIs from LISP, but because the code is in the same memory space as AutoCAD, it executes very quickly - on a par with VBA, ObjectARX or the managed API in terms of the speed with which it can access AutoCAD's object model.

    .NET: Defining LISP-callable functions from a .NET application

    The following technique is really the more future-proof approach, and has become possible since the implementation of "LISP callable wrappers" in AutoCAD 2007's managed API. Essentially it comes down to the ability to declare specific .NET functions as being LISP-callable. If you look back at one of my early posts about creating a .NET application, you'll notice the use of an attribute to declare a command, such as <CommandMethod("MyCommand")>. With AutoCAD 2007 you can simply use <LispFunction("MyLispFunction")> to denote a function that can be called directly from LISP.

    From there it's simply a matter of unpackaging the arguments passed in and packaging up the results (the bit in-between is where you get to have fun, using .NET capabilities to create beautiful user interfaces or to integrate with other systems etc., etc.). Here's some code to show the handling of arguments and packaging of the results:

    <LispFunction("LISPfunction")> _
    Public Function VBfunction(ByVal rbfArgs As ResultBuffer) As ResultBuffer

        'Get the arguments passed in...
        Dim arInputArgs As Array
        Dim realArg1 As Double
        Dim intArg2 As Integer
        Dim strArg3 As String
        arInputArgs = rbfArgs.AsArray
        realArg1 = CType(arInputArgs.GetValue(0), TypedValue).Value
        intArg2 = CType(arInputArgs.GetValue(1), TypedValue).Value
        strArg3 = CType(arInputArgs.GetValue(2), TypedValue).Value

        'Do something interesting here...
        '...
        '...

        'Package the results...
        'Use RTREAL (5001) for doubles
        'Use RTSTR (5003) for strings
        'Use RTSHORT (5005) for integers
        Dim rbfResult As ResultBuffer
        rbfResult = New ResultBuffer( _
            New TypedValue(CInt(5001), 3.14159), _
            New TypedValue(CInt(5003), 42), _
            New TypedValue(CInt(5005), "Goodbye!"))
        Return rbfResult

    End Function

    The code assumes the first argument passed in will be a real, followed by an integer and then finally a string. Here's what happens if you call the code like this:

    Command: (LISPfunction 1.234 9876 "Hello!")
    (3.14159 42 "Goodbye!")

    Here are a couple of useful articles on the ADN site regarding this:

    LispFunction examples for AutoLISP to .NET
    .NET ResultBuffer returns dotted pairs to Visual LISP instead of normal list

    July 28, 2006

    Advanced Visual Studio debugging: how to stop stepping into certain functions

    While debugging it's sometimes very frustrating to find yourself repeatedly stepping into an irrelevant function. For instance, complex statements pretty commonly include object constructors etc. that you know function perfectly well, but the debugger routinely takes you into them.

    The Visual Studio IDE has an undocumented (and unsupported) mechanism to help with this. During the VC 6.0 timeframe it was implemented via our old friend the autoexp.dat file (see my previous post on this), in a special section called [ExecutionControl]. Since VC 7.0 this has been moved to the Registry.

    For VC 7.0 and 7.1, it was in the HKCU (HKEY_CURRENT_USER) section:

    HKCU\Software\Microsoft\VisualStudio\7.0\NativeDE\StepOver
    HKCU\Software\Microsoft\VisualStudio\7.1\NativeDE\StepOver

    For VC 8.0 (Visual Studio 2005), it shifted across to HKLM (HKEY_LOCAL_MACHINE):

    HKLM\SOFTWARE\Microsoft\VisualStudio\8.0\NativeDE\StepOver

    Inside this key you will need to create string values that specify the functions and class methods to exclude (or include) while stepping through code in the debugger. For VC7.0/7.1 you needed to use a numeric identifier for the name, but with VC8.0 you can thankfully use something more meaningful:

    Name                Value
    AcDbOP cons    AcDbObjectPointer\<.*\>\:\:AcDbObjectPointer.*=NoStepInto
    App init              InitApplication=NoStepInto

    [ Note: I would have used something even more meaningful than AcDbOP above, but I wanted the text to all fit on one line for clarity... :-) ]

    The first part of the string value needs to be defined as a regular expression: the text I'm looking for in the first instance is really "AcDbObjectPointer<*>::AcDbObjectPointer*", but I had to use an escape character to prefix some of the symbols. Check MSDN for more information on regular expressions.

    This is also the string as you would enter it via the Registry Editor - if you're using a .reg file then you'll need to double-up the slashes.

    The second part of the string value is likely to be "=NoStepInto" or "=StepInto", depending on whether you are telling the debugger to exclude or include the locations defined by the expression. If you want to get really clever, you can use wildcards to exclude all the functions of a particular class, and then specifically re-include the ones that interest you. This CodeGuru forum post shows some good examples of that. I'd also recommend reading this entry from Andy Pennell's blog.

    Incidentally, you don't have to restart Visual Studio for the changes to be picked up - both autoexp.dat and this section of the Registry are read in as a debug session is started (although the autoexp.dat timestamp needs to be more recent than the end of the last debugging session, interestingly enough... so if you make changes and save while a debug session is running, they won't be picked up in the next session unless a save is done between the sessions <phew!>).

    When you come to execute your code, you should find that it no longer steps into the constructor of any class defined using the AcDbObjectPointer template (such as AcDbObjectPointer<AcDbViewportTableRecord>).

    [ Side note: This template is one of the ObjectARX "smart pointers", and are a great way to handle the opening and closing of persistent ObjectARX objects. I won't go into detail here, but they are well worth checking out in the ObjectARX SDK documentation. ]

    When I say "step into", I literally mean you would need to be stepping through the code using the debug toolbar or F11 - execution still stops in that particular function if you set a break-point there.

    July 26, 2006

    Autodesk Building Systems .NET API Webcast

    A quick note: there's a free webcast tomorrow, July 27th, on Autodesk's Building System's .NET API. It's being delivered by Steve Milligan, a member of the ABS Engineering team. For more information go here, or here to register.

    July 24, 2006

    Advanced Visual Studio debugging: automatic expansion of watched variables

    I've been using Visual C++ (and afterwards Visual Studio) since it was 16-bit, back in version 1.52. OK, maybe that's not so long ago, relatively (11 short years), but the point is that in spite of having followed the Visual Studio technology over this period, I've so far been completely unaware of the autoexp.dat file.

    This feature of the Visual Studio was brought to my attention by Ahsan Ali, a programmer in the Inventor Engineering team who was based over in Bangalore at the same time I was (we had both previously worked in the US - he had come across from Tualatin while I had moved there from San Rafael). During a recent technology discussion, Ahsan shared some information with our Bangalore-based team on some advanced debugging techniques, and I thought it would be a great topic for this blog.

    The autoexp.dat file's default location (for Visual Studio 2005) is:

    C:\Program Files\Microsoft Visual Studio 8\Common7\Packages\Debugger\autoexp.dat

    It's basically a text file that informs the Visual Studio IDE which information is especially relevant in a particular class, so that this information can be displayed automatically (and in the appropriate format) in the watch window, without you having to expand the various datatypes. The clicks you use to get down to data all add up, especially when dealing with a particularly repetitive debugging task, so this feature can really be helpful. MSDN refers to it here.

    The technique seems to be specific for native (presumably C++?) code, but the above link refers to techniques to implement this type of functionality for VB.NET and C#.

    So let's look at an example of the problem - as it relates to ObjectARX - and how this technique helps. Here's the default view of two very common ObjectARX datatypes, AcGePoint3d and AcGeMatrix3d:

    Watch_window_1

    The default view of the pickpnt variable (an AcGePoint3d) is OK, but all those zeroes do make your eyes hurt. The data displayed for xform (an AcGeMatrix3D) tells you nothing whatsoever of any use - it's the address of the 2D array holding the matrix contents. Here's what you get when you expand these two variables:

    Watch_window_1b_1

    You need 5 clicks just to determine that the xform variable is actually the unit matrix, which is way too much.

    So what can we do? The autoexp.dat rules format is very simple and is documented in the header of the file itself. I won't reproduce the whole description of the format here, aside from this brief statement:

    An AutoExpand rule is a line with the name of a type, an equals sign, and text with replaceable parts in angle brackets. The part in angle brackets names a member of the type and an  optional Watch format specifier.

    Thereafter follow some specifics regarding the syntax - for an alternative source of information regarding this, you might also try this CodeGuru article. I'd also recommend looking at the various types listed in the file itself - as usual real-life examples often paint better pictures than abstract descriptions.

    So, let's look at what we can do for AcGePoint3d and AcGeMatrix3d. Here are the two entries I added to the autoexp.dat file:

    AcGePoint3d =x=<x,g> y=<y,g> z=<z,g>

    AcGeMatrix3d =<entry[0][0],g> <entry[0][1],g> <entry[0][2],g> <entry[0][3],g>,<entry[1][0],g> <entry[1][1],g> <entry[1][2],g> <entry[1][3],g>,<entry[2][0],g> <entry[2][1],g> <entry[2][2],g> <entry[2][3],g>,<entry[3][0],g> <entry[3][1],g> <entry[3][2],g> <entry[3][3],g>

    The first is fairly simple: it simply tells Visual Studio to display each of the X, Y and Z values of the point, but using the "g" type specifier. Without going into details, this means that they are floating-point values that should be abbreviated to significant digits only. I chose to leave in the labels, x, y & z, to improve readability.

    The second is clearly longer - once again it uses the "g" type specifier for each of the 16 entries in the 4x4 matrix. Given the volume of information in the matrix class, I decided to leave out the labels, simply listing the contents with each row separated each by a comma.

    Here are the results:

    Watch_window_2a

    When we have something more meaningful contained in the variables, the descriptions will get longer, of course:

    Watch_window_2b

    Watch_window_2c

    A few tips about the entries in autoexp.dat:

    • Don't just add them to the end of the file: they should be part of the [AutoExpand] section, not the [Visualizer] section. I placed mine at line 147.
    • It seems that the string itself is limited to 256 characters to the right of the first equals sign (in this day and age - can you imagine?). I was lucky - the AcGeMatrix3d rule eventually came to exactly 256 characters (which is how I found out about the limitation - I had to squeeze out a few redundant spaces for it to work).
    • I haven't been able to get multiple lines to display - which would be especially useful for the matrix class, of course. If someone can work it out or find the information on the internet, please post a comment!
    • It would also be great to do conditional display of data, especially for union types such as the good old resbuf. While this is not supported directly, you can develop an AddIn to make it work, apparently. See this article for more details.

    July 21, 2006

    Calling the DWFRender Web Service from HTML

    I decided to go ahead and put a test page together for the DWFRender Web Service. It still wasn't feasible to make it part of the blog itself, given the issues with scripting, particularly, but I've included the files here for you to download and run locally:

    Download freewheel_web_service_test.zip

    There are three web methods called by this code:

    • pdkVersion - gets the version of the PDK on the server (a string value)
    • pageCount - gets the number of pages for a particular DWF file (an integer value)
    • load - gets more detailed information about each page in a DWF (a more complex data structure is used for this)

    Web Services are generally called asynchronously from HTML, which means the call is made and the page then sits back until either a callback is invoked or an event is fired (the Web Service Behavior used in the attached sample - and linked to in my previous post - can be made to work with both approaches... I've chosen to use callbacks in my code, as it allows more flexibility when handling different result types: you can have different callback functions invoked depending on the datatype you expect to receive).

    The first two methods will return basic dataypes - here are typical results without the SOAP header:

    <pdkVersionResult>1.4.1976.5fw</pdkVersionResult>

    <pageCountResult>5</pageCountResult>

    Inside the attached code it is simple to access these datatypes by using result.value in the callback receiving the results.

    For the load method, however, things are more complicated. As a complex datatype is used to return the information about the various pages, you will need to use result.raw.xml to access the underlying XML content:

    <
    loadResponse xmlns="http://autodesk.com/DWFRender">
      <
    loadResult>
        <
    pageCount>5</pageCount>
        <
    sectionInfo>
          <
    SectionInformation>
            <
    name>Assembly Exploded:1</name>
            <
    paperSize>
              <
    units>kMillimeters</units>
              <
    width>431.8</width>
              <
    height>279.40000000000003</height>
            </
    paperSize>
          </
    SectionInformation>
          <
    SectionInformation>
            <
    name>Assembly:2</name>
            <
    paperSize>
              <
    units>kMillimeters</units>
              <
    width>431.8</width>
              <
    height>279.40000000000003</height>
            </
    paperSize>
          </
    SectionInformation>
          <
    SectionInformation>
            <
    name>Sony PSP Body:3</name>
            <
    paperSize>
              <
    units>kMillimeters</units>
              <
    width>431.8</width>
              <
    height>279.40000000000003</height>
            </
    paperSize>
          </
    SectionInformation>
          <
    SectionInformation>
            <
    name>UMD COVER:4</name>
            <
    paperSize>
              <
    units>kMillimeters</units>
              <
    width>279.40000000000003</width>
              <
    height>215.9</height>
            </
    paperSize>
          </
    SectionInformation>
          <
    SectionInformation>
            <
    name>MAGIC GATE COVER:5</name>
            <
    paperSize>
              <
    units>kMillimeters</units>
              <
    width>279.40000000000003</width>
              <
    height>215.9</height>
            </
    paperSize>
          </
    SectionInformation>
        </
    sectionInfo>
      </
    loadResult>
    </
    loadResponse>

    July 20, 2006

    Project Freewheel: Losing Control!

    Project Freewheel is a project that was recently launched via Autodesk Labs. It's a technology preview that allows you to share DWF files without the need for installing a client application that includes an ActiveX control for DWF display (such as the DWF Viewer or Design Review).

    There are three main features of Project Freewheel that are of interest to developers. Let's take a look at them below.

    Embedding a navigable DWF

    It's now really simple to embed a DWF file in your web page without forcing a download of the DWF Viewer application. As long as your DWF file is accessible somewhere on the Internet, you can use Project Freewheel's Interactive Software Viewer to allow navigation of your DWF without a component install. Very cool and very easy.

    Once your DWF is in a publicly accessible location, you simply use a frame or inline frame tag in your HTML to embed the results of a URL passing the required parameters (in this case we're just passing the location of the DWF):

    <iframe src="http://dwfit.com/dwf.aspx?dwf=http://dwfit.com/sample/psp.dwf" width="400" height="300" scrolling="no" frameborder="0" />

    Embedding a static image of a DWF

    If you merely want a static image, that's easy too. You can use the Rendering Service to serve up a rendered view of a particular sheet from a DWF file. This time we're going to pass in a few more parameters regarding the size of the image and the view target/scale:

    <iframe src="http://dwfit.com/dwfImage.aspx?cx=0.5&cy=0.5&scale=1&page=3&width=370&height=270& path=http://dwfit.com/sample/psp.dwf" width="400" height="300" scrolling="no" frameborder="0" />

    Getting some data regarding a DWF

    The last item of interest to developers is the DWFRender Web Service that allows you to query non-graphical information about a particular DWF file. If you're new to web services, I recommend this primer (although it's now slightly dated).

    Most interestingly you can use the service to query the number of pages contained in a particular DWF and various information on those pages (name, paper-size, units).

    There are a number of approaches you can use to call SOAP web services from HTML using JavaScript, and the one I've used myself in the past is documented here:

    http://msdn.microsoft.com/workshop/author/webservice/webservice.asp

    It takes a little work to set up - you need to define some local script (typically JavaScript) and make use of an HTC file provided by Microsoft. I would have done it directly from this page, but it's actually quite tricky to do from within a blog - you have to get the HTC file onto the server and try to get the hosted page to call the script properly. Anyway - you can imagine the results: the page would simply have some embedded text representing the number of pages and the page-names, all of which would have been queried from the web service (the calling code would have had to pass in the URL to the DWF, of course).

    For more detailed information on how to use Project Freewheel as a developer, please check this page.

    July 18, 2006

    Linking 2D and 3D with the DWF Viewer 7 API

    I haven't written about the API capabilities of the DWF Viewer, as yet, but this is a technology I really enjoy working with. The latest release (version 7), especially, has some really cool API capabilities: while you've been able to respond to selection events in 2D views for some time, it's now also possible to manipulate 3D views, selecting which objects should be displayed.

    If you throw in the capabilities of AutoCAD's DWF Metadata API (which allows you to add in custom metadata into both 2D and 3D DWF sheets, as they're being published) you really get some interesting possibilities for applications working on "downstream" data.

    A quick aside - if you're thinking of embedding the DWF Viewer in your application, be sure to check out the API documentation.

    So here's what I've been playing around with - primarily intended to be part of the Component Technologies webcast material I mentioned in a post a few weeks ago.

    The idea is that during the publishing of your model from AutoCAD, you add some metadata to objects (in this case I'm using 3D solids) as they get published both into 2D and 3D sheets. The data being published in this sample is simple enough - the handle of the object and the material applied to it. Having this metadata in the DWF will allow us to hook into selection events in the 2D view and create a connection between what's selected in 2D and displayed in 3D.

    Next we create a web app in HTML that embeds the viewer twice - once to display the 2D sheet, which is our "main" view, and a second time to show a small window in the corner, which will show a 3D view of the model (or a subset of the model).

    And here's where it gets cool: you can define JavaScript functions that respond to events in the 2D view, and dynamically manipulate the 3D view as you hover around in the 2D view. Whichever object you're hovering over in the 2D view gets isolated and displayed in 3D. You can move across and orbit on the 3D view before moving back to hover around the model, looking at different objects.

    Here's a quick screenshot of the app as it stands right now:

    Materials_dashboard





    The code is certainly still a "work-in-progress", there are still some quirks I'm still trying to iron out (it even occasionally freezes my machine - although I haven't yet determined whether that is due to this app or something else). All this to say - please save anything you're working on before launching the app, if indeed you feel you must take a look... :-)

    Download dwf_materials_demo.zip

    July 14, 2006

    Migrating ObjectARX applications to support Unicode - some resources

    The work we did to migrate AutoCAD 2007 to use Unicode (rather than MBCS), has impacted many developers around the world. For those that are yet to go through the pain themselves, I thought I'd talk about the resources that are available to ObjectARX developers needing to port their applications to Unicode.

    Firstly, you should check out the Migration Guide that ships with the ObjectARX 2007 SDK (docs/acad_xmg.chm):

    Unicode_migration_docs







    There's a whole section called "Upgrading to Unicode", with lots of useful information. A good deal of the material in the guide was compiled during the development phase of AutoCAD 2007, as our own engineering teams got to grips with porting AutoCAD to support Unicode.

    Here's a quick outline listing of the topics, to give you a feel for the contents:

    • Upgrading to Unicode
      • Why Convert AutoCAD to Unicode?
      • Terminology and Basic Concepts
        • Characters and Glyphs
        • MBCS, Multi-byte, DBCS, ANSI, ASCII, and Code Pages
        • Unicode and its Formats (UTF-8, UTF-16, and UTF-32)
        • Compiler Types: char, wchar_t
        • Autodesk Type: ACHAR
        • Affected AutoCAD-based Products
        • Effect of Unicode Conversion on AutoCAD File Types
      • Creating Unicode-compatible Source Code
        • Unicode Porting Tool: Visual Teefy
        • Text File Utilities Provided in the ObjectARX SDK
        • Outline of Autodesk Porting Process
        • Updating Large ObjectARX and ObjectDBX Applications
        • Project Definitions for Unicode Compilation
          • Command Line Processing Tip
        • Basic Coding Tasks
          • Change char to ACHAR or TCHAR for ObjectARX APIs
          • Guidelines for Using TCHAR and ACHAR
          • Wrap Literal Strings and Characters with _T() or ACRX_T()
          • Replace ANSI String Pointer Types
          • Update String Formatting Functions
          • Check String Allocations for TCHAR Compatibility
          • Check Usage of Win32 APIs With No Unicode Equivalents
          • Check Usage of Lead Byte APIs
          • Use _TUCHAR in Unicode Character Classification Functions
          • Use #ifdef to Call the Correct Unicode Function
          • Replace strlen() With _tcslen, Rather Than _tcsclen, in Dual Build Code
          • Use Unicode Code Page Descriptors in Win32 APIs
          • Use Native wchar_t
          • AcArray of std::wstring Type Requires Special Allocator Argument
          • DCL Dialogs Use Unicode, but Definition Files are ANSI
        • Secondary Coding Tasks
          • Centralize Recurring Literal Strings
          • Replace Char Buffers with String Classes
          • Revisit Low-Level Win32 API Calls
          • Avoid Calling CRichEditCtrl::GetSelText()
          • Evaluate Usage of wctombs() And mbstowcs()
        • Prepare User Interface for Unicode Compatibility
          • Fine-Tuning System Font Usage for Special Cases
        • Prepare File I/O for Unicode Compatibility
        • Tips on Supporting Supplementary Plane (UTF-32) Characters
        • Linker Errors Caused By Conflicting Definitions of wchar_t
      • Frequently Asked Questions
      • Resources

    Additionally I'd recommend using a tool called Visual Teefy, which is distributed via the ADN website. The name comes from the fact it helps add the T() macro into string literals (among other things), hence "T()-ify" => Teefy. The tool hooks off the Visual Studio IDE's search & replace mechanism to find potentially problematic pieces of code, and provide suggestions on how to address them. It's not recommended to use the automatic settings (Teefy really just makes suggestions), as clearly a  search & replace-based tool is inevitably going to have trouble - as an example - differentiating a string literal in a compiler directive (such as #include "acdb.h" - which does not need any modification) from a string literal that does require the use of the T() macro.

    As for other resources, I'd recommend searching the ADN KB, if you have access to it, or submitting your questions via the ObjectARX discussion group or DevHelp Online: at this stage it's almost certain that someone in our development community has experienced the same migration issues you're hitting, and it can certainly save time to ask.

    July 11, 2006

    Free API webcasts

    The DevTech team spends a substantial amount of time delivering API training, and not just to ADN members. We schedule class-room trainings at various Autodesk facilities, deliver training on-site at developers' offices, and deliver training webcasts via Microsoft LiveMeeting.

    Mikako Harada, the team lead for DevTech East Asia and the technical lead for our BSD workgroup, will deliver a webcast on Architectural Desktop's .NET API on July 13th. This webcast is absolutely free to everyone.

    For more information on our API training - and to register for the webcast - please check Autodesk's API training page at http://www.autodesk.com/apitraining. For your convenience, here's a direct link to the API training schedule.

    By the way - we do post recordings of our API webcasts to the ADN website, although you do need to be a member of ADN to access them.

    July 10, 2006

    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.

    Feed & Share

    Search