Turning off InfoCenter in AutoCAD 2014

The AcadInfoCenterOff.msi file on the ManuSoft Freebies page has been updated to support AutoCAD 2014 based products. Note that you must log out of Autodesk 360 if you disable the InfoCenter, else some versions of AutoCAD will crash during startup.

C++ function declaration tips

Creating and calling functions is one of the most fundamental tasks in C++ programming. The function declaration serves as the primary description of a function’s interface – or contract – with callers, thereby making it the most important piece of code documentation. A good function declaration should convey as much information as possible. I’m sure an entire book could be written on the topic of function interface design, but I’ll just touch briefly on a few factors to consider when designing a function’s interface.

Error handling

Simple one-purpose functions should leave all or most error handling to the caller. This is more efficient because it eliminates error checking overhead on “known good” input values. A common pattern is a low level function that performs no input validation along with a high level function that does (and then calls the low level function to do the work). Such a design pattern gives the caller maximum flexibility.

If the function must return error (or other status) values, it should do so via the return value whenever possible, and it should define an enum or type alias (typedef) so that the return value type name makes it clear that the value is a status. Note that using the return value as a status may necessitate extra arguments passed by reference so the function has a way to return a result to the caller without using the return value.

Signed vs. unsigned

Consider this function:
int CountWords(char* pszInputString);

The int type is a signed value, but a count is always zero or greater. The return value should at least be unsigned int, or better yet size_t:
size_t CountWords(char* pszInputString);

Likewise, arguments that must never be negative should always be declared as an unsigned type.

By value or by reference

If the caller needs to pass arguments that the function will modify, they must be passed by reference. If the argument will not be modified, then it should be passed by value if it is a trivial type, else by const reference (except in unusual cases). For example:
bool CountWords(const CountOptions& options, char* pszInputString, size_t& ctWords);

Const or not

Incoming arguments that will not be modified should be declared const. This conveys a clear guarantee that the function will not try to modify the incoming value. Proper const delarations also give the optimizing compiler more ways to optimize the code.

Since counting words does not require the input string to be modifed, the CountWords() function’s string argument should really be const char* const (constant pointer to constant char) instead of char*:
size_t CountWords(const char* const pszInputString);

Now the caller can pass a string literal or any other string (even an MFC CString), and be assured that the CountWords function won’t change it.

Source code annotation language

Source code annotation language (SAL) is a fairly recent Microsoft specific development. It consists of predefined macros that help more fully express the constraints and purposes of function arguments without changing the compiled output. SAL helps convey additional information about the function’s contract with the caller, and it also helps the compiler detect potential problems at compile time by performing additional static code analysis.

As you can see below, source code annotation can get a bit messy. I rarely use source code annotation in my own code for this reason and also because it’s not portable to older versions of Visual Studio without extra work. Source code annotation is most appropriate in an API that will be consumed by others, and even then it should be considered carefully in light of its readability and portability trade-offs.

Exception specification

In C++, the exception specification conveys information about what kinds of exceptions a function may throw, either directly or indirectly via functions it calls. If a function cannot throw an exception, you should include the empty throw() suffix on the function declaration.

Putting it all together

Suppose we create a higher level function that validates the input, then calls a lower level function to count words. We might end up with something like this:

size_t CountWords(_In_z_ const char* const pszInputString) throw();

enum Status { eOK, eNull, eError, };

_Check_return_ Status ValidateAndCountWords(_In_z_ const char* const pszInputString, _Out_ size_t& ctWords) throw();

Using /delayload to specify dependent DLL path

Let’s say you have an ObjectARX module with an implicit dependency on another DLL. Your installer puts both your ARX module and the dependent DLL into a private application folder. Windows must be able to resolve the implicit DLL dependency at load time, otherwise it will fail to load your ARX module. Alas, Windows will not automatically search for the dependent DLL in your application folder, even though your ARX module is located there. Therefore your ARX module won’t load because the dependent DLL cannot be resolved.

To address this problem, you may be tempted to add your application folder to the AutoCAD support path so that Windows can find your dependent DLL. This is a Very Bad Idea – please don’t ever do it! It imposes a completely unnecessary burden on end users, and it’s not scaleable because it could cause the maximum support path length to be exceeded. A better solution is to change the implicit dependency into an explicit dependency by using the linker’s delayload feature.

Making the change is easy. First, change your ARX project’s linker settings to delayload the dependent DLL:

