Friday
Platform: Windows 11, ArchiCAD 27 (build 6020)
DevKit: API.Development.Kit.WIN.27.6003
Toolchain: Visual Studio 2019 (v142), CMake, Ninja
I want to be upfront: this add-on is vibe coded — meaning it was written with AI assistance without deep prior knowledge of the ArchiCAD C++ API. I'm an architect, not a developer. The goal is a simple launcher add-on that opens a Python GUI application from ArchiCAD's menubar.
CheckEnvironment called! RegisterInterface called! RegisterMenu OK ← ACAPI_MenuItem_RegisterMenu returns 0 Initialize called! InstallMenuHandler OK ← ACAPI_MenuItem_InstallMenuHandler returns 0
No menu entry appears anywhere in the menubar or any submenu.
I've been trying to get a custom top-level menu ("Layout Manager") to appear in the menubar using MenuCode_UserDef.
During debugging I discovered my hand-rolled enum had the wrong value — I wasn't including ACAPinc.h (see note below), so I had defined it manually:
// What I had (WRONG — this is actually MenuCode_Image): MenuCode_UserDef = 8 // Correct value from APIdefs_Registration.h: MenuCode_UserDef = 0
I have now corrected this and rebuilt. The menu still does not appear.
I also noticed this note in the Add-On Manager dialog:
"The visibility and menu location of Add-Ons in the Archicad interface depend on Work Environment settings. These can be customized in Work Environment → Command Layout Schemes."
When searching for "Layout Manager" in Work Environment → Menus, the command does not appear in the available commands list.
I'm not sure what I'm missing. It could be the enum value, the resource structure, the Work Environment, the APIAddon_Preload return value, the struct layouts, the way I'm forward-declaring the API functions — or something else entirely that I haven't thought of.
Rather than assuming I know what the problem is, I'd really appreciate a fresh set of eyes. What would you check first? Is there something fundamentally wrong with this approach that would explain why RegisterMenu returns NoError but nothing shows up?
#include <windows.h> #include <shellapi.h> #include <stdio.h> typedef int GSErrCode; typedef unsigned int GSFlags; #define NoError 0 typedef enum { APIAddon_DontRegister = 0, APIAddon_Normal = 1, APIAddon_Preload = 2 } API_AddonType; typedef enum { MenuCode_UserDef = 0 // from APIdefs_Registration.h } APIMenuCodeID; #define MenuFlag_Default 0 struct API_MenuItemRef { short menuResID; short itemIndex; }; struct API_ServerApplicationInfo { short mainVersion; short releaseVersion; char pad[508]; }; struct API_EnvirParams { API_ServerApplicationInfo serverInfo; char pad[1024]; }; struct API_MenuParams { API_MenuItemRef menuItemRef; }; GSErrCode __cdecl ACAPI_MenuItem_RegisterMenu( short menuStrResID, short promptStrResID, APIMenuCodeID menuPosCode, GSFlags menuFlags); GSErrCode __cdecl ACAPI_MenuItem_InstallMenuHandler( short menuStrResID, GSErrCode (__cdecl* handlerProc)(const API_MenuParams*)); static void WriteLog(const wchar_t* msg) { /* logs to file */ } static void LaunchLayoutManager() { /* ShellExecuteW to .exe */ } static GSErrCode __cdecl MenuCommandHandler(const API_MenuParams*) { LaunchLayoutManager(); return NoError; } extern "C" { __declspec(dllexport) API_AddonType __cdecl CheckEnvironment(API_EnvirParams*) { return APIAddon_Preload; } __declspec(dllexport) GSErrCode __cdecl RegisterInterface(void) { return ACAPI_MenuItem_RegisterMenu(32001, 0, MenuCode_UserDef, MenuFlag_Default); } __declspec(dllexport) GSErrCode __cdecl Initialize(void) { return ACAPI_MenuItem_InstallMenuHandler(32001, MenuCommandHandler); } __declspec(dllexport) GSErrCode __cdecl FreeData(void) { return NoError; } } // extern "C"
'STR#' 32001 "Menu strings" { "Layout Manager" "Layout Manager..." }
'MDID' 32500 "Add-On Identifier" { 944270126 /* developer ID */ 1113797136 /* local ID */ }
I am intentionally not using ACAPinc.h because including it caused linker conflicts with the Module libraries in my CMake setup. Instead I forward-declare the two API functions I need (ACAPI_MenuItem_RegisterMenu and ACAPI_MenuItem_InstallMenuHandler) with their C++ name-mangled signatures as exported by ACAP_STAT.lib. Both functions return NoError at runtime.
I'm open to the possibility that this approach is flawed in a way I don't understand. Any guidance appreciated.
Thank you.
Operating system used: Windows 12
Monday
Hi Barry,
Asking for fresh eyes was the right call — and the writeup is genuinely good, better than most bug reports here. The enum fix was necessary but it was never going to be sufficient, and here's why.
ACAPI_MenuItem_RegisterMenu does not load your strings. It records the resource ID and returns. Archicad reads the actual 'STR#' resource much later, when it builds the menu bar. So a missing or uncompiled resource fails silently, long after your return code was checked. That's your whole symptom: four green log lines and an empty menu bar.
So the question isn't "why did registration fail" — it didn't. It's "where did the strings go."
This is the one that matches your Work Environment symptom exactly.
In the WE command list, commands are grouped under the add-on's registered name. That name comes from CheckEnvironment, which is supposed to do this:
API_AddonType __ACENV_CALL CheckEnvironment (API_EnvirParams* envir)
{
RSGetIndString (&envir->addOnInfo->name, 32000, 1, ACAPI_GetOwnResModule ());
RSGetIndString (&envir->addOnInfo->description, 32000, 2, ACAPI_GetOwnResModule ());
return APIAddon_Normal;
}Yours returns immediately without touching addOnInfo — and it can't, because your stub API_EnvirParams is char pad[1024] with no addOnInfo member at all. There's also no 'STR#' 32000 anywhere in your RFIX file to read from.
Result: an unnamed add-on. Nothing to search for in Work Environment → Menus. Which is precisely what you observed.
Two halves, both required — the string in RFIX and the RSGetIndString calls in CheckEnvironment. Fixing either one alone gets you nowhere.
You have LayoutManagerAddon.grc in RFIX and LayoutManagerAddon.grc in RINT. The DevKit convention is:
res/RFIX/LayoutManagerAddonFix.grc <- note the "Fix" suffix res/RINT/LayoutManagerAddon.grc
The suffix isn't cosmetic. ResConv compiles each .grc to an intermediate output derived from the source filename, and identical base names in two directories can land on the same output path — one silently overwrites the other. That would explain MDID being found (add-on loads, shows in Add-On Manager) while 'STR#' 32001 is nowhere to be seen.
I'm not certain this is what's biting you specifically, but it costs thirty seconds to rename and rebuild.
How to verify either of the above: open the built .apx in Resource Hacker, or in Visual Studio via File → Open → File → then Resource View. Look for a custom resource of type STR#. If your menu strings aren't in there, the C++ side is irrelevant until that's fixed.
res/RFIX/LayoutManagerAddonFix.grc
'STR#' 32000 "Add-On name and description" {
/* [ 1] */ "Layout Manager"
/* [ 2] */ "Launches the Layout Manager application"
}
'MDID' 32001 "Add-On Identifier" {
944270126
1113797136
}res/RINT/LayoutManagerAddon.grc
'STR#' 32500 "Menu strings" {
/* [ 1] */ "Layout Manager" // top-level menu title
/* [ 2] */ "Layout Manager..." // the command item
}
'STR#' 32501 "Menu prompt strings" {
/* [ 1] */ "Layout Manager"
/* [ 2] */ "Opens the Layout Manager"
}ACAPI_MenuItem_RegisterMenu (32500, 32501, MenuCode_UserDef, MenuFlag_Default);
The specific numbers are yours to choose as long as they're in the 32000–32767 range and match the code. What matters: the prompt STR# must have the same number of lines as the menu STR#, title line included. ResConv accepts a mismatch without complaint; Archicad then quietly drops items. Your current promptStrResID = 0 should be a real resource.
Also drop APIAddon_Preload back to APIAddon_Normal — preload is for add-ons that must be alive before a project opens, which a launcher isn't.
I understand why you went there, but this is the actual root problem rather than a workaround for it.
API_EnvirParams and API_MenuParams are not shaped like your stubs. You're passing pointers to structures whose real layout you're guessing at, and reading fields at addresses that hold something else. Right now it happens not to crash. That's luck, not correctness — and it's already cost you addOnInfo.
The linker conflicts that pushed you here are almost certainly a C runtime mismatch: the DevKit libraries are built against the release DLL runtime (/MD), while CMake's default Debug configuration uses /MDd. That produces exactly the flavour of duplicate-symbol and unresolved-symbol noise that looks like ACAPinc.h is at fault when it isn't.
Rather than patching your own CMakeLists, take Examples/Example_Add-On from the DevKit wholesale as your scaffold and drop your .cpp into it. It has the runtime setting, the WINDOWS / ACExtension / UNICODE defines, and the ResConv steps already wired correctly. Copying just the CMakeLists.txt out of it won't work — the relative paths to the DevKit break.
Bisect the resource question. Temporarily swap MenuCode_UserDef for one of the standard placement codes and restart Archicad. If the item appears anywhere at all, your resources are compiling and the problem is UserDef placement plus Work Environment. If it still doesn't, go back to section 2.
Clear the cache. Archicad caches add-on registration and command layout. Your first broken registration may still be cached, meaning you're rebuilding code and looking at a stale menu. Quit Archicad completely (not just the project) before each rebuild, and once the add-on has a proper name, create a fresh Command Layout Scheme from the factory default in Work Environment → Command Layout Schemes.
One last thing: for what it's worth, the fact that you got four correct entry points, correct exports, and a clean load without a working DevKit build is not nothing. The resource pipeline is the part of this API that catches out people who have read the documentation — it fails quietly by design, and there's no error to search for. You didn't miss something obvious.
Good luck — post the resource inspection result if it doesn't come together.