找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2824

积分

0

好友

384

主题
发表于 前天 05:52 | 查看: 12| 回复: 0

原文中的接口定义可参考本系列的前一篇文章,我们直接来看在 C# 中如何定义。

接口定义

使用 DllImport 特性来声明对 C 动态库的调用,并将回调函数作为参数传递。

[DllImport("XXX.dll")]
public static extern IntPtr Init(string pcPayDeviceIP, int usTlsPort, OnPayResult onPayResult);

委托定义

回调函数在 C# 中通过委托来表示。为了确保与 C 代码的调用约定一致,必须使用 UnmanagedFunctionPointer 特性并指定 CallingConvention.Cdecl。这是 平台调用 中处理回调的关键步骤。

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnPayResult(IntPtr pstPayResult);

委托绑定接口及调用

定义一个委托实例,并将其传递给原生的 Init 函数。

OnPayResult onPayResult += new OnPayResult(OnPayResult);
Init(pcPayDeviceIP, usTlsPort, onPayResult);

接口定义

OnPayResult 方法内部,核心任务是将从 C 层传来的指针数据安全地转换为 C# 可用的结构。这里定义了一个抽象方法 PayResult 供子类实现具体业务逻辑。

protected abstract void PayResult(PayResult payResult);

private void OnPayResult(IntPtr pstPayResult)
{
    PayResultTmp st = (PayResultTmp)Marshal.PtrToStructure(pstPayResult, typeof(PayResultTmp));
    PayResult payResult = new PayResult();
    payResult.place = enPlace.SD_D_PLACE_BUTT;
    payResult.place = (enPlace)Marshal.ReadInt32(pstPayResult);
    payResult.errCode = PtrToUtf8String(st.errCode);
    payResult.errInfo = PtrToUtf8String(st.errInfo);
    payResult.seqId = PtrToUtf8String(st.seqId);
    payResult.merOrderId = PtrToUtf8String(st.merOrderId);
    payResult.srcReserve = PtrToUtf8String(st.srcReserve);
    payResult.attachedData = PtrToUtf8String(st.attachedData);
    payResult.totalAmount = PtrToUtf8String(st.totalAmount);
    payResult.couponAmount = PtrToUtf8String(st.couponAmount);
    payResult.payAmount = PtrToUtf8String(st.payAmount);
    payResult.payTime = PtrToUtf8String(st.payTime);
    payResult.orderCreateTime = PtrToUtf8String(st.orderCreateTime);
    payResult.status = PtrToUtf8String(st.status);
    PayResult(payResult);
}

由于回调函数传过来的是 C 语言申请的内存,其内存布局与 C# 的结构体可能不一致,因此必须通过 Marshal 类提供的方法进行显式转换。如果直接进行强制类型转换,极有可能引发内存访问异常,导致程序崩溃。这正是 C/C++ 与托管环境交互时需要特别注意的细节。

结构体定义

需要定义两个结构体。PayResultTmp 用于精确匹配 C 层的内存布局,其字符串字段均为 IntPtr 类型。而 PayResult 则是供 C# 业务逻辑使用的友好结构,字段为 string 类型。

[StructLayout(LayoutKind.Sequential)]
private struct PayResultTmp
{
    public enPlace place; //收单机构
    public IntPtr errCode; //错误码
    public IntPtr errInfo; //错误信息
    public IntPtr seqId; //平台流水号
    public IntPtr merOrderId; //商户订单号
    public IntPtr srcReserve; //请求系统预留字段
    public IntPtr attachedData; //商户附加数据
    public IntPtr totalAmount; //订单金额
    public IntPtr couponAmount; //优惠金额
    public IntPtr payAmount; //实付金额
    public IntPtr payTime; //支付时间
    public IntPtr orderCreateTime; //订单创建时间
    public IntPtr status; //交易状态
}

[StructLayout(LayoutKind.Sequential)]
public struct PayResult
{
    public enPlace place; //收单机构
    public string errCode; //错误码
    public string errInfo; //错误信息
    public string seqId; //平台流水号
    public string merOrderId; //商户订单号
    public string srcReserve; //请求系统预留字段
    public string attachedData; //商户附加数据
    public string totalAmount; //订单金额
    public string couponAmount; //优惠金额
    public string payAmount; //实付金额
    public string payTime; //支付时间
    public string orderCreateTime; //订单创建时间
    public string status; //交易状态
}

实现

最后,通过继承包含抽象方法 PayResult 的基类,并重写该方法,即可实现回调的具体业务逻辑。这样就将底层的 C 回调与上层的 C# 业务逻辑清晰地连接了起来。


protected override void PayResult(PayResult payResult)
{
    Console.WriteLine("errCode:{0}, errInfo:{1}", payResult.errCode, payResult.errInfo);
}



上一篇:Claude Code技能管理工具skill-monitor:监控Token成本与安全风险
下一篇:掌握Go语言控制语句:if快乐路径、for range陷阱与break跳转详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 17:08 , Processed in 0.634861 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表