delayload

Next, implement a delayload hook in one of your source files to explicitly load the dependent DLL from the same folder as your ARX module:

#include <delayimp.h>

#pragma comment(lib, "delayimp")

HMODULE MyExplicitLoadLibrary( LPCSTR pszModuleName )
{
  if( lstrcmpiA( pszModuleName, "MyDependent.dll" ) == 0 )
  {
    CHAR szPath[MAX_PATH] = "";
    //_hdllInstance is the HMODULE of *this* module
    DWORD cchPath = GetModuleFileNameA( _hdllInstance, szPath, MAX_PATH );
    while( cchPath > 0 )
    {
      switch( szPath[cchPath - 1] )
      {
        case '\\':
        case '/':
        case ':':
          break;
        default:
          --cchPath;
          continue;
      }
      break; //stop searching; found path separator
    }
    lstrcpynA( szPath + cchPath, pszModuleName, MAX_PATH - cchPath );
    return LoadLibraryA( szPath ); //call with full path to dependent DLL
  }
  return NULL;
}

FARPROC WINAPI MyDliNotifyHook( unsigned dliNotify, PDelayLoadInfo pdli )
{
  if( dliNotify == dliNotePreLoadLibrary )
    return (FARPROC)MyExplicitLoadLibrary( pdli->szDll );
  return NULL;
}
extern "C" PfnDliHook __pfnDliNotifyHook2 = MyDliNotifyHook;

When you use this technique, you do have to ensure that the delayload hook is added before any function in the dependent DLL is called. This is not a problem in most cases, but it could be a consideration if your ARX module initializes global objects whose constructors must call functions in the dependent DLL. This is just one more reason why you should avoid global variables.

QuirkyPolyline: exposing foolish programmers

In AutoCAD, the AcDbCurve class defines a general parametrized curve. On a parametrized curve, points in space are mapped to real numbers (parameters) by an arbitrary function F(p) for values of p from the curve start parameter to the curve end parameter. Defining a curve this way simplifies working with complex 3D curves because one can work in the curve’s one dimensional “parameter space” instead of the more complex three dimensional cartesian space.

The choice for how to parametrize a curve entity is up to the implementor. Parameters are designed to be opaque, so consumers of parametrized curves must make no assumptions about how a specific curve implements them. The only thing guaranteed about the parameters of a curve is that all points on the curve map to a unique parameter in a continuous range from the starting parameter to the ending parameter.

AutoCAD entities such as lines, circles, arcs, polylines, rays, and others are derived from AcDbCurve. These basic curve entities have very simple and straighforward parameter mappings that can be reverse engineered with very little effort, so lazy programmers sometimes make assumptions that are not guaranteed to always be true. Even smart programmers often get tripped up on parametrized curves because they use parameter space when they should be using distance space (that is, distance along the curve). The AcDbCurve interface provides functions to translate from parameter space back and forth to distance space, as well as back and forth to three dimensional cartesian space.

Wise programmers always let the curve itself perform all translations to or from distance space or cartesian space so that their code never relies on the actual value of a parameter. Foolish programmers take shortcuts by making assumptions about parameter values. For example, it happens that in all past versions of AutoCAD, the parameter of the midpoint of a polyline segment is exactly halfway between the segment starting parameter and the segment ending parameter, and the segment starting and ending parameters are always equal to the index number of the corresponding vertex. A foolish programmer (e.g. here and here) may use this knowledge to calculate the midpoint of the segment by guessing it’s parameter. The foolish programmer’s code will fail when a case arises where that guess is wrong. It is the purpose of QuirkyPolyline to provide such a case.

QuirkyPolyline defines a QPOLY command that prompts for a selection set of lightweight polylines in AutoCAD, then converts the selected polylines to “quirky polylines” that expose a completely different parameter scheme. Quirky polylines look and act perfectly normal in all respects. If you save quirky polylines, they will be stripped of their quirkiness. There are no long term effects of making polylines quirky; it’s just a temporary change that wears off on its own.

Making quirky polylines is useful for testing code that works with parametrized curves. If your code makes assumptions that it shouldn’t, quirky polylines will expose the error of your ways. Over the years, I have had arguments with people who insisted that their assumption had been validated by extensive testing. My counterargument is that someone, somewhere (possibly even Autodesk itself) already has or eventually will generate a derived entity where the assumption is not true. In addition to providing a proving ground, QuirkyPolyline is designed to demonstrate to the naysayers that unusual parametrization implementations can exist in the wild.

