Skip to content

Latest commit

History

History
380 lines (281 loc) · 10.2 KB

File metadata and controls

380 lines (281 loc) · 10.2 KB

Stdc Examples

Stdc - Helps porting C code to .NET

Rationale

Porting C code to .NET doesn't sound like being fun. And it is mostly for sure not funny... The mind breaking rewriting of printf formatting code to String.Format formats can cost a bunch of time and is error prone.

The same difficulties arise with scanf, really tedious to port such code.

Signal handling is another topic where one can loose quite some hours.

Stdc is a pure .NET library enabling a quick port of existing C code by emulating most of the C syntax in a very similar way, to not say in an identical manner. The code can be then refactored step by step further by removing the C functions. The Stdc libray enables a quick first shot so you have at least a running executable to work with.

Note that the Stdc library is written in pure .NET core. No call to native functions is made (no "cheating" with P/Invoke, to call the native C runtime methods is performed).

This can be important for portability between .NET on Linux and Windows for example.

A ported program should then run without recompilation under different platforms.

Who cares about Stdc?

If you...

  • have to quickly port a large portion of code and defer a clean .NET implementation to later on
  • are not used to the .NET framework and want to mimic the C API in .NET
  • wonder how to port some parts of the code but want a working result right now

Examples

Hello world

#include<stdio.h>voidmain(void) {
printf("Hello World!\n");
}
namespaceexamples;// a namespace to contain the codeusingstdc;// instead of #include ...publicclassHelloWorld:C{// in C# methods must be in a classpublicstaticvoidmain(){printf("Hello World!\n");}}

The main difference is that the code must be embedded in a class and a namespace. The functions turn consequently into public static methods (equivalent in .NET to C functions).

In further examples we will omit this necessary code parts to keep the focus on the real code changes. Includes are replaced by deriving the class from a base C class which automatically makes the functions from C available.

Care was taken to put all the C functions into separate files using the partial implementation feature of C#. That way in a real port, you can delete the portions of the C library that are not needed to save space or not pollute the namespace.

Therefore within a class derived from C, printf can be used as it would be the case in plain C. This first example is simplistic but it is there just to get a feeling for the basic principles in porting C to .NET.

Printing powers of 2 - printf()

