1
votes

I have created a C++ Test Project for my C++ library in Visual Studio 2010. The test project uses C++/CLI (/clr set) and I am having problems retrieving the last error set by my library functions; GetLastError always returns zero.

In the example below I want to test that the correct return value and last error is set by my Write function:

[TestMethod]
void Write_InvalidHandle_Error()
{
    char buffer[] = "Hello";
    DWORD actual = -1;
    DWORD expected = ERROR_INVALID_HANDLE;
    int actualRetVal = 0;
    int expectedRetVal = -1;
    HANDLE handle = INVALID_HANDLE_VALUE;

    actualRetVal = Write(handle, buffer);
    actual = GetLastError();
    Assert::AreEqual(expectedRetVal, actualRetVal);
    Assert::AreEqual(expected, actual);
}

I have checked my Write function and it does set the correct return value and last error but the latter is not retrieved in my test method. Even when I change the Write function to just set the error and return the problem occurs (and I call no other function before calling GetLastError in my test method):

int Write(HANDLE h, const char* buf)
{
    SetLastError(ERROR_INVALID_HANDLE);
    return -1;
}

Any idea how I can fix this? I assume there is a problem with C++/CLI because when I use my library outside of this testing scenario (pure C++) GetLastError works.

1
Can you provide the source code of the Write function?LukasT
@LukasT I added the simplest implementation possible that still produces the problem. I Think the problem has to do with C++/CLI because my code works when I don't involve it (i.e not running the test project).dbostream
So it boils down to the situation where you call SetLastError(VAL) and GetLastError returns 0 in c++-cli environment?LukasT
Yes, because it works when not using the testing project.dbostream

1 Answers

2
votes

Relying on GetLastError()/SetLastError() across the managed/unmanaged boundary is problematic.

When using P/Invoke and the DllImport attribute you can (must) set the SetLastError property to get access to the native error code on the managed side.

When using C++/CLI, however, the compiler handles all marshalling for you, and explicitly does not set that flag.

You can read some more details about it in this blog post. The gist of it is:

If you use DllImport explicitly in C++, the same rules apply as with C#. But when you call unmanaged APIs directly from managed C++ code, neither GetLastError nor Marshal.GetLastWin32Error will work reliably.

This is also covered at length in Chapter 9 of "Expert Visual C++/CLI" by Marcus Heege which is available on Google Books:

As mentioned before, for these native local functions, C++/CLI automatically generates P/Invoke metadata without the lasterror flag, because it is very uncommon to use the GetLastError value to communicate error codes within a project. However, the MSDN documentation on GetLastError allows you to use SetLastError and GetLastError for your own functions. Therefore, this optimization can theoretically cause wrong GetLastError values.

Basically, don't do it!

I would recommend to use (native) C++ exceptions to communicate errors between managed and unmanaged code. C++/CLI supports these very nicely. If you can't modify your Write() function directly, you could create a wrapper function on the unmanaged side which uses GetLastError() and then throws an exception if necessary.