The download contains ARX modules for AutoCAD 2000 through AutoCAD 2015. To use QuirkyPolyline, just load the module that matches your version and architecture of AutoCAD. Create a lightweight polyline with the PLINE command, then use the QPOLY command to convert the normal polyline to a quirky polyline. You can use the LIST command to verify that it did become quirky, but you should notice no other detectable changes.

QuirkyPolyline.zip [132 kb]

Here is a useful little lisp function that defines a PP command for displaying the parameter value of a picked point on a selected curve entity. You can use this to explore other curve entities, and to see how the polyline’s parametrization changes after it becomes quirky.


(vl-load-com)
(defun C:PP (/ sel pointoncurve param)
(if (setq sel (entsel))
(progn
(setq pointoncurve (apply 'vlax-curve-getclosestpointto sel))
(setq param (vlax-curve-getparamatpoint (car sel) pointoncurve))
(princ (strcat "\nParam = " (rtos param)))
)
)
(princ)
)

I hope this helps you avoid the pitfalls of parametric curve code. Remember, don’t be a foolish programmer!

RoboCache for Windows offline files cache management

I’ve written before about RoboCache, a command line utility I wrote for managing the offline files cache in Windows Vista, Windows 7, and now Windows 8. After using the utility internally for a few years, I decided that I might as well clean it up and make it available to the rest of you. RoboCache is now available at the ManuSoft web site. There is a shareware version available, and you can purchase the registered version at the ManuSoft store (cost is 25 USD for a single user license).

It’s nothing fancy, but it works great for my needs. The command syntax is modeled after the ROBOCOPY command, and is designed for handling an entire directory tree recursively, filtering files and folders by wildcard. My typical use case is running it from a batch file to pin remote Visual Studio project files on my laptop before travelling. Visual Studio project folders contain a lot of temporary build files and output files that don’t need to be (and therefore shouldn’t be) included in the cache. The goal is to pin only the necessary files, ignoring the ones that are not needed or will be recreated when the project is built. Here’s a sample call to recursively pin all files in the ‘Build’ folder (this is all one line in a batch file executed from the laptop):

ROBOCACHE Build /op:pin /s /xd debug* release* x64 Win32 .svn _* obj bin ipch /xf *.log *.tlog *._ls *.ncb *.user *.suo *.aps *.ilk *.pch BuildLog.htm *.err *.dmp *.pdb !*.bat #*.bat *.chm *.dia *.aps *.lnk *.Res.dll *.zip *log.txt *report.txt *.winmerge *.sdf *.opensdf

Programmers might notice that the /xd (ignore directories) and /xf (ignore files) wildcard lists look very similar to what one might encounter in a Subversion commit script. In fact, when I created the batch file I just copied and pasted from my SVN ignore lists. For me, the benefit of using RoboCache in this way is that only the minimum needed files are cached. After working on the cached files while I’m out of the office, my changes automatically sync to the folders on my desktop when I return to the office. I do use Subversion repositories for all my projects, and could just as well commit my remote changes to the repository, then update my desktop from the repository; however by using the offline files cache, the syncing all happens automatically and I never have to think about it.

RoboCache can do more than just pin and unpin files. It can perform any of ten operations on the target files (or any of four administrative functions on the files cache itself), including some operations that are not exposed through the Windows user interface. Below is a list of all currently supported operations (captured from the help screen, and not showing the available command line options for each operation).

/OP:cmd :: OPeration to perform (default is /OP:info).
:: info : display status info about the target(s)
:: pin : assure offline availability
:: unpin : unpin the target(s)
:: sync : synchronize cached files with remote files
:: rename : rename cached item (requires reboot)
:: delete : delete cached item
:: suspend : suspend the target folders (ignores files)
:: unsuspend : unsuspend the target folders (ignores files)
:: online : transition to online state
:: offline : transition to offline state
:: enable : enable offline files cache (ignores target)
:: disable : disable offline files cache (ignores target)
:: encrypt : encrypts offline files cache (ignores target)
:: decrypt : decrypts offline files cache (ignores target)

The installer simply adds RoboCache.exe to your system folder; no folders are created and no other changes are made. The shareware version of RoboCache displays a nag notification balloon in the system tray, but is otherwise completely functional. There is no separate documentation, however ROBOCACHE /? displays syntax and command options.