Reputation: 181
I'm writing a wrapper for a C dll. Various C functions are wrapped for C# applicatons. Now consider some simplified part of the wrapper below.
public enum ErrorCode
{
OK = 0,
...
...
}
public class AppException: ApplicationException
{
public AppException(ErrorCode errorCode) : base()
{
error = errorCode;
}
public ErrorCode error { get; private set; }
}
public class A
{
public ErrorCode last_ret;
private IntPtr datap;
public A(string filename)
{
last_ret = (ErrorCode)ClibDllFunc1(filename, out datap);
if (last_ret != ErrorCode.OK)
throw new AppException(last_ret);
// go on processing
last_ret = (ErrorCode)ClibDllFunc2(datap);
if (last_ret != ErrorCode.OK)
throw new AppException(last_ret);
}
public void getSize(out int sz)
{
last_ret = (ErrorCode)ClibDllFunc3(datap, out sz);
if (last_ret != ErrorCode.OK)
throw new AppException(last_ret);
}
// ...
// many functions like these, all working by calling c/c++ dll functions
// with different number and types of parameters
}
[DllImport("clibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
static extern internal int ClibDllFunc1(string filename, out IntPtr data);
// ... other C function declarations follow similarly
As you can see, the wrapper calls various C functions. All C functions return integer as the status code (an ErrorCode), and the wrapper has to check this return code and throw an application defined exception if the C function fails. This has to be done exactly the same way for all C function calls. Only the function name and parameters change but the 3 line calling-block is the same. It looks really cheap in this form as copy/pasted 3-line blocks of function calls.
In C#, is there a way to simplify and encapsulate the "call, check return code, throw exception" cycle in a simpler and more compact way?
For reference (actually this is what I'd like to do to simplify the calls); in C/C++ we can define a macro like this one:
#define SAFE_CALL(call) do{ if((last_ret = (ErrorCode)call) != OK) throw AppException(last_ret); }while(0)
and call like this:
SAFE_CALL(ClibDllFunc1(filename, &datap));
SAFE_CALL(ClibDllFunc2(datap));
SAFE_CALL(ClibDllFunc3(datap, &sz));
Upvotes: 2
Views: 764
Reputation: 6957
Rereading your problem, I answered the wrong question. What you really want is a function CheckErrorCode that takes an int, and then simply pass the result of the native call.
/// <summary>
/// Takes the result code from invoking a native function. If the result is
/// not ErrorCode.OK, throws an AppException with that error code.
/// </summary>
/// <param name="returnCodeInt">
/// The return code of a native method call, as an integer.
/// Will be cast to ErrorCode.
/// </param>
private static void CheckErrorCode(int returnCodeInt)
{
ErrorCode returnCode = (ErrorCode)returnCodeInt;
if(returnCode != ErrorCode.OK)
{
throw new AppException(returnCode);
}
}
public void getSize(out int sz)
{
CheckErrorCode(ClibDllFunc3(datap, out sz));
}
The fact that lambda syntax in C# is so terse means you can use a Func<T>
in a similar way to macros. Try something like this:
/// <summary>
/// Calls a function's native implementation, then checks if the error code
/// is not ErrorCode.Ok. If it is, throws an AppException.
/// </summary>
/// <param name="nativeCall">
/// A function that returns the status code as an int.
/// </param>
private static void CheckErrorCode(Func<int> nativeCall)
{
var returnCode = (ErrorCode)nativeCall();
if(returnCode != ErrorCode.OK)
{
throw new AppException(returnCode);
}
}
You can then call it with:
public void getSize(out int sz)
{
// drawback: compiler can't check that sz is always written.
sz = 0;
CheckErrorCode(() => ClibDllFunc3(datap, out sz));
}
The lambda creates what's known as a closure. This is a way to pull the logic of calling ClibDllFunc3 (specific to this function) away from the logic of handling its results (which is standard across all your DLL functions). Unlike many closures, this one is called immediately.
Upvotes: 2