C# Support
Although applications written in C# are running in the .NET environment, it is possible to access the Kithara driver. To run a C# project using the Kithara driver properly some preparations have to be made.
Other Topics
- Realtime with C# - How to run native code on the kernel level with C#?
Preparing a Visual Studio C# project
A wrapper class is provided for accessing the Kithara driver from C#. The wrapper class contains constants, structures and functions to work with the Kithara software.
The Kxxxx.cs contains the wrapper class and can be found in the subdirectoy dev in the installation root. xxxx stands for the version of the Kithara kernel you purchased. For further information on files and directories installed with the Kithara software have a look at the documentation chapter 2.
Add wrapper class to project
To use the wrapper class in your project just add Kxxxx.cs to it.
The dev directory also contains a file named Kithara.cs. This is a proxy class which helps accessing the wrapper class. See the comment in Kithara.cs for further information.
C# applications accessing the Kithara driver contains unsafe contexts so it is necessary to compile a C# project using the Kithara driver with the /unsafe flag set.
How to set /unsafe flag in Visual Studio
- Open your project in Visual Studio
- Right click on your project file in the project explorer, project properties will open
- Select the build tab at the left hand side
- Check “Allow unsafe code”
Garbage Collection (GC)
Keep in mind that .NET's GC may move variables in memory while the Kithara driver use pointers to them. Therefore it is necessary to ensure that a variable won't move while the Kithara driver (which is unmanaged code) works with those pointers.
First of all, which variables are moveable?
In general all variables which are stored on the heap memory are moveable.
The most important cases are:
- Instances of reference types
- Every (static) member of a class, even if it is a value type
- Parameters passed by ref/out
The following cases can be considered as fixed variables:
- Local variable or value parameter as long as not captured by anonymous methods
- Member of structure that can be considered to be fixed
- Variables and its members you can access via a pointer
See ECMA C# language specification chapter 27.3 for further information on fixed and moveable variables.
Fixed variable for single function invocation
A variable has to be fixed as long as the pointer to it is used by the Kithara driver. If calling a function that only works with the pointer when it is invoked, you only have to pass a fixed variable for this one invocation.
For example all Kithara functions that expect a pointer to a handle type use this pointer only for the time the function executes. Also KS_createSharedMem expect a pointer to a variable to which it can write the address of the located memory.
After returning to your code sequence there is no copy of the pointer saved by the Kithara driver, so it is not necessary to fix the variable any longer.
Have a look at the following example:
public unsafe class Class1 : Kithara
{
public Handle hEvent_;
public void f() {
// [...]
Handle hEvent;
KS_createEvent(&hEvent, "MyEvent", 0);
hEvent_ = hEvent;
// [...]
}
}
Because hEvent_ is a member of Class1, it is a moveable variable and its address cannot be obtained. Thats why we temporarily use a local variable to store the handle. Afterwards the content is written to the class member. It is even impossible to obtain the address from member hEvent_—the compiler would raise an error CS0212. An alternative would be to use
hEvent_ in a fixed-statement.
Fixed variable for a period of time
An easy and comfortable way to store data in a block of memory that cannot be moved by the GC is to create Shared Memory. Although it might not be used for data exchange between application and kernel level it is safe and comfortable to use.
In the following example a callback function receives a pointer to user data.
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;
}
// [...]
The pointer to the user data has to stay valid all the time the callback function could be invoked by Kithara driver. Therefore we create Shared Memory elsewhere in the class.
// [...]
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;
}
// [...]
You can even create the Shared Memory in another class. The important thing is that a valid pointer is available when creating the callback.
// [...]
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
// [...]
}
}
Whenever the callback function will be invoked the pointer is valid. So it is necessary that the Shared Memory won't be freed until the callback is removed.
public void removeCallBack() {
int ksError;
ksError = KS_removeCallBack(hCallBack_);
// Don't free Shared Memory while CallBack is active
ksError = KS_freeSharedMem(pAppData_);
}
}
Again the alternative would be the fixed-statement, which is not as flexible as Shared Memory (see below).
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
}
}
As long as callBackFunction could be called, leaving fixed context would lead to unpredictable program behaviour, because data_ is a member of Class2 and therefore a moveable variable. So, you first had to remove the callback by using KS_removeCallback and then leave the fixed context.
Delegates and GC
Does the GC collect or rellocate my delegates so they doesn't work with Kithara callbacks? While keeping a valid handle to the delegate in your application the Kithara driver is able to invoke your callback function without problems. While marshalling the delegate the Common Language Runtime (CLR) ensures that a thunk, connected with your delegate, is stored on the unmanaged heap, so it can't be moved. The thunk stays active while the delegate handle is not removed by the GC. There is a german MSDN article that deals with marshalling and delegate lifetime.
Marshalling
To pass parameters to Kithara functions properly it might be necessary to control the .NET marshaller with attributes. In some cases attributes need to be declared in the type definition of the parameter to pass.
For example you want to pass a constant array to unmanaged code. Initializing an array with a given number of elements is not enough for the marshaller. You have to tell the marshaller which size the array has. Use the MarshalAsAttribute in the type definition:
// C#
public unsafe struct KSDeviceInfo{
[...]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 80)]
public char[] pDeviceName;
[...]
}
The constructor of the MarshallAsAttribute needs a value of UnmanagedType enum which is the type as which the parameter is passed to unmanaged code.SizeConst tells the marshaller the count of elements.
The import from the DLL looks like that:
// C#
[DllImport("Kxxx.dll", CallingConvention = CallingConvention.StdCall)]
public static extern unsafe int KS_getDeviceInfo(
string deviceName, ref KSDeviceInfo pDeviceInfo, int flags);
Pay attention that the parameter pDeviceInfo is not a pointer but passed by ref. A function call could be something like this:
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
That is a result of declaring an array in KSDeviceInfo. In C# it is impossible to get a pointer from a reference type and an array is a reference type. So it is impossible to get a pointer from a structure holding an array. So the ref keyword is used instead to pass the parameter as a pointer.
So the data is marshalled to the callee and marshalled back to the caller. That means the pointer is not valid for the unmanaged code after the function has returned.