#include<stdio.h>#defineN 16
voidmain(void) {
intn; /* The current exponent */intval=1; /* The current power of 2 */printf ("\t n \t 2^n\n");
printf ("\t================\n");
for (n=0; n<=N; n++) {
printf ("\t%3d \t %6d\n", n, val);
val=2*val;
}
}
usingstdc;publicclassPowerExample:C{privateconstintN=16;publicstaticvoidmain(){intn;// The current exponentintval=1;// The current power of 2printf("\t n \t 2^n\n");printf("\t================\n");for(n=0;n<=N;n++){printf("\t%3d \t %6d\n",n,val);val=2*val;}}}

Note that absolutely no change was needed to be made to the formatting strings.

Generating a file - FILE, fopen(), fclose(), putc()

#include<stdio.h>voidmain () {
FILE*pFile;
charc;
pFile=fopen ("alphabet.txt", "wt");
for (c='A' ; c <= 'Z' ; c++) {
putc (c , pFile);
}
fclose (pFile);
}
usingstdc;publicclassFileExample:C{publicstaticvoidmain(){FILEpFile;charc;pFile=fopen("alphabet.txt","wt");for(c='A';c<='Z';c++){putc(c,pFile);}fclose(pFile);}}

The fopen, fclose can be used exactly like in C, only the pointer symbol (*) disappears.

A small guessing game - rand(), scanf()

#include<stdio.h>#include<stdlib.h>#include<time.h>voidmain () {
intiSecret, iGuess;
srand ( time(NULL) );
iSecret=rand() % 10+1;
do {
printf ("Guess the number (1 to 10): ");
scanf ("%d",&iGuess);
if (iSecret<iGuess)
puts ("The secret number is lower");
elseif (iSecret>iGuess)
puts ("The secret number is higher");
} while (iSecret!=iGuess);
puts ("Congratulations!");
}
usingstdc;publicclassGuessExample:C{publicstaticvoidmain(){intiSecret;objectguess;intiGuess;srand(time(NULL));iSecret=rand()%10+1;do{printf("Guess the number (1 to 10): ");scanf("%d",outguess);// can we get rid of this ugly casting here...iGuess=(int)guess;if(iSecret<iGuess)puts("The secret number is lower");elseif(iSecret>iGuess)puts("The secret number is higher");}while(iSecret!=iGuess);puts("Congratulations!");}}

The only ugly step needed here is the need for a cast, as scanf implementation is only able to handle object's as out parameters. The API does not provide a solution for this dilemma right now.

Quick Sort an array of ints, step by step refactoring - qsort()

#include<stdio.h>#include<stdlib.h>intvalues[] = { 40, 10, 100, 90, 20, 25 };
intcompare (constvoid*a, constvoid*b) {
return ( *(int*)a-*(int*)b );
}
voidmain () {
intn;
qsort (values, 6, sizeof(int), compare);
for (n=0; n<6; n++)
printf ("%d ",values[n]);
}
usingstdc;publicclassSortExample:C{publicstaticint[]values=newint[]{40,10,100,90,20,25};publicstaticintcompare(inta,intb){returna-b;}publicstaticvoidmain(){intn;qsort(values,6,sizeof(int),compare);for(n=0;n<6;n++)printf("%d ",values[n]);}}

Second step, refactoring, getting rid of the C-like syntax and use .NET strengths.

We transformed the for loop into a foreach loop, making the use of the magic number '6' superfluous.

usingstdc;publicclassSortExample:C{publicstaticint[]values=newint[]{40,10,100,90,20,25};publicstaticintcompare(inta,intb){returna-b;}publicstaticvoidmain(){qsort(values,compare);foreach(intvinvalues)printf("%d ",v);}}

Third step: get rid of all C functions and replace them with their .NET equivalents. After that there is no need anymore to use the base class C and the code is fully ported.

usingSystem;publicclassSortExample{publicstaticint[]values=newint[]{40,10,100,90,20,25};publicstaticintcompare(inta,intb){returna-b;}publicstaticvoidmain(){Array.Sort(values,compare);foreach(intvinvalues)Console.Write("{0} ",v);}}

Did you notice? From the first step on, the C# compare method didn't need any casts unlike the C version. Thanks to the use of generics, the code readability is greaty improved. This also shows the basic steps in refactoring the C code. Stdc just helps you to keep a testable running version between successive steps of refactoring.

Remarks

In order to provide the advanced emulation functionality (like signals and atexit support, argc, argv emulation), the library needs to control the code to be run. There is a trampoline from the .NET Main method to the ported main C function.

This should be used like this:

namespaceexamples;usingstdc;classProgram:C{staticvoidMain(string[]args){// use one of theseRunVMain(args,CProgram.main);// if the main is returning nothing (void)RunIMain(args,CProgram.main);// if the main is returning an int}}

The signature of the main function is one of:

  • int main(int argc, string[] argv)
  • int main()
  • void main(int argc, string[] argv)
  • void main()

The .NET arguments do not contain the program name unlike in C where argv[0] contains the name of the executable. To enable to reuse code from C that expects this behavior, you must use the C.RunI/VMain() function. The RunMain() function also provides an environment where the signal() and raise() C functions can be used.

Further examples

Using atexit - atexit()

#include<stdio.h>#include<stdlib.h>voidatexit_handler1 (void) {
puts ("handler 1");
}
voidatexit_handler2 (void) {
puts ("handler 1");
}
voidmain () {
atexit (atexit_handler1);
atexit (atexit_handler2);
puts ("atexit handlers should be "+"called in reverse order 2 and then 1!");
}
namespaceexamples;usingstdc;publicclassProgram:C{publicstaticvoidatexit_handler1(){puts("handler 1");}publicstaticvoidatexit_handler2(){puts("handler 2");}publicstaticvoidmain(){atexit(atexit_handler1);atexit(atexit_handler2);puts("atexit handlers should be "+"called in reverse order 2 and then 1!");}staticvoidMain(string[]args){RunVMain(args,main);// trampoline}}

The full code is provided for this example, to demonstrate how to let the RunMain() method call the ported main() function. RunMain() calls main() after initializing an environment where the signals can work properly. The behavior expected from C regarding the order in which the handlers are called is implemented correctly: the handlers are called in reverse order or registration.