Accessing C Code From C#
Accessing C Code From C#
This document explains how to create a shared C library and to access it from
C#, in a portable way. The code should be easy to port to Mono and Linux.
The library here will provide the simple functionality of listing files in a given
directory.
We want to be able to get the list of files and directories under this folder.
The C part
filename.h
#ifndef FILENAME_H
#define FILENAME_H
#endif /* FILENAME_H */
filename.c
#include "filename.h"
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#define strdup _strdup
#endif
This chained list structure is very simple and should be totally portable.
Windows dir
Let’s start with the Windows way of finding files, using FindFirst / FindNext /
FindClose. We will create a chained list, containing the found file names. For
practical reasons, Unicode will not be used here.
Here is the function used to fill the data structure under Windows:
HANDLE hFind;
dir_all = malloc(strlen(dir) + 3);
*dir_all = 0;
strcpy(dir_all, dir);
strcat(dir_all, "\\*");
hFind = FindFirstFileA((LPCSTR)dir_all, &FindFileData);
free(dir_all); dir_all = NULL;
if (hFind == INVALID_HANDLE_VALUE) {
return NULL;
} else {
while(1){
cur = newFileName((char*)FindFileData.cFileName);
if(!last){
ret = cur;
last = cur;
} else {
last->next = cur;
last = cur;
}
if(FindNextFile(hFind, &FindFileData) == FALSE){
break;
}
}
FindClose(hFind);
return ret;
}
return NULL;
}
It is also fairly simple. \* is added to the directory name to make sure all files are
found in it, an not just the directory itself.
The _CRT_SECURE_NO_DEPRECATE macro has to be defined in your VS 2005 project
to avoid deprecation warnings.
UNIX ls -a
DIR *hFind;
hFind = opendir(dir);
if (hFind == NULL) {
return NULL;
} else {
while(finfos = readdir(hFind)){
cur = newFileName(finfos->d_name);
if(!last){
ret = cur;
last = cur;
} else {
last->next = cur;
last = cur;
}
}
closedir(hFind);
return ret;
}
return NULL;
}
The code is similar and as simple if not more.
Now that we have the data structure and the function to fill it, we need to create a
shared object / DLL interface to be able to access it from .NET using P/Invoke.
This is done using __declspec(dllexport) in Visual Studio, while there is no
need to do this if you’re using GCC.
This conditional definition makes it work for both:
#ifdef WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
#ifdef WIN32
return loadFileName_WIN(dir);
#else
return loadFileName_UNIX(dir);
#endif
The two functions being defined above. This way, you’ll compile the good
function on the good platform. Be sure also to add the good header inclusion
directives in the #ifdef block on top of the file: Windows.h is needed in Visual
Studio, sys/types.h and dirent.h in GCC.
Retrieve the number of files
delFileName(d);
}
Now this works on both platforms, but you get a pointer, not a string. To convert
this pointer to a string, you have to use
The second way is a hack, to get rid of the Marshaling call. It only works on
Windows and should be avoided if the performance impact is not important.
The problem we have is that the .NET Framework will free the returned pointer
using CoTaskMemFree if the function is supposed to return a string. This is why
you will have to allocate a copy using CoTaskMemAlloc, and return this copy:
}
else {
return NULL;
}
#else
return NULL; /* doesn't work on Mono */
#endif
}
The function can then be called directly from C# (only in the Microsoft
Framework, once again):
Final C# code
Program.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApp
{
class Program
{
#if MONO
const string DLL_File = "CProject";
#else
const string DLL_File = "CProject.dll";
#endif
Console.WriteLine("Files: ");
IntPtr cur = d;
while (cur != IntPtr.Zero) {
#if MONO
Console.WriteLine("\t{0}",
Marshal.PtrToStringAnsi(getPtrFileName(cur)));
#else
Console.WriteLine("\t{0}", getStringFileName(cur));
#endif
cur = getNextFile(cur);
}
clearAll(d);
Console.ReadLine();
}
}
}
UNIX configuration
Under UNIX, you will have to create a Makefile to compile your C code, here is
the one used in the example:
Makefile
c-code.o: c-code.c
gcc -o c-code.o -c c-code.c
clean:
rm -f CProject.so filename.o c-code.o
Something has to be pointed out here: we are creating libCProject.so, where the
C# code is accessing “CProject”. This is on purpose, as Mono is looking for “lib”
+ your_lib_name + “.so”, among other patterns.
You will also have to compile your C# code with the mono compiler. Do not forget
to define “MONO” to enable #if MONO blocks and discard others.
This is done by running the mono compiler MCS with the –define command line
flag: mcs -define:MONO Program.cs
Good luck !