Lately, I’ve been writing a fair bit of C++ code for InDesign C++ plug-ins, and I’ve started using some simple constructs that use the C preprocessor to make my code easier to follow.
This post focuses specifically on macro usage in the InDesign C++ SDK environment.
Pre-pre-amble: Plugin vs. Plug-In
Skip to the next section if you’re an experienced InDesign C++ developer.
If you’re new to in InDesign C++ plug-in development, you have to know there is a steep learning curve ahead of you.
First of all, make sure you understand the difference a plugin and a plug-in.
An InDesign plugin (no dash) is an enhancement built within the UXP environment.
An InDesign plug-in (with a dash) is an enhancement built on top of the C++-based InDesign SDK.
You need to start here:
https://developer.adobe.com/console/servicesandapis
There is an entry that says ‘InDesign Plugin’ which is for the UXP environment.
That’s not the one you want. For the InDesign C++ SDK, you need another entry that says just ‘InDesign’:
For my training course for would-be InDesign C++ developers, I wrote a book that I use as training course notes.
I wrote this more than a decade ago, and CS3 and CS4 are now fossils of a distant past, but as it so turns out, the underlying concepts of InDesign C++ programming have not changed, and the book is still highly relevant.
Before you can dive into the SDK you need to ‘grok’ a whole range of higher-level abstractions and terminology: boss classes, interfaces, implementations, Query vs Get,…
And remember, if you are interested in getting a head start with InDesign C++ development, make sure to talk to me: [email protected]
Pre-Amble
This blog post is about a few simple C macros that make my life easier.
So, I won’t get into the pros and cons of do-while(false)
vs. early return
vs. throw/catch
, I won’t delve into D.R.Y (Don’t Repeat Yourself) and K.I.S.S (Keep It Simple, Stupid), or ‘how many lines in a function’. That’s stuff to be discussed over a beer.
The InDesign SDK
For the longest time, I’ve been mimicking the style of the SDK when writing C++ plug-ins.
I.e. I want to make sure the code I am writing ‘looks the part’, and follows a similar style and similar coding conventions.
do-while(false);
One of the somewhat controversial constructs in the InDesign SDK is the use of do-while(false)
; i.e. a loop that does not loop.
This construct is used to implement what I describe as ‘a pre-condition ladder’.
The idea is that every function starts with a list of pre-conditions and assertions that are expected to be true, and once all the pre-condition tests pass, you get to the ‘meat’ of the function and execute the function.
The preconditions are like the rungs of the ladder, and if something is not right, you break
and ‘fall off the ladder’ towards the while (false)
. If all is well, you can ‘climb down the ladder’ and safely reach the ‘meat’ of the function.
Something like
int f(params)
{
int value = 0;
do
{
if (! somePrecondition1())
{
break;
}
if (somethingUnexpectedBad())
{
LOG_ERROR("Something unexpected bad happened");
break;
}
value = AllisWell(bla) + 123;
}
while (false);
return value;
}
The function purposely only has a single return
at the end, which makes it easier to break and inspect the return value with a debugger, so we don’t use ‘early returns’.
Using C Macros
I’ve recently built a few simple C macros that, at least for me, make my InDesign plug-in code less verbose and more readable.
Note that the macros I am presenting here are simplified versions of the ones that I am actually using.
The full-fledged versions that I have built and actually use have many more features, like compile-time balancing and error detection, but discussing those details would lead me too far into the woods.
The idea is that I want to help the reader of my code (that reader is often me, six months from now).
If the reader of my code sees the beginning of a do
statement, I am leaving them guessing a bit as to what that do
will be used for. A real loop? An infinite loop? A condition ladder construct?
A second idea is that in C/C++ the ‘end of a thing’ is the closing curly brace. It is heavily overloaded, and used for many different things. That can make code hard to figure out.
With these macros, the ‘end of some things’ is much more explicit – rather than a curly brace, there is an explicit ‘END’ to the ‘thing’.
The third idea is that in the InDesign C++ plug-in code I like to distinguish between different kinds of pre-conditions:
• ‘normal’ pre-conditions. Things you want to check for, and bail out of the function if not met, but which are normal and expected -> PRE_CONDITION
• ‘error’ pre-conditions. Things that ‘should not happen’, so you want to emit errors to the logs -> SANITY_CHECK
• things that make you go ‘huh?’ but are not necessarily errors -> EXPECTED
#ifndef __PREPROCESSOR_CONSTRUCTS__h_
#define __PREPROCESSOR_CONSTRUCTS__h_
//
// Preprocessor magic to convert code into string
//
#ifndef __STR
#ifdef __VAL
#undef __VAL
#endif
#define __STR(x) __VAL(x)
#define __VAL(x) #x
#endif
#define BEGIN_CONDITION_LADDER do {
#define END_CONDITION_LADDER } while (false)
#define CONDITION_LADDER_BREAK break
#define BEGIN_INFINITE_LOOP do {
#define END_INFINITE_LOOP } while (true)
#define INFINITE_LOOP_BREAK break
#define PRE_CONDITION(CONDITION, BREAK) if (! (CONDITION)) BREAK
#define SANITY_CHECK(CONDITION, BREAK) if (! (CONDITION)) { ASSERT("! " __STR(CONDITION) ); BREAK; }
#define EXPECTED(CONDITION) if (! (CONDITION)) ASSERT("! " __STR(CONDITION))
#define BEGIN_FUNCTION do {
#define END_FUNCTION } while (false)
#define FUNCTION_BREAK break
#endif // __PREPROCESSOR_CONSTRUCTS__h_
A sample function:
bool16 ActivePageItemHelper::GetStoryFrameList(
const UIDRef& storyUIDRef,
UIDList& pageItemList)
{
bool16 success = kFalse;
BEGIN_FUNCTION;
IDataBase* dataBase = storyUIDRef.GetDataBase();
SANITY_CHECK(dataBase, FUNCTION_BREAK);
pageItemList = UIDList(dataBase);
InterfacePtr<ITextModel> textModel(storyUIDRef, UseDefaultIID());
SANITY_CHECK(textModel, FUNCTION_BREAK);
InterfacePtr<IFrameList> frameList(textModel->QueryFrameList());
PRE_CONDITION(frameList, FUNCTION_BREAK);
success = kTrue;
int32 curPageItemIdx = -1;
for (int32 frameIdx = 0; frameIdx < frameList->GetFrameCount(); frameIdx++)
{
bool16 frameIdxSuccess = kFalse;
BEGIN_CONDITION_LADDER;
InterfacePtr<ITextFrameColumn> textFrameColumn(frameList->QueryNthFrame(frameIdx));
SANITY_CHECK(textFrameColumn, CONDITION_LADDER_BREAK);
InterfacePtr<IHierarchy> textFrameColumnHierarchy(textFrameColumn, UseDefaultIID());
SANITY_CHECK(textFrameColumnHierarchy, CONDITION_LADDER_BREAK);
InterfacePtr<IHierarchy> multiColumnItemHierarchy(textFrameColumnHierarchy->QueryParent());
SANITY_CHECK(multiColumnItemHierarchy, CONDITION_LADDER_BREAK);
InterfacePtr<IHierarchy> splineItemHierarchy(multiColumnItemHierarchy->QueryParent());
SANITY_CHECK(splineItemHierarchy, CONDITION_LADDER_BREAK);
InterfacePtr<IGraphicFrameData> splineItem(splineItemHierarchy, UseDefaultIID());
SANITY_CHECK(splineItem, CONDITION_LADDER_BREAK);
frameIdxSuccess = kTrue;
//
// Check if already in list
//
UID splineUID = ::GetUID(splineItem);
PRE_CONDITION(curPageItemIdx < 0 || pageItemList[curPageItemIdx] != splineUID, CONDITION_LADDER_BREAK);
pageItemList.Append(splineUID);
END_CONDITION_LADDER;
success = success && frameIdxSuccess;
}
END_FUNCTION;
return success;
}
Points of interest:
– I am expecting textModel
to be non-null, and if it is null, something is seriously wrong, hence I use SANITY_CHECK
.
– I need frameList
to be non-null, but if it is null, that’s not an error; it simply means that we cannot run the function in this particular context, hence I use PRE_CONDITION
.
– Within the loop, I have little CONDITION_LADDER
. Make sure all is well before executing pageItemList.Append(splineUID)
There are some interesting aspects to this.
Because these are macros, it is easy to (temporarily) add some extra code to macros like BEGIN_FUNCTION
/END_FUNCTION
.
You should add logging/tracing code in a debug version, or can add code that will break into the debugger when certain conditions are met.
If you have logger infrastructure, add the logging calls to the macros (e.g. SANITY_CHECK
would logs errors, PRE_CONDITION
does not log anything)
Adding logging and tracing to the BEGIN_FUNCTION
/END_FUNCTION
macros can be help in situations where you’re unable to use a debugger on a problem, e.g. when the issue happens on a customer’s server, and you cannot get access to the server.
Such code will ‘pooff!’ away in the release version, and only be added in the debug version.
Take it for what it is worth; it works for me. Using these macros adds another level of abstraction, which can be considered a ‘bad thing’, but overall, I find these helpful.