2

If I'm designing my own COM error codes that coexist with Microsoft's HRESULT values, should I set the facility code to FACILITY_ITF ("defined solely by the developer of the interface or function that returns the status code"), or should I set bit 29 which indicates it is a customer code? Should I do both?

If I set bit 29, can I define my own facility codes that conflict with Microsoft's?

snips-n-snails
  • 637
  • 5
  • 22
  • The problem with FACILITY_ITF is since it's (supposedly) used by everyone, it's not owned by anyone. So you cannot distinguish your errors from others. Using this is a stupid 25 years old recommendation IMHO. Relying only on HRESULT for error reporting is difficult in a multi-component system. What I suggest is reuse already defined Microsoft's error as much as possible (winerror.h has a good choice). When I really need to define my own, I create my own FACILITY and hope no one else will use the same... I wouldn't use b29 at all. There's no magic bullet – Simon Mourier Nov 08 '18 at 07:02
  • If you use FACILITY_ITF, you will already be competing/colliding with more than 3200 codes already defined by Microsoft: https://www.magnumdb.com/search?q=0x8004%2A – Simon Mourier Nov 08 '18 at 07:06
  • The most important thing to do by a very long mile is to implement ISupportErrorInfo so the client programmer has a shot at getting a descriptive error message. Next most important is to pick an existing error code, there are many that fit. Do not use E_FAIL or E_UNEXPECTED. After that it the only thing that still matters is that you can pick up the phone, listen to the quoted error code and shoot off right away, "oh, you did foo wrong" or "that didn't come from my code". – Hans Passant Nov 08 '18 at 07:11
  • @SimonMourier Why would you not use bit 29? – snips-n-snails Nov 08 '18 at 22:19
  • I don't know, probably because I just never did and never seen someone use it, plus I don't remember there was this "customer" bit years ago... memory failure :-). But the problem is the same, b29 or not, if everyone use it, you still have a colliding issue. – Simon Mourier Nov 09 '18 at 07:11
  • @SimonMourier Yes, that has been the case long before HRESULTs. Whenever you call somebody else's function, don't return their return code verbatim unless you share a common set of return values or you've made room for theirs in your return code numbering space! – snips-n-snails Nov 09 '18 at 19:11

2 Answers2

1

If you are returning an error code from an interface you have created and control (the vast majority of cases), then using FACILITY_ITF is the way to go.

As a way of evidence, look up the definition of the vbObjectError constant used in Visual Basic 6 to define custom error codes. VB6 applications should “add” that constant to any custom error code they define. It turns out that that constant is 0x80040000, which is FACILITY_ITF with the Severity bit set to SEVERITY_ERROR (= 1). The requirement to “add” custom codes to it is just a sloppy way to say “OR the constant with your custom code”.

Returning custom error codes on an interface you don’t control is tricky, unless the particular interface naturally lends itself to that sort of thing. You don’t know how the other side is going to react to it. Realistically, unless the other side is a programming environment that can plainly stop and report the code to the user, there is nothing sensible it could do with a custom error. I’ve never had the need to do that, and I would try to stick to documented error codes for such interfaces.

