Introduction
Often, for those who program in C and are starting to program in C # / VB or some other compiled language for .NET, they want or even need to call function codes in C we write.
And whenever I researched on the Internet, or speak to use Visual C++ with the /
clr
in the compiler, or use pInvoke
to call the C function.
So talking to my friend David, creator of OrangeC/C++ compiler, I gave him the idea to create a new back-end for your compiler, which generates CLR code, therefore, there was no C open-source compiler which generated CLR code.
After separating the compiler back-end that generates x86 code, the development of the new back-end was started after a number of fixes and implementations, we have succeeded to the compiler Sqlite3 and use the DLL compiled with a C# code.
Articles about OrangeC to .NET
Links of the C/C++ Compiler and the Back-end to MSIL
You can follow the development of the OrangeC/C++ compiler or make contributions in the project on the following links:
Building the Orangec Compiler and the Orangec for Msil
To build the Orange C compiler for MSIL, you need to download the complete code of OrangeC compiler, you can get the source in:
or:
To build the source, it is necessary to have one of these compilers:
- MinGW
- CLang
- Visual C/C++ 15
- OrangeC compiler
After the download and the extraction of all files, open the CMD
navigate into folder C:\orangec\src, type, config.bat, after the execution of config.bat, type:
This will build all the orangec compiler.
After the execution of these commands, you will have the occil.exe.
Using the OrangeC Compiler to .NET Executables
To use the compiler, you simply download it at the link provided in this article, extract all the zip content in C:\ folder, open the CMD
and navigate in to C:\OrangeC folder, after you are in the occil folder, type, config.bat, when running config.bat file, a new environment variable will be created in your CMD
context, so the compiler can locate the include folder and other libs.
Let's Create A Small Example
Create a C file with name, "float_to_ieee754.c" and put this code in your C file:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *strrev_t(char *str)
{
char *p1, *p2;
if (!str || !*str)
return str;
for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2)
{
*p1 ^= *p2;
*p2 ^= *p1;
*p1 ^= *p2;
}
return str;
}
void floatToBinary(float f, char *str, int numeroDeBits)
{
int i = 0;
int strIndex = 0;
union
{
float f;
unsigned int i;
}u;
u.f = f;
memset(str, '0', numeroDeBits);
for (i = 0; i < numeroDeBits; i++)
{
str[strIndex++] = (u.i & (1 << 0)) ? '1' : '0';
u.i >>= 1;
}
str[strIndex] = '\0';
str = strrev_t(str);
}
int main()
{
float input = 0.0;
const int numeroDeBits = 32;
char *str = (char*)malloc(sizeof(char) * numeroDeBits);
printf("Type a float number to convert to binary: ");
scanf("%f", &input);
floatToBinary(input, str, numeroDeBits);
printf("%s\n", str);
if(str != NULL)
free(str);
return 0;
}
P.S.: It's necessary to copy the following DLLs from the bin folder to the same folder where the source code is:
- lscrtlil.dll
- lsmsilcrtl.dll
- occmsil.dll
Now, to build this code, you type in CMD:
occil /NProgram.FloatToBinary /9 float_to_ieee754.c
Explanation of each argument:
/N<NameSpace>.<Class>
/9: C99 mode
After executing this command, the compiler will generate the executable referring to your code.
To see get the IL code that refers to generated code, you just need to add the parameter /S
in command line:
occil /S /NProgram.FloatToBinary /9 float_to_ieee754.c
Generate IL code:
.corflags 3
.assembly float_to_ieee754{
}
.assembly extern mscorlib{
}
.assembly extern lsmsilcrtl{
.ver 1:0:0:0
.publickeytoken = (bc 9b 11 12 35 64 2d 7d )
}
.field public static uint16 * '_pctype'
.field public static void * '__stdin'
.field public static void * '__stdout'
.field public static void * '__stderr'
.namespace 'Program' {
.class public ansi sealed 'FloatToBinary'
{.field private static valuetype 'Program.FloatToBinaryøint8[]' 'L_1' at $L_1
.data $L_1 = bytearray (
44 69 67 69 74 65 20 75
6d 20 6e 75 6d 65 72 6f
20 66 6c 6f 61 74 3a 20
00 )
.field private static valuetype 'Program.FloatToBinaryøint8[]' 'L_2' at $L_2
.data $L_2 = bytearray (
25 66 00 )
.field private static valuetype 'Program.FloatToBinaryøint8[]' 'L_3' at $L_3
.data $L_3 = bytearray (
25 73 0a 00 )
.method public static hidebysig int32 'main'(int32'argc', void *'argv') cil managed{
.locals (
[0] int8 * 'str/0',
[1] float32 'input/1'
)
.maxstack 3
L_4:
ldloca.s 'input/1'
ldc.r4 0
stind.r4
ldloca.s 'str/0'
ldc.i4.s 32
conv.u4
call void * [lsmsilcrtl]lsmsilcrtl.rtl::malloc(uint32)
stind.i4
ldsflda valuetype 'Program.FloatToBinaryøint8[]' Program.FloatToBinary::'L_1'
call vararg int32 'printf'(void *, ...)
pop
ldsflda valuetype 'Program.FloatToBinaryøint8[]' Program.FloatToBinary::'L_2'
ldloca.s 'input/1'
call vararg int32 'scanf'(void *, ..., void *)
pop
ldloc.1
ldloc.0
ldc.i4.s 32
call void Program.FloatToBinary::'floatToBinary'(float32, int8 *, int32)
ldsflda valuetype 'Program.FloatToBinaryøint8[]' Program.FloatToBinary::'L_3'
ldloc.0
call vararg int32 'printf'(void *, ..., void *)
pop
ldloc.0
brfalse.s L_7
ldloc.0
call void [lsmsilcrtl]lsmsilcrtl.rtl::free(void *)
L_7:
ldc.i4.0
L_5:
ret
}
.method public static hidebysig int8 * 'strrev_t'(int8 *'str') cil managed{
.locals (
[0] int8 * 'p1/0',
[1] int8 * 'p2/1'
)
.maxstack 3
L_17:
ldarg.0
brfalse.s L_37
ldarg.0
ldind.i1
brtrue.s L_20
L_37:
ldarg.0
br.s L_18
L_20:
ldarg.0
stloc.0
ldarg.0
call uint32 'strlen'(void *)
ldarg.0
add
ldc.i4.1
sub
stloc.1
br.s L_26
L_25:
ldloc.0
ldloc.0
ldind.i1
ldloc.1
ldind.i1
xor
stind.i1
ldloc.1
ldloc.1
ldind.i1
ldloc.0
ldind.i1
xor
stind.i1
ldloc.0
ldloc.0
ldind.i1
ldloc.1
ldind.i1
xor
stind.i1
L_28:
ldloc.0
ldc.i4.1
add
stloc.0
ldloc.1
ldc.i4.1
sub
stloc.1
L_26:
ldloc.1
ldloc.0
bgt.s L_25
L_27:
ldarg.0
L_18:
ret
}
.method public static hidebysig void 'floatToBinary'
(float32'f', int8 *'str', int32'numeroDeBits') cil managed{
.locals (
[0] int32 'i/0',
[1] int32 'strIndex/1',
[2] valuetype 'Program.FloatToBinaryø__anontype_2486130647_0' 'u/2'
)
.maxstack 3
L_41:
ldloca.s 'i/0'
ldc.i4.0
stind.i4
ldloca.s 'strIndex/1'
ldc.i4.0
stind.i4
ldloca.s 'u/2'
ldc.i4.0
add
ldarg.0
stind.r4
ldarg.1
ldc.i4.s 48
ldarg.2
conv.u4
call void * 'memset'(void *, int32, uint32)
pop
ldc.i4.0
stloc.0
br.s L_45
L_44:
ldloc.1
ldarg.1
add
ldloc.1
ldc.i4.1
add
stloc.1
ldloca.s 'u/2'
ldc.i4.0
add
ldind.u4
ldc.i4.1
and
brfalse.s L_56
ldc.i4.s 49
br.s L_57
L_56:
ldc.i4.s 48
L_57:
conv.i1
stind.i1
ldloca.s 'u/2'
ldc.i4.0
add
ldloca.s 'u/2'
ldc.i4.0
add
ldind.u4
ldc.i4.1
shr.un
stind.i4
L_47:
ldloc.0
ldc.i4.1
add
stloc.0
L_45:
ldloc.0
ldarg.2
blt.s L_44
L_46:
ldloc.1
ldarg.1
add
ldc.i4.0
stind.i1
ldarg.1
call int8 * Program.FloatToBinary::'strrev_t'(int8 *)
starg.s 'str'
L_42:
ret
}
.method private static hidebysig void '$Main'() cil managed{
.locals (
[0] int32 'argc/0',
[1] void * 'argv/1',
[2] void * 'environ/2',
[3] void * 'newmode/3'
)
.entrypoint
.maxstack 5
call uint16 * '__pctype_func'()
stsfld uint16 * '_pctype'
call void * '__iob_func'()
dup
stsfld void * '__stdin'
dup
ldc.i4.s 32
add
stsfld void * '__stdout'
ldc.i4.s 64
add
stsfld void * '__stderr'
ldloca.s 'argc/0'
ldloca.s 'argv/1'
ldloca.s 'environ/2'
ldc.i4.0
ldloca.s 'newmode/3'
call void '__getmainargs'(void *, void *, void *, int32, void *)
ldloc.0
ldloc.1
call int32 Program.FloatToBinary::'main'(int32, void *)
call void 'exit'(int32)
ret
}
.method private static hidebysig int32 * '__GetErrno'() cil managed{
.maxstack 1
call int32 * '_errno'()
ret
}
.class nested public ansi sealed value sequential '__file2' {
.pack 4 .size 16
}
.class nested public ansi sealed value sequential '__file__' {
.pack 4 .size 32
.field public int16 'token'
.field public uint16 'flags'
.field public uint8 'hold'
.field public int32 'fd'
.field public int32 'level'
.field public int32 'bsize'
.field public uint8 * 'buffer'
.field public uint8 * 'curp'
.field public valuetype 'Program.FloatToBinaryø__file2' * 'extended'
}
.class nested public ansi sealed value sequential '__anontype_511211642_1' {
.pack 1 .size 8
.field public int32 'quot'
.field public int32 'rem'
}
.class nested public ansi sealed value sequential '__anontype_511211642_2' {
.pack 1 .size 8
.field public int32 'quot'
.field public int32 'rem'
}
.class nested public ansi sealed value sequential '__anontype_511211642_3' {
.pack 1 .size 16
.field public int64 'quot'
.field public int64 'rem'
}
.class nested public ansi sealed value sequential '__anontype_2486130647_0' {
.pack 4 .size 4
}
.class nested private explicit ansi sealed value 'int8[]' {
.pack 1 .size 1
}
}
}
.method public static hidebysig pinvokeimpl("msvcrt.dll" cdecl)
vararg int32 'printf'(void *) preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" cdecl)
vararg int32 'scanf'(void *) preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" cdecl)
uint32 'strlen'(void *) preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" cdecl)
void * 'memset'(void *, int32, uint32) preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" stdcall)
uint16 * '__pctype_func'() preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" stdcall)
void * '__iob_func'() preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" stdcall)
void '__getmainargs'(void *, void *, void *, int32, void *) preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" stdcall)
void 'exit'(int32) preservesig{}
.method public static hidebysig pinvokeimpl("msvcrt.dll" stdcall)
int32 * '_errno'() preservesig{}
To execute the generated EXE/DLL, it is necessary to have two DLLs with the executable, you can get these two DLLs in C:\occil\bin\, you just copy the DLLs to the same EXE folder, and execute the generated EXE.
Building a Simple GUI Application
The compiler does not yet support everything that is necessary to create a complex program with GUI for Windows, but can compile simple programs, for this example, let's create a simple Window.
*NOTE*: To compile the code to use graphical interface, at the moment we still need to declare our main.
For this simple sample, let's create a C file called window.c.
#include <windows.h>
#include <stdio.h>
const char g_szClassName[] = "WindowClass";
void createButtons(HWND hwnd)
{
CreateWindow("button", "Beep",
WS_VISIBLE | WS_CHILD,
20, 50, 80, 25,
hwnd, (HMENU)1, NULL, NULL);
CreateWindow("button", "Quit",
WS_VISIBLE | WS_CHILD,
120, 50, 80, 25,
hwnd, (HMENU)2, NULL, NULL);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
createButtons(hwnd);
break;
case WM_COMMAND:
{
if (LOWORD(wParam) == 1)
Beep(40, 50);
if (LOWORD(wParam) == 2) {
MessageBox(hwnd, "Goodbye, cruel world!", "Note", MB_OK);
PostQuitMessage(0);
}
break;
}
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowExA(
WS_EX_CLIENTEDGE,
g_szClassName,
"Test window in .Net!! :)",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 230, 150,
NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
int main(int argc, char* argv[])
{
STARTUPINFOA si;
GetStartupInfoA(&si);
int ret = WinMain(GetModuleHandleA(NULL), NULL, "", (si.dwFlags & 1) ? si.wShowWindow : 10);
return ret;
}
To build this source, it is necessary to inform the compiler what libs are used in the source, so, the command line to build the source will be:
occil /Lkernel32 /Luser32 /9 window.c
After build, we can execute the application. :)
Creating and Using a DLL From Your C Code in C#
Now we know how to create a .NET EXE from your C code. Now, let's create a DLL from your C code and use it with your C# code.
Let's create a simple stack in C, for this, create a C file with name, "stack.c" and insert this code in the file:
#include <stdio.h>
#include <stdlib.h>
typedef struct _stack_
{
int size;
int totalitems;
int* stack;
} stack;
stack* pl_initastack(int size);
void pl_push(stack* pl, int elemento, int* success);
int pl_pop(stack* pl, int* success);
int pl_top(stack *pl, int* success);
int pl_base(stack* pl, int *success);
int pl_stackfull(stack* pl);
int pl_stackempty(stack* pl);
void pl_freestack(stack *pl);
void pl_cleanstack(stack *pl);
stack* pl_initastack(int size)
{
stack* pl = (stack*)malloc(sizeof(stack));
pl->stack = (int*)malloc(sizeof(int) * size);
pl->size = size;
pl->totalitems = 0;
return pl;
}
void pl_push(stack* pl, int elemento, int* success)
{
if (!pl_stackfull(pl))
pl->stack[pl->totalitems++] = elemento;
else
*success = 0;
}
int pl_pop(stack* pl, int* success)
{
if (!pl_stackempty(pl))
{
*success = 1;
return pl->stack[--pl->totalitems];
}
else
{
*success = 0;
return -1;
}
}
int pl_top(stack *pl, int* success)
{
if (pl_stackempty(pl))
{
*success = 0;
return -1;
}
else
{
*success = 1;
return pl->stack[pl->totalitems - 1];
}
}
int pl_base(stack* pl, int *success)
{
if (pl_stackempty(pl))
{
*success = 0;
return -1;
}
else
{
*success = 1;
return pl->stack[0];
}
}
int pl_stackfull(stack* pl)
{
return pl->totalitems >= pl->size;
}
int pl_stackempty(stack* pl)
{
return pl->totalitems == 0;
}
void pl_freestack(stack* pl)
{
free(pl->stack);
free(pl);
}
void pl_cleanstack(stack *pl)
{
pl->stack = malloc(sizeof(int) * pl->size);
pl->totalitems = 0;
}
Now, you need to build this source with option /Wd. This tells the compiler that you want to generate a DLL, so to build this file, we use this command line:
occil /ostackdll.dll /Wd /9 /NStackLib.Stack stack.c
Now, let's create the C# project. For this article, I created a C# project for .NET 4.0.
For that, you can use the DLL generated by OCC, you need to set some settings in C# project:
- Enable Unsafe code
- Platform target: x86
After you set these options, add the stack.dll in references and write the code to use the DLL, in this case, I wrote this simple sample program:
using System;
using StackLib;
namespace Stack
{
unsafe class Program
{
static void Main(string[] args)
{
StackLib.Stack._stack_* stk = null;
stk = StackLib.Stack.pl_initastack(5);
int success = 1;
Console.WriteLine("Pushing values to stack...");
for(int i=0; (success == 1); i++)
{
int val = i * 10;
StackLib.Stack.pl_push(stk, val, &success);
}
Console.WriteLine("Base value in stack: {0}", StackLib.Stack.pl_base(stk, &success));
Console.WriteLine("Top value in stack.: {0}", StackLib.Stack.pl_top(stk, &success));
Console.WriteLine("Poping values from stack");
while(true)
{
int val = StackLib.Stack.pl_pop(stk, &success);
if (success == 0)
break;
Console.WriteLine("{0}", val);
}
StackLib.Stack.pl_freestack(stk);
stk = null;
}
}
}
After you build the EXE, do not forget to copy the two DLLs that are in the BIN folder of orangec folder.
Using the SQLite
Now that we know how to create and use a DLL from the source code in C, let's use the SQLite!
You can find the source SQLite within the folder, \samples\sqlite3.
To build the SQLite source, it's necessary to use this command line:
occil /9 /Wd /Lkernel32 sqlite3.c /Nsqlite3.sqlite
After build the SQLite, create a C# or any other .NET project of your choice, add the reference of compiled DLL of the SQlite in your project, set the project type to x86 and if necessary, enable the unsafe mode, in my case, I created a simple C# project and added a small program to use the SQLite:
using System;
using System.IO;
using sqlite3;
using lsmsilcrtl;
namespace sqliteil
{
unsafe class Program
{
static string[] Names { get; } = new string[]
{
"Bob",
"Tom",
"Carlos",
"Marcos",
"Alexandre",
"Alex",
"Morgana",
"Maria",
"Jose",
"Joao",
"Marcos",
"Gustavo",
"Roberto",
"Rodrigo",
"Teste"
};
static int Main(string[] args)
{
String dbName = "dbtest.db";
if (File.Exists(dbName))
File.Delete(dbName);
sqlite.sqlite3* db;
int rc = sqlite.sqlite3_open(CString.ToPointer(dbName), &db);
if (rc != 0)
{
Console.WriteLine("Fail to create the database :(");
return -1;
}
sqlite.sqlite3_stmt* stmt;
sqlite3.sqlite.sqlite3_prepare_v2(db, CString.ToPointer
("CREATE TABLE demo (name TEXT, age INTEGER);"), -1, &stmt, null);
rc = sqlite.sqlite3_step(stmt);
if (rc != 101)
{
Console.WriteLine("Fail to create the table :(");
return -1;
}
sqlite.sqlite3_finalize(stmt);
foreach (var name in Names)
{
var insertLine = String.Format("insert into demo (name, age)
values ('{0}', {1});", name, new Random().Next(1, 99));
var query = CString.ToPointer(insertLine);
sqlite.sqlite3_prepare_v2(db, query, insertLine.Length, &stmt, null);
rc = sqlite.sqlite3_step(stmt);
if (rc != 101)
{
Console.WriteLine("Fail to insert the name: {0}", name);
}
sqlite.sqlite3_finalize(stmt);
}
var select = "SELECT * FROM demo;";
rc = sqlite.sqlite3_prepare_v2(db, CString.ToPointer(select), select.Length, &stmt, null);
if(rc == 0)
{
bool done = false;
while(!done)
{
switch(rc = sqlite.sqlite3_step(stmt))
{
case 5:
case 101:
done = true;
break;
case 100:
{
string name =
new CString(sqlite.sqlite3_column_text(stmt, 0)).ToString();
int age = sqlite.sqlite3_column_int(stmt, 1);
Console.WriteLine("Name: {0} -- Age: {1}", name, age);
rc = 0;
}
break;
default:
done = true;
break;
}
}
}
sqlite.sqlite3_close(db);
return 0;
}
}
}
This project is inside the SQLite test folder.
What is Not Implemented Yet
- Complex numbers aren't implemented
- Atomics aren't implemented
- thread and thread local storage aren't implemented
- runtime library is msvcrtl.dll, and doesn't support C11 or C99 additions to the CRTL
- arrays aren't implemented as managed arrays but as pointers to unmanaged memory
- array types are actually implemented as .NET classes
- variable length argument lists are done in the C# style rather than in the C style - except during calls to unmanaged functions
- variable length argument lists get marshalling performed when being passed to unmanaged code, but this only handles simple types
- thunks are generated for pointers-to-functions passed between managed and unmanaged code (e.g. for qsort and for
WNDPROC
style functions) but when the pointers are placed in a structure, you need to give the compiler a hint. Use CALLBACK
in the function pointer definition and make the callback
a stdcall
function - in the thunks for the transition from unmanaged to managed code used by function pointers passed to unmanaged code marshalling is performed, but this only handles simple types
- variable length arrays and '
alloca
' are implemented with a managed memory allocator instead of with the 'localalloc
' MSIL function - structures passed by value to functions get copied to temporary variables before the call
- many compiler optimizations found in the native version of the compiler are currently turned off
- The compiler will not allow use of unprototyped functions
Any Questions?
Feel free to ask! :)
History
- 16-Dez-2019
- 08-Set-2019
- Added new OrangeC compiler Release
- 12-Oct-2017
- Added link to part 2 of article
- Updated the zip with the new executables
- 09-Mar-2017
- Added link to article about DotNetPeLib
- 08-Mar-2017
- Updated the zip file with new executables
- Updated occ folder in article
- Added the official link to the project
- 10-Feb-2017
- Updated in what is not implemented yet
- 05-Nov-2016
- Updated executables
- Added link to DotNetPeLib
- Changes in the examples code to use
typedef
/struct
real name instead of "void
*" - Removed the explanation about build with Borland
- Removed references to ILASM
- Added what is not implemented yet
- 22-Sep-2016
- Added section: Building a simple GUI application
- Added section: Using the SQLite
- Updated the executables files to download
- 13-Sep-2016