KRTS使用C#语言及环境配置

C# 支持



尽管用 C# 编写的应用程序在 .NET 环境中运行,但可以访问 Kithara 驱动程序。若要使用 Kithara 驱动程序正确运行 C# 项目,必须进行一些准备工作。

其他主题

准备 Visual Studio C# 项目

提供了一个包装类,用于从 C# 访问 Kithara 驱动程序。包装类包含用于 Kithara 软件的常量、结构和函数。该Kxxxx.cs包含包装类,可以在安装根目录的子目录 dev 中找到。xxxx 代表您购买的 Kithara 内核版本。有关随 Kithara 软件一起安装的文件和目录的更多信息,请查看文档第 2 章。

将包装类添加到项目中

要在项目中使用包装类,只需向其添加Kxxxx.cs即可。dev 目录还包含一个名为Kithara.cs的文件。这是一个代理类,有助于访问包装类。有关详细信息,请参阅Kithara.cs中的注释。

访问 Kithara 驱动程序的 C# 应用程序包含 unsafe 上下文,因此必须使用设置了 /unsafe 标志的 Kithara 驱动程序编译 C# 项目。

如何在 Visual Studio 中设置 /unsafe 标志

  1. 在 Visual Studio 中打开项目

  2. 在项目资源管理器中右键单击您的项目文件,将打开项目属性

  3. 选择左侧的构建选项卡

  4. 勾选允许不安全代码

在这里插入图片描述

垃圾回收 (GC)

请记住.NET 的 GC 可能会在内存中移动变量,而 Kithara 驱动程序使用指向它们的指针。因此,有必要确保在 Kithara 驱动程序(非托管代码)使用这些指针时变量不会移动。首先,哪些变量是可移动的?
通常,存储在堆内存上的所有变量都是可移动的。
最重要的案例是:

  • 引用类型的实例

  • 类的每个(静态)成员,即使它是一个值类型

  • ref/out 传递的参数

以下情况可以视为固定变量:

  • 局部变量或值参数,只要不被匿名方法捕获

  • 可以认为是固定的结构构件

  • 您可以通过指针访问的变量及其成员

有关固定变量和可移动变量的更多信息,请参见 ECMA C# 语言规范 第 27.3 章。

修复了单函数调用的变量

只要 Kithara 驱动程序使用指向该变量的指针,就必须固定该变量。如果调用的函数仅在调用指针时才使用指针,则只需为这一次调用传递一个固定变量。例如,所有需要指向句柄类型的指针的 Kithara 函数仅在函数执行时使用此指针。此外,KS_createSharedMem需要指向变量的指针,它可以将所定位内存的地址写入该变量。返回到代码序列后,Kithara 驱动程序没有保存指针的副本,因此无需再修复变量。
请看以下示例:

public unsafe class Class1 : Kithara
{
  public Handle hEvent_;

  public void f() {
    
    // [...]
    Handle hEvent;
    KS_createEvent(&hEvent, `MyEvent`, 0);
    hEvent_ = hEvent;
    // [...]

  }
}

因为 hEvent_ 是 Class1 的成员,所以它是一个可移动的变量,无法获取其地址。这就是为什么我们临时使用局部变量来存储句柄的原因。之后,将内容写给班级成员。甚至不可能从成员 hEvent 获取地址 — 编译器会引发错误 CS0212
另一种方法是在 fixed语句中使用 hEvent

一段时间内固定变量

将数据存储在 GC 无法移动的内存块中的一种简单而舒适的方法是创建共享内存**。虽然它可能不用于应用程序和内核级之间的数据交换,但它使用起来既安全又舒适。

在以下示例中,回调函数接收指向用户数据的指针。

public unsafe class CallBackClass : Kithara {

  public struct TestData {
    public int counter_;
    public char value_;
  }

  // Class members
  public TestData* pAppData_;
  public TestData* pSysData_;
  public Handle hCallBack_;

  // The callback function
  public int callBackFunction(void* pArgs, void* pContext) {
    TestData* pData = (TestData*)pArgs;
    ++pData->counter_;
    return KS_OK;
  }
  // [...]

指向用户数据的指针必须始终保持有效,只要 Kithara 驱动程序可以调用回调函数。因此,我们在类的其他地方创建共享内存。

