I am writing a wrapper around a rather large unmanaged API. Almost every imported method returns a common error code when it fails. While I'm doing this:
ErrorCode result = Api.Method(); if (result != ErrorCode.SUCCESS) { throw Helper.ErrorToException(result); }
It works great. The problem is that I have so many unmanaged method calls that it becomes extremely frustrating and repetitive. So, I tried switching to this:
public static void ApiCall(Func<ErrorCode> apiMethod) { ErrorCode result = apiMethod(); if (result != ErrorCode.SUCCESS) { throw Helper.ErrorToException(result); } }
Lets me cut all these calls to one line:
Helper.ApiCall(() => Api.Method());
However, there are two problems with this. Firstly, if my unmanaged method uses out parameters, I must first initialize the local variables, because the method call is actually in the delegate. I would just declare the assignment out without initializing it.
Secondly, if an exception is thrown, I really don't know where it came from. The debugger goes into the ApiCall method, and the stack trace shows only the method that contains the ApiCall call, not the delegate itself. Since I can have many API calls in one method, this makes debugging difficult.
Then I thought about using PostSharp to wrap all unmanaged calls by checking the error code, but I'm not sure how this could be done using extern methods. If it ends just by creating a wrapper method for each of them, then I would have the same problem with the exception as with the ApiCall method, right? Also, how would the debugger know how to show me the site of the generated exception in my code if it exists only in the assembly?
Then I tried to implement a custom marshaler that would intercept the return value of the API calls and check the error code there. Unfortunately, you cannot use your own marshaler to return values. But I think it would be a really clean solution if it worked.
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ApiMethod))] public static extern ErrorCode Method();
Now I have absolutely no ideas. What are some other ways I could handle this?