Custom Errors
-
Are custom errors, as defined in the following link, supported in R20?
https://developers.maxon.net/docs/cpp/2023_2/page_maxonapi_error_types.html
Everything compiles and works fine for R21 in VS 2017. But for R20 with VS 2015 I get the following errors.
Note that I get the errors when trying to return the Error from another method in another class. The apierror.cpp file itself compiles, but linking to the header and trying to use it does not.
Might be a very basic user error on my part, but these are so tricky to hunt down with the new Maxon API.
These are all within the generated apierror1.hxx file that was generated from my apierror.h/cpp files.
Error C3083 'ErrObj': the symbol to the left of a '::' must be a type
Error C3861 'GetInstance': identifier not found
Error C2039 'GetInstance': is not a member of 'ApiErrorInterface'
Error C3083 'Hxx1': the symbol to the left of a '::' must be a type
Error C2039 'ReferenceClass': is not a member of 'ApiErrorInterface' -
Problem solved. There was a change in the API from R20 to R21.
Wrapping the code in your header interface definition like shown below will allow Custom Errors to compile in both.
#if API_VERSION >= 20000 && API_VERSION < 21000 * static_cast<typename S::DirectlyReferencedType::ReferenceClassHelper::type*>(this) = S::DirectlyReferencedType::ReferenceClassHelper::object::GetInstance() (); #elif API_VERSION >= 21000 && API_VERSION < 22000 *static_cast<typename S::DirectlyReferencedType::Hxx1::ReferenceClass*>(this) = S::DirectlyReferencedType::Hxx1::ErrObj::GetInstance()(); #endif
-
Hi @kbar, thanks for reaching out us.
With regard to the issue encountered, I appreciate a lot you sharing your workaround but I've doubt that's the sole direction to look especially if the change is introduced in some generated header file.
Two questions from my side:
- did you used the respective project tool for R20 and R21 to create the related IDE solutions?
- can you provide a sample project to reproduce the issue on our side?
Looking forward further information of yours, give best.
-
Hi @r_gigante
I believe my solution is the correct one since I got this change from the official C4D documentation by comparing the code from R20 and R21 in the docs.
Here is the documentation from R20
And here is the documentation from R21
On a side note, the documentation is very unclear how you should use code like this. The main point that is never discussed is that in your header file you should include the generated files, in my case apierror1.hxx and apierror2.hxx. Working examples for these error codes (and all other Maxon API examples) with a full .h and .cpp file would be great to include directly in the documentation itself, ie not code snippets, but the full usable code.
Cheers,
Kent -
Even though my solution compiles now for R20, when I return an error it hits an assert somewhere internally in C4D.
Would it be possible for you to create a full working example of a Custom Error that works in R20 so that I can see exactly what you have added to your files? With all of these pre-processor issues and fancy macros it is hard to know what we need to add to make things work.
I don't know what to add to make it work in R20, works fine in R21 using the changes mentioned above from the latest docs. But for R20 it just asserts every time I return my custom error.
My problem may be due to the following, since I am not using either of these yet.
Where, when and how do you use these?
I am introducing additional methods so I can't use the second one (even though I have no idea how or when you would use it). But perhaps I need to use the first one somewhere? Is it between the two generated headers? If so then why does it work fine without this call in R21?
I am using MAXON_COMPONENT_OBJECT_REGISTER as mentioned in the docs. Is this wrong? Are the docs wrong?
Thanks in advance,
KentHere is my error...
CINEMA 4D.exe!00007ff6ddacd841() Unknown testplugin.xdl64!maxon::Declaration<ApiError,ApiErrorObjectPrivateHelper>::operator()<&maxon::`anonymous namespace'::g_translationUnit,0>() Line 567 C++ testplugin.xdl64!ApiErrorInterface::COWReferenceFunctionsImpl<maxon::ErrorInterface::COWReferenceFunctionsImpl<maxon::Object::COWReferenceFunctionsImpl<maxon::RefBaseFunctions<maxon::DirectRef<ApiErrorInterface const ,maxon::StrongCOWRefHandler,ApiError> > > > >::Create(const maxon::SourceLocation & allocLocation, ApiResult result, APIErrorData data) Line 49 C++
This is where the error occurs in module.h
-
One additional piece of information I just found. In the Output in VS I see this when the breakpoint hits.
../../core.framework/generated/hxx/objectbase1.hxx(441): CRITICAL: Module net.maxon.c4d.cinema.framework attempts to initialize com.gamelogicdesign.error.api of different module com.gamelogicdesign.pluginmanager [caused by com.gamelogicdesign.error.api]. [object_impl.cpp(2379)]
CINEMA 4D.exe has triggered a breakpoint.The message seems strange to me since the error is in the same "module" (ie plugin). I even tried giving the error id a similar path such as com.gamelogicdesign.pluginmanager.error.api but it had a similar warning, just displaying this identifier instead.
-
Hi @kbar, a quick follow up to confirm that a complete example will be made available on GitHub asap.
Best, R
-
Thanks @r_gigante. Looking forward to it.
Cheers,
Kent -
Hi @kbar , the Custom Error example is available on GitHub.
It's made of:- example.customerror.framework - where the CustomError interface is defined
- example.customerror.module - where the CustomError interface is implemented
- example.customerror.use - where the CustomError is actually used.
Because of the release of the framework, the repository now contains a new folder named frameworks, whose contents should be copied in the SDK frameworks folder.
The same code runs compiles on both R20 and R21 and doesn't hit any Critical break point.
Last, with regard to registration the way to go is
MAXON_COMPONENT_OBJECT_REGISTER
as you've already pointed out and theMAXON_ERROR_REGISTERX
is simply wrong due to the additional "X" at the end: the correct macro isMAXON_ERROR_REGISTER
Best, R
-
Hi @r_gigante ,
Thanks for the code. I have it working now in R20. The issue seems to be that for R20 the interface file needs to be part of a framework. This is not the case for R21. In R21 I could include all the code locally within my plugin and it worked fine, but for R20 it needed to be extracted out to the frameworks folder as shown in your example. I didn't need to create a new module (ie its own xdl64) since I can register the framework from within my main plugin itself. So I have a working solution now.
This is another reason why I would really like to have the ability to include our own framework folder in the projectdefinition.txt file. Mixing it in with the regular SDK ones is really not ideal.
Thanks again for the example code.
Cheers,
Kent -
Hi @r_gigante!
Looking at this thread, it looks like the only way for me to register and use my own error types, requires me to put them into a separate framework, instead of just defining them as part of my project, as well as actually copying them into the SDK folder. Is it only me or is this mindblowingly clumsy and tedious? Or am I just getting this wrong? This makes the Cinema 4D error handling system so much harder to use and embrace.
It would be great to know whether this is just a misunderstanding on my side (which I sincerely hope it is). Also the documentation is not really clear on this there is example code how in the regular SDK docs which make it look like a piece of cake and they are lacking any reference to this disproportionate complexity introduced to just add twenty lines of code for a custom error handler.
I am trying to get all our developers to use and embrace the new error handling, but this feels like it will make my life pretty hard in this regard.
Any further clarification very much appreciated.
Best
Timm -
It should be possible to declare and implement MAXON interfaces within a module, setting
register=true
That is what the maxonsdk.module does.
I don't know if that would also work with custom error stuff or not.
Using frameworks is not "clumsy and tedious", it the clean way to create classes and functions and stuff you can re-use in different projects. All of Cinema's error types are defined in frameworks, so different modules can use them - including your plugins.
-
Hi @tdapper thanks for reaching out us.
With regard to your comment, although I confirm that you don't have to put them in a separate framework but it's actually recommended to grant good design and straightforward reusability.
For the sake of clarity, find below the files used to create a project which includes both the declaration and the implementation of the custom error together with the code using it. The example is derived from the code presented on GitHub.
project/projectdefinition.txt
// Supported platforms - can be [Win64;OSX] Platform=Win64;OSX // Type of project - can be [Lib;DLL;App] Type=DLL // API dependencies APIS=cinema.framework;mesh_misc.framework;math.framework;crypt.framework;python.framework;core.framework; // C4D component C4D=true stylecheck.level=3 // must be set after c4d=true stylecheck.enum-registration=false stylecheck.enum-class=false ModuleId=net.maxonexample.support.single_plugins.PC12316
source/PC12316_CustomErrorImpl.cpp
#include "PC12316_CustomErrorInterface.h" // This example shows the implementation of a custom error type. class PC12316_CustomErrorImpl : public maxon::Component<PC12316_CustomErrorImpl, PC12316_CustomErrorInterface> { // use ErrorObjectClass to implement basic error functionality MAXON_COMPONENT(NORMAL, maxon::ErrorObjectClass); public: maxon::Result<void> CopyFrom(const PC12316_CustomErrorImpl& src) { _errorCode = src._errorCode; return maxon::OK; } public: MAXON_METHOD maxon::String GetMessage() const { return FormatString("Custom error code is @", _errorCode); } // custom methods MAXON_METHOD void SetCustomErrorCode(maxon::Int errorCode) { _errorCode = errorCode; } MAXON_METHOD maxon::Int GetCustomErrorCode() const { return _errorCode; } private: maxon::Int _errorCode; ///< error code value }; // register all the non-static methods in the implementation MAXON_COMPONENT_OBJECT_REGISTER(PC12316_CustomErrorImpl, PC12316_CustomErrorObject);
source/PC12316_CustomErrorInterface.h
#ifndef PC12316_CUSTOMERRORINTERFACE_H__ #define PC12316_CUSTOMERRORINTERFACE_H__ #include "maxon/object.h" // This example shows the declaration of a custom error type. // The custom error is able to store a custom error code. // --------------------------------------------------------------------- // Custom error class that stores an error code. // --------------------------------------------------------------------- class PC12316_CustomErrorInterface : MAXON_INTERFACE_BASES(maxon::ErrorInterface) { MAXON_INTERFACE(PC12316_CustomErrorInterface, MAXON_REFERENCE_COPY_ON_WRITE, "net.maxonexample.example.errors.PC12316_customerror"); public: MAXON_ADD_TO_COPY_ON_WRITE_REFERENCE_CLASS( void Create(MAXON_SOURCE_LOCATION_DECLARATION, maxon::Int errorCode) { #if API_VERSION >= 20000 && API_VERSION < 21000 * static_cast<typename S::DirectlyReferencedType::ReferenceClassHelper::type*>(this) = S::DirectlyReferencedType::ReferenceClassHelper::object::GetInstance() (); #elif API_VERSION >= 21000 *static_cast<typename S::DirectlyReferencedType::Hxx1::ReferenceClass*>(this) = S::DirectlyReferencedType::Hxx1::ErrObj::GetInstance()(); #endif typename S::DirectlyReferencedType::Ptr e = this->MakeWritable(false).GetPointer(); e.SetLocation(MAXON_SOURCE_LOCATION_FORWARD); e.SetCustomErrorCode(errorCode); } ); // custom methods // --------------------------------------------------------------------- // Stores an custom error code. // --------------------------------------------------------------------- MAXON_METHOD void SetCustomErrorCode(maxon::Int errorCode); // --------------------------------------------------------------------- // Returns the stored custom error code. // --------------------------------------------------------------------- MAXON_METHOD maxon::Int GetCustomErrorCode() const; }; #include "PC12316_CustomErrorInterface1.hxx" #include "PC12316_CustomErrorInterface2.hxx" #endif /* PC12316_CUSTOMERRORINTERFACE_H__ */
source/PC12316.cpp
#include "c4d.h" static const Int32 PluginID = 99912316; #include "PC12316_CustomErrorInterface.h" // Dummy test function maxon::Result<void> TestFunction(maxon::Int * val); maxon::Result<void> TestFunction(maxon::Int * val) { iferr_scope; if (!val) return PC12316_CustomError(MAXON_SOURCE_LOCATION, 4242); ApplicationOutput("Value is @", *val); return maxon::OK; } // Command to test the custom error class CustomErrorExample : public CommandData { public: #if API_VERSION >= 20000 && API_VERSION < 21000 Bool Execute(BaseDocument* doc) #elif API_VERSION >= 21000 Bool Execute(BaseDocument* doc, GeDialog* parentManager) #endif { iferr_scope_handler { return false; }; if (!doc) return false; iferr (TestFunction(nullptr)) ApplicationOutput("Error: @", err); maxon::Int a = 100; iferr (TestFunction(&a)) ApplicationOutput("Error: @", err); return true; } static CommandData* Alloc() { return NewObj(CustomErrorExample) iferr_ignore("Unexpected failure on allocating CustomErrorExample plugin."_s); } }; Bool RegisterCustomErrorExample(); Bool RegisterCustomErrorExample() { return RegisterCommandPlugin(PluginID, "PC12316_CustomErrorExample"_s, PLUGINFLAG_COMMAND_OPTION_DIALOG, nullptr, ""_s, CustomErrorExample::Alloc()); } Bool PluginStart() { if (!RegisterCustomErrorExample()) return false; return true; } void PluginEnd() { } Bool PluginMessage(Int32 id, void* data) { return true; }
A final minor note on
register=true
: since R21 it's not needed anymore because in former releases it was needed to specify when a module was supposed to run in pure legacy-mode but nowadays modules can be either hybrid (classic + maxon API) or maxon API-only.Hoping this makes using our Error Handling system more clear and easy to integrate in your products, give best and don't hesitate to ask for more.
Best, R