0% found this document useful (0 votes)
62 views9 pages

Accessing C Code From C#

This document describes how to create a shared C library that can be accessed from C# code in a portable way that works with both Microsoft .NET and Mono. The C library defines a simple data structure for storing file names and provides functions for listing files in a directory, accessing file names, and cleaning up memory. The C code is compiled into a DLL on Windows and a shared object file on Linux. P/Invoke is used to call the C functions from C# code.

Uploaded by

EMGeek
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
62 views9 pages

Accessing C Code From C#

This document describes how to create a shared C library that can be accessed from C# code in a portable way that works with both Microsoft .NET and Mono. The C library defines a simple data structure for storing file names and provides functions for listing files in a directory, accessing file names, and cleaning up memory. The C code is compiled into a DLL on Windows and a shared object file on Linux. P/Invoke is used to call the C functions from C# code.

Uploaded by

EMGeek
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

Accessing C code from C#

A portable way, using both Microsoft .NET and Mono

Author: Nicolas Favre-Félix ([email protected])


Introduction

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

We will need two C files, a H file, and a CS file.


The data structure is coded in two files:

filename.h

#ifndef FILENAME_H
#define FILENAME_H

typedef struct FileName_ {


char *name;
struct FileName_ *next;
} FileName;

FileName *newFileName(char *name);


void delFileName(FileName *d);

#endif /* FILENAME_H */

filename.c

#include "filename.h"
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#define strdup _strdup
#endif

FileName *newFileName(char *name)


{
FileName *d = malloc(sizeof(FileName));
if(!d) return NULL;
d->name = strdup(name);
d->next = NULL;
return d;
}

void delFileName(FileName *d)


{
free(d->name);
free(d);
}

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.

The C code after this point has to be put in c-code.c.

Here is the function used to fill the data structure under Windows:

FileName * loadFileName_WIN(char *dir)


{
char *dir_all = NULL;
WIN32_FIND_DATAA FindFileData;
FileName *ret = NULL, *cur = NULL, *last = NULL;

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

The UNIX equivalent, to be added in the same C file:

FileName * loadFileName_UNIX(char *dir)


{

FileName *ret = NULL, *cur = NULL, *last = NULL;


struct dirent *finfos;

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

Then, a function definition must have the EXPORT macro in front of it to be


exported by the DLL.

We will have to export these functionalities:


• Open a directory, give me the first file “handle”
• Retrieve the number of files
• Retrieve the name of the current file
• Retrieve the handle of the next file
• Clean up memory

Open a directory and get a handle for the first file

EXPORT FileName * loadFileName(char *dir){

#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

EXPORT int nbFiles(FileName *d)


{
int ret = 0;
while(d){
ret++;
d=d->next;
}
return ret;

Retrieve the handle of the next file


EXPORT FileName *getNextFile(FileName *d)
{
if(d) return d->next;
else return NULL;
}

Clean up the memory


EXPORT void clearAll(FileName *d)
{
if(!d) return;
clearAll(d->next);

delFileName(d);
}

Retrieve the name of the current file

(This is the tough part)


We have two possible ways to do it in Microsoft .NET, and only one in Mono.
Let’s try by the official and common way. We will declare the function in C# as
“returning a pointer”.
We’ll call this function GetPtrFileName, as it is declared as returning a pointer :

[DllImport(DLL_File, EntryPoint = "getPtrFileName")]


public static extern IntPtr getPtrFileName(IntPtr d);

The C function being called is trivial:

EXPORT char *getPtrFileName(FileName *d)


{
if(d){
return d->name;
}
else {
return NULL;
}
}

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

IntPtr returned_pointer = loadFileName("/some/path/");


string s = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(
getPtrFileName(returned_pointer)
);

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.

We declare the function as returning a string, in C#:

[DllImport(DLL_File, EntryPoint = "getStringFileName")]


public static extern string getStringFileName(IntPtr d);

(Hence the name).

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:

EXPORT char *getStringFileName(FileName *d)


{
#ifdef WIN32
void *r = NULL;
if(d){
r = CoTaskMemAlloc(1+strlen(d->name));
strcpy(r, d->name);
return r;

}
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):

IntPtr returned_pointer = loadFileName("/some/path/");


string s = getStringFileName(returned_pointer);
Our system is now operational :

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

[DllImport(DLL_File, EntryPoint = "loadFileName")]


public static extern IntPtr loadFileName(string s);

[DllImport(DLL_File, EntryPoint = "nbFiles")]


public static extern int nbFiles(IntPtr d);

[DllImport(DLL_File, EntryPoint = "getPtrFileName")]


public static extern IntPtr getPtrFileName(IntPtr d);

[DllImport(DLL_File, EntryPoint = "getStringFileName")]


public static extern string getStringFileName(IntPtr d);

[DllImport(DLL_File, EntryPoint = "getNextFile")]


public static extern IntPtr getNextFile(IntPtr d);

[DllImport(DLL_File, EntryPoint = "clearAll")]


public static extern void clearAll(IntPtr d);

static void Main(string[] args)


{
IntPtr d;
if (args.Length == 0) {
Console.WriteLine("Give the directory path as a parameter,
here is for '.'");
d = loadFileName(".");
} else {
d = loadFileName(args[0]);
}

Console.WriteLine("Number of files: {0}", nbFiles(d));

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

all: c-code.o filename.o


gcc -shared c-code.o filename.o -o libCProject.so

filename.o: filename.c filename.h


gcc -o filename.o -c filename.c

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 !

You might also like