【问题标题】:Simplified C-macro-like function calls in C#在 C# 中简化了类似 C 宏的函数调用
【发布时间】:2014-08-27 11:45:32
【问题描述】:

我正在为 C dll 编写一个包装器。为 C# 应用程序包装了各种 C 函数。现在考虑下面包装器的一些简化部分。

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

如您所见,包装器调用了各种 C 函数。所有 C 函数都返回整数作为状态代码(一个错误代码),如果 C 函数失败,包装器必须检查此返回代码并抛出应用程序定义的异常。对于所有 C 函数调用,这必须以完全相同的方式完成。只是函数名和参数改变了,但 3 行调用块是一样的。作为复制/粘贴的 3 行函数调用块,这种形式看起来非常便宜。

在C#中,有没有办法将“调用、检查返回码、抛出异常”循环简化封装成更简单紧凑的方式?

供参考(实际上这是我想做的以简化调用);在 C/C++ 中,我们可以定义一个像这样的宏:

#define SAFE_CALL(call) do{ if((last_ret = (ErrorCode)call) != OK) throw AppException(last_ret); }while(0)

然后这样调用:

SAFE_CALL(ClibDllFunc1(filename, &datap));
SAFE_CALL(ClibDllFunc2(datap));
SAFE_CALL(ClibDllFunc3(datap, &sz));

【问题讨论】:

  • 应该能够创建一个将 ClibDllFunc 作为 arg 的函数 SAFE_CALL(返回一个 int 或错误代码)对吗?
  • @NWard 根据下面接受的答案,我做了完全相同的事情。它就像魔术一样工作。它完成了这项工作,但我不明白如何:)

标签: c# macros


【解决方案1】:

编辑

重读你的问题,我回答错了问题。你真正想要的是一个函数 CheckErrorCode,它接受一个 int,然后简单地传递本机调用的结果。

/// <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));
}

原文(如何用 lambda 模拟宏)

C# 中的 lambda 语法非常简洁,这意味着您可以像使用宏一样使用 Func&lt;T&gt;。试试这样的:

/// <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);
    }
}

然后你可以调用它:

public void getSize(out int sz)
{
    // drawback: compiler can't check that sz is always written.
    sz = 0;
    CheckErrorCode(() => ClibDllFunc3(datap, out sz));
}

lambda 创建了所谓的closure。这是一种将调用 ClibDllFunc3(特定于此函数)的逻辑从处理其结果的逻辑(这是所有DLL 函数的标准)中拉出来的一种方法。与许多闭包不同,这个闭包是立即调用的。

【讨论】:

  • 嗯,这比我想象的要简单。令我惊讶的是,它也适用于“out”参数。我仍然不完全理解它是如何工作的。你能简单解释一下 lambda 表达式行发生了什么吗?我会把它读成“没有参数'去'ClibDllFunc3(datap,out sz)”。我没有想到这一点,因为我认为“datap”和“out sz”应该是“去”ClibDllFunc3 之类的。
  • 在回答这些问题时,我决定编辑我的答案。在编辑我的答案时,我意识到我回答了错误的问题。您不需要 lambda 或宏,只需一个简单的方法来检查结果并抛出异常。
猜你喜欢
  • 2020-01-09
  • 1970-01-01
  • 2022-11-24
  • 1970-01-01
  • 1970-01-01
  • 2021-06-28
  • 1970-01-01
  • 2016-01-19
  • 2016-10-28
相关资源
最近更新 更多