  // [...]
  public void createMemory() {
    int ksError;
    // Fixed variables for pointers
    TestData* pApp,pSys;

    ksError = KS_createSharedMem(
              (void**)&pApp,           // Application pointer
              (void**)&pSys,           // System pointer
               `MySharedMem`,          // Name of shared memory
               (uint)sizeof(TestData), // Size of memory to allocate 
               0);                     // Flags
    // Write pointers to class members
    pAppData_ = pApp;
    pSysData_ = pSys;
  }
// [...]

您甚至可以在另一个类中创建 共享内存。重要的是,在创建回调时,有效的指针可用。

  // [...]
  public void createCallBack() {

    int ksError;
    // Fixed variable for temp. callback handle
    Handle hCallBack;
    CallBackRoutine callBack = new CallBackRoutine(callBackFunction);

    if(pAppData_ != null) {
      ksError = KS_createCallBack(
                  &hCallBack, callBack, pAppData_, KSF_USER_EXEC, 16);
      // Write callback handle to class member
      hCallBack_ = hCallBack;
      // For example use it as timer callback
      // [...]
    } 
  }

每当调用回调函数时,指针都是有效的。因此,在删除回调之前,共享内存不会被释放。

  public void removeCallBack() {

    int ksError;

    ksError = KS_removeCallBack(hCallBack_);

    // Don't free shared memory while CallBack is active
    ksError = KS_freeSharedMem(pAppData_);
  }
}

同样,替代方案是 fixed语句,它不如共享内存灵活(见下文)。

public unsafe class Class2 : Kithara {
  // [...]
  public void f() {

    int ksError;
    Handle hEvent;
    CallBackRoutine callBack = new CallBackRoutine(callBackFunction);

    fixed (TestData* pData = &data_) {

      ksError = KS_createCallBack(
                  &hEvent, callBack, pData, KSF_USER_EXEC, 16);

      // For example use it as timer callback
      // [...]
      // If timer not needed anymore you can remove it
      // But before leaving fixed context the callBack should be removed

      ksError = KS_removeCallBack(hEvent);
      // Now there is no copy of the pointer pData the Kithara driver
      // could use
    } // so we can leave fixed context

  }
}

只要可以调用 callBackFunction,离开固定上下文将导致不可预测的程序行为,因为 data_ 是 Class2 的成员,因此是一个可移动的变量。因此,您首先必须使用KS_removeCallback删除回调,然后离开固定上下文。

委托和 GC

GC 是否收集或重新分配我的委托,以便它们不使用 Kithara 创建回调?在应用程序中保留委托的有效句柄的同时,Kithara 驱动程序能够毫无问题地调用 回调函数 。封送委托时,Common语言Runtime (CLR) 确保与委托连接的 thunk 存储在非托管堆中,因此无法移动它。当 GC 未删除委托句柄时,thunk 将保持活动状态。有一篇德语 MSDN 文章涉及封送和 委托生存期

编组

若要正确地将参数传递给 Kithara 函数,可能需要使用属性控制 .NET 封送器。在某些情况下,需要在要传递的参数的类型定义中声明属性。

例如,要将常量数组传递给非托管代码。对于编组器来说,使用给定数量的元素初始化数组是不够的。您必须告诉编组员阵列的大小。在类型定义中使用 MarshalAsAttribute

// C#
  public unsafe struct KSDeviceInfo{
    [...]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)]
    public char[] pDeviceName;
    [...]
  }

MarshallAsAttribute 的构造函数需要 UnmanagedType 枚举的值,该值是将参数传递给非托管代码的类型。
SizeConst 告诉编组员元素的计数。
从 DLL 导入如下所示:

// C#
[DllImport(`Kxxx.dll`, CallingConvention = CallingConvention.StdCall)]
public static extern unsafe int KS_getDeviceInfo(
    string deviceName, ref KSDeviceInfo pDeviceInfo, int flags);

请注意,参数 pDeviceInfo 不是指针,而是由 ref 传递。
函数调用可以如下所示:

KSDeviceInfo deviceInfo = new KSDeviceInfo();
deviceInfo.structSize = (uint)Marshal.SizeOf(deviceInfo);

ksError = KS_getDeviceInfo(
              devName,             // Device name
              ref deviceInfo,      // KSDeviceInfo struct to write to by ref
              0);                  // Flags, here 0

这是在KSDeviceInfo中声明数组的结果。在 C# 中,不可能从引用类型获取指针,而数组是引用类型。因此,不可能从保存数组的结构中获取指针。因此,使用 ref 关键字代替将参数作为指针传递。因此,数据被编送到被叫方,然后编组回调用方。这意味着在函数返回后,指针对非托管代码无效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值