As far as the C bit (bit-29): at least some documentation (e.g. https://learn.microsoft.com/en-us/windows/desktop/com/structure-of-com-error-codes) lists it as RESERVED. I would leave “C” bit-29 alone (leave as zero), unless there is a clear and specific documentation otherwise.

Euro Micelli
  • 33,285
  • 8
  • 51
  • 70
  • Perhaps the C bit is marked as reserved because it's reserved for the customer? – snips-n-snails Nov 08 '18 at 22:18
  • @traal, that’s extremely unlikely. The word “reserved” has usually a very precise meaning in technical specifications: “not to be used by consumers of the specification”. Typically one of: “the owner of the specification put this aside for potential future use”; “the system uses this for undocumented internal purposes subject to change at any time, don’t touch it”; “a previous (maybe draft) version used this but it’s no longer used and now we can’t use it anymore for anything else”; or “this has to exist (‘padding’) but it has no purpose”. Besides, R,C,N, and r are all reserved, not just C – Euro Micelli Nov 09 '18 at 13:51
  • “the owner of the specification put this aside for potential future use” -- your definition makes sense. They set bit 29 aside, then later designated it the Customer bit because "[All HRESULT values used by Microsoft have the C bit clear.](https://msdn.microsoft.com/en-us/library/cc231214.aspx#Appendix_A_1)" – snips-n-snails Nov 09 '18 at 19:25
0

I believe that FACILITY_ITF can be used for your own interface. Normally, you should be able distinguish your errors from other errors, with the Interface ID (IID).

from https://learn.microsoft.com/en-us/windows/desktop/com/structure-of-com-error-codes:

FACILITY_ITF 4 For most status codes returned from interface methods. The actual meaning of the error is defined by the interface. That is, two HRESULTs with exactly the same 32-bit value returned from two different interfaces might have different meanings.

See also this: https://learn.microsoft.com/en-us/windows/desktop/com/codes-in-facility-itf (link already provided in the question, which suggests you really can use FACILITY_ITF for your own interface).

May there's a simpler way, but here is a simple example:

#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
        const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}

// {7D51F88F-76BD-4970-BEC1-E090C032A7F5}
MIDL_DEFINE_GUID(IID, IID_MyInterface, 
0x7d51f88f, 0x76bd, 0x4970, 0xbe, 0xc1, 0xe0, 0x90, 0xc0, 0x32, 0xa7, 0xf5);

// {614EBF64-44FF-4615-90DE-09D05AF7F09B}
MIDL_DEFINE_GUID(IID, IID_ISomeoneElseInterface, 
0x614ebf64, 0x44ff, 0x4615, 0x90, 0xde, 0x9, 0xd0, 0x5a, 0xf7, 0xf0, 0x9b);

void TestITFFacility();
IErrorInfoPtr CreateErrorInfo(LPCTSTR desc, const CLSID& clsid, LPCWSTR source, DWORD helpContext);

int _main(int argc, TCHAR* argv[], TCHAR* envp[])
{
    TestITFFacility();
    return 0;
}

void TestITFFacility()
{
    IID tab[2] = { IID_MyInterface, IID_ISomeoneElseInterface };
    for (int i=0; i<sizeof(tab)/sizeof(IID); i++)
    {
        try
        {
            // artificially throw a _com_error.
            // note: 0x0200: see https://learn.microsoft.com/en-us/windows/desktop/com/codes-in-facility-itf
            // it is recommended that only code values in the range of 0x0200-0xFFFF be used
            throw _com_error(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (0x7D + 0x0200)),
                CreateErrorInfo(_T("something went wrong"),
                tab[i],
                L"Some Source",
                0));

        }
        catch(_com_error e)
        {
            auto hr = e.Error();
            if (InlineIsEqualGUID(e.GUID(), IID_MyInterface))
                _tprintf(_T("My Interface, hr=%08lX, code=%04X\n"), hr, e.WCode());
            else
                _tprintf(_T("Someone else Interface, hr=%08lX, code=%04X\n"), hr, e.WCode());
        }
    }
}

// method to create an IErrorInfo
IErrorInfoPtr CreateErrorInfo(LPCTSTR desc, const CLSID& clsid, LPCWSTR source, DWORD helpContext)
{
    ICreateErrorInfoPtr pcerrinfo;
    IErrorInfoPtr perrinfo;
    HRESULT hr = CreateErrorInfo(&pcerrinfo);
    assert(SUCCEEDED(hr));
    if (S_OK == hr)
    {
        _bstr_t olestr = _bstr_t(desc);
        pcerrinfo->SetDescription(olestr);
        pcerrinfo->SetGUID(clsid);
        pcerrinfo->SetHelpContext(helpContext);
        pcerrinfo->SetSource((LPOLESTR)source);
        hr = pcerrinfo->QueryInterface(IID_IErrorInfo, (LPVOID FAR*) &perrinfo);
        assert(SUCCEEDED(hr));
    }
    return perrinfo;
}
ggo
  • 471
  • 7
  • 17