An interesting/annoying problem involving callbacks and native C and C#

P

per9000

An interesting/annoying problem.

I created a small example to provoke an exception I keep getting.
Basically I have a C-struct (Container) with a function-pointer in it.
I perform repeated calls to the function in the container. I allocate
(and free in c) arrays to get garbage collection in both C and C#.
After a few seconds I get an exception.

I assume GarbageCollector moves the delegate (or collects is) and when
I don't find it in C (since it has moved) I get an exception. In my
real life problem this behaviour is not stopped by adding
GC.KeepAlive(X), but here it seems to be. Can the callback/delegate/
functionpointer still be moved by the garbage collector even if I add
GC.KeepAlive(X)?

I have somewhere seen that I can map a C-struct to a C#-struct but I
have had problems with it so that is not an option for me at this
point.

Can my problems be solved with the keyword fixed? If so how? I tried
fixed (DateCallBack* pdcb = &dcb) {...} but I am told that I cannot
take the address of, get the size of, or declare a pointer to a
managed type.

Any help is appreciated,
Per

//
// Contents of files below
//
// --------------------------------------------

/*
* File: CallBackExample.cs
*
* Compiled with:
* csc CallBackExample.cs
* Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
*
* Purpose:
* Illustrates problems with callback function
* when using it in combination with
* CallBackCaller.c
*
* Created 20070212
* by Per Erik Strandberg, www.pererikstrandberg.se
* for use in newsgroups
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace PER9000.CallBackExample
{
class CallBackExample
{
// delegate "class" to match my TimeStamper
delegate void DateCallBack(ref int y, ref int m, ref int d, ref
int h,
ref int min, ref int s, ref int ms);

// sets year, month, day, hour, minute, second and millisecond
public static void TimeStamper(ref int y, ref int m, ref int d,
ref int h, ref int min, ref int s, ref int ms)
{
DateTime dt = DateTime.Now;
y = dt.Year;
m = dt.Month;
d = dt.Day;
h = dt.Hour;
min = dt.Minute;
s = dt.Second;
ms = dt.Millisecond;

double[] tempArr = new double[y * (m + d)];

for (int i = 0; i < tempArr.Length; i++)
tempArr = Math.Sin(((double)y) / (d + m)) +
Math.Sqrt((double)(ms + s + min));
}

[DllImport("CallBackCaller.dll")]
extern static void InitStruct(DateCallBack func, ref IntPtr Ctr);

[DllImport("CallBackCaller.dll")]
extern static void KillStruct(IntPtr Ctr);

[DllImport("CallBackCaller.dll")]
extern static void RunFuncs(IntPtr Ctr, int i, int j);


// testprogram does the tests
unsafe static void Main(string[] args)
{
// say: Hell o World
Console.WriteLine("C# >> start");

// Create IntPtr to manage C-struct
IntPtr Ctr = new IntPtr();

// Create callback to put in Container
DateCallBack dcb = new DateCallBack(
PER9000.CallBackExample.CallBackExample.TimeStamper);

fixed (DateCallBack* pdcb = &dcb)
{
// ...
}

// create container
InitStruct(dcb, ref Ctr);

// Call RunFuncs

int i = 5;
int j = 5;
try
{
i = int.Parse(args[0]);
j = int.Parse(args[1]);
}
catch(Exception)
{
i = 5;
j = 5;
}

Console.WriteLine("C# >> calling with (i,j) == ({0},{1})", i,
j);
RunFuncs(Ctr, i, j);

// Kill Container
KillStruct(Ctr);

// keep alive and see the same (?) behaviour
// GC.KeepAlive(dcb);

// say that's it folks
Console.WriteLine("C# >> done");

}
}
}

/* CallBackExample.cs EOF */

// --------------------------------------------

/*
* File: CallBackCaller.c
*
* Compiled with:
* cl CallBackCaller.c /LD /Zi
* Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804
for 80x86
*
* Purpose:
* Illustrates movement (?) of callback function
* when using it in combination with
* CallBackExample.cs
*
* Created 20070212
* by Per Erik Strandberg, www.pererikstrandberg.se
* for use in newsgroups
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/* should match C# delegate DateCallBack */
typedef void (__stdcall * DateCallBack)(int*, int*, int*, int*, int*,
int*, int*);


/* struct with function pointer */
typedef struct ContainerStruct
{
DateCallBack F;
} Container;


/* Creates a struct with a callback */
void __declspec(dllexport) InitStruct(DateCallBack Function,
Container ** Ctr)
{
*Ctr = (Container *) malloc(1 * sizeof(Container));
(*Ctr)->F = Function;
}


/* Frees the struct */
void __declspec(dllexport) KillStruct(Container * Ctr)
{
free(Ctr);
}


/* Evil tester - runs the callback very many times */
void __declspec(dllexport) RunFuncs(Container * Ctr, int i, int j)
{
/* p,q are loop counters */
int p, q;

/* year, month and so on */
int y, m, d, h, min, s, ms;

/* for creation of temporary array */
int l, len;
int * tempArr;
len = i+j+500;

/*
* 1 - call the callback and print result on console
* 2 - allocate memory (int-array)
* 3 - fill array with annoying stuff that
* a - takes time
* b - provoces memory management
* 4 - free memory
*/
for (p = 1; p < i; p++)
{
/* 1 */
Ctr->F(&y, &m, &d, &h, &min, &s, &ms);
printf("C >> (p,q) = (%d,%d) of (%d,%d): %d-%d-%d, %d:%d:%d,%d
\n",
p, q, i, j, y, m, d, h, min, s, ms);
fflush(stdout);

/* 2 */
tempArr = (int * ) malloc((len)*sizeof(int));

/* 3 */
for (l = 1; l < len; l++)
for (q = 1; q < j; q++)
tempArr[l] = (q+(l*l/l)/l+123)-(321+(l*l/l)/l)*l/l;

/* 4 */
free(tempArr);
}
}

/* CallBackCaller.c EOF */
 
W

Willy Denoyette [MVP]

per9000 said:
An interesting/annoying problem.

I created a small example to provoke an exception I keep getting.
Basically I have a C-struct (Container) with a function-pointer in it.
I perform repeated calls to the function in the container. I allocate
(and free in c) arrays to get garbage collection in both C and C#.
After a few seconds I get an exception.

I assume GarbageCollector moves the delegate (or collects is) and when
I don't find it in C (since it has moved) I get an exception. In my
real life problem this behaviour is not stopped by adding
GC.KeepAlive(X), but here it seems to be. Can the callback/delegate/
functionpointer still be moved by the garbage collector even if I add
GC.KeepAlive(X)?

I have somewhere seen that I can map a C-struct to a C#-struct but I
have had problems with it so that is not an option for me at this
point.

Can my problems be solved with the keyword fixed? If so how? I tried
fixed (DateCallBack* pdcb = &dcb) {...} but I am told that I cannot
take the address of, get the size of, or declare a pointer to a
managed type.

Any help is appreciated,
Per

//
// Contents of files below
//
// --------------------------------------------

/*
* File: CallBackExample.cs
*
* Compiled with:
* csc CallBackExample.cs
* Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
*
* Purpose:
* Illustrates problems with callback function
* when using it in combination with
* CallBackCaller.c
*
* Created 20070212
* by Per Erik Strandberg, www.pererikstrandberg.se
* for use in newsgroups
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace PER9000.CallBackExample
{
class CallBackExample
{
// delegate "class" to match my TimeStamper
delegate void DateCallBack(ref int y, ref int m, ref int d, ref
int h,
ref int min, ref int s, ref int ms);

// sets year, month, day, hour, minute, second and millisecond
public static void TimeStamper(ref int y, ref int m, ref int d,
ref int h, ref int min, ref int s, ref int ms)
{
DateTime dt = DateTime.Now;
y = dt.Year;
m = dt.Month;
d = dt.Day;
h = dt.Hour;
min = dt.Minute;
s = dt.Second;
ms = dt.Millisecond;

double[] tempArr = new double[y * (m + d)];

for (int i = 0; i < tempArr.Length; i++)
tempArr = Math.Sin(((double)y) / (d + m)) +
Math.Sqrt((double)(ms + s + min));
}

[DllImport("CallBackCaller.dll")]
extern static void InitStruct(DateCallBack func, ref IntPtr Ctr);

[DllImport("CallBackCaller.dll")]
extern static void KillStruct(IntPtr Ctr);

[DllImport("CallBackCaller.dll")]
extern static void RunFuncs(IntPtr Ctr, int i, int j);


// testprogram does the tests
unsafe static void Main(string[] args)
{
// say: Hell o World
Console.WriteLine("C# >> start");

// Create IntPtr to manage C-struct
IntPtr Ctr = new IntPtr();

// Create callback to put in Container
DateCallBack dcb = new DateCallBack(
PER9000.CallBackExample.CallBackExample.TimeStamper);

fixed (DateCallBack* pdcb = &dcb)
{
// ...
}

// create container
InitStruct(dcb, ref Ctr);

// Call RunFuncs

int i = 5;
int j = 5;
try
{
i = int.Parse(args[0]);
j = int.Parse(args[1]);
}
catch(Exception)
{
i = 5;
j = 5;
}

Console.WriteLine("C# >> calling with (i,j) == ({0},{1})", i,
j);
RunFuncs(Ctr, i, j);

// Kill Container
KillStruct(Ctr);

// keep alive and see the same (?) behaviour
// GC.KeepAlive(dcb);

// say that's it folks
Console.WriteLine("C# >> done");

}
}
}

/* CallBackExample.cs EOF */

// --------------------------------------------

/*
* File: CallBackCaller.c
*
* Compiled with:
* cl CallBackCaller.c /LD /Zi
* Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804
for 80x86
*
* Purpose:
* Illustrates movement (?) of callback function
* when using it in combination with
* CallBackExample.cs
*
* Created 20070212
* by Per Erik Strandberg, www.pererikstrandberg.se
* for use in newsgroups
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/* should match C# delegate DateCallBack */
typedef void (__stdcall * DateCallBack)(int*, int*, int*, int*, int*,
int*, int*);


/* struct with function pointer */
typedef struct ContainerStruct
{
DateCallBack F;
} Container;


/* Creates a struct with a callback */
void __declspec(dllexport) InitStruct(DateCallBack Function,
Container ** Ctr)
{
*Ctr = (Container *) malloc(1 * sizeof(Container));
(*Ctr)->F = Function;
}


/* Frees the struct */
void __declspec(dllexport) KillStruct(Container * Ctr)
{
free(Ctr);
}


/* Evil tester - runs the callback very many times */
void __declspec(dllexport) RunFuncs(Container * Ctr, int i, int j)
{
/* p,q are loop counters */
int p, q;

/* year, month and so on */
int y, m, d, h, min, s, ms;

/* for creation of temporary array */
int l, len;
int * tempArr;
len = i+j+500;

/*
* 1 - call the callback and print result on console
* 2 - allocate memory (int-array)
* 3 - fill array with annoying stuff that
* a - takes time
* b - provoces memory management
* 4 - free memory
*/
for (p = 1; p < i; p++)
{
/* 1 */
Ctr->F(&y, &m, &d, &h, &min, &s, &ms);
printf("C >> (p,q) = (%d,%d) of (%d,%d): %d-%d-%d, %d:%d:%d,%d
\n",
p, q, i, j, y, m, d, h, min, s, ms);
fflush(stdout);

/* 2 */
tempArr = (int * ) malloc((len)*sizeof(int));

/* 3 */
for (l = 1; l < len; l++)
for (q = 1; q < j; q++)
tempArr[l] = (q+(l*l/l)/l+123)-(321+(l*l/l)/l)*l/l;

/* 4 */
free(tempArr);
}
}

/* CallBackCaller.c EOF */



Above code (C#) is missing a GC.KeepAlive(dcb), but seems like you know that.
The problem is not a "moving" delegate instance, in above case it's because of a pre-mature
collection of the delegate. The CLR is unaware of what the unmanaged code will do with the
delegate, the JIT reports the delegate as garbage after the call to "InitStruct", the next
run of the GC will collect the instance and at the next callback the interop layer will call
into invalid memory ....

Other than this, your C function signatures should all use the stdcall calling convention
like:
__declspec(dllexport) void __stdcall RunFuncs(Container * Ctr, int i, int j)

Don't know what exactly is you real-life problem though...

Willy.
 
P

per9000

Hi Willy,

thanks for the hint on using stdcall, but it seems I have added that
it to the real-life problem.

My real life problem is pretty much a mix of .NET C# and MC++; and
native C and Fortran. I have methods written in C# that are stored in
structs (delegates to them anyway) in C (from objects in C#). C and
Fortran talks a lot and runs the methods from time to time given lots
of conditions.

I've had two major problems. The first one is the one you mention,
that is fixed by GC.KeepAlive(..), the typical exception was Unhandled
Exception: System.AccessViolationException.

The other one, that I suspected was caused by a moved delegate, is
that my program just shuts down - no exception or anything. A major
problem for me here is if this is caused by some old exit condition in
some obscure Fortran code that no one has touched for years or is it
caused by the delegate that has moved. My idea was the following: IF
the delegate has moved THEN any call to it (to some random memory I
guess) could do about anything - for example shut down. Also this
behavior only occurred after several seconds (almost a minute) or
loops that really torture my native code so I suspected the garbage
man.

In a blog at http://blogs.msdn.com/sebby1234/archive/2006/04/05/565090.aspx
I found (under Pinned object): "The garbage collector has the
possibility of physically moving the objects for which it is
responsible..." This is pretty much why I started suspecting the
garbage man.

So, my problem distills to a number of questions:
- When, why and how is the garbage man moving objects?
- Is pinning the answer to my prayers? Why/Why not?
- Can I "pin" managed objects? How/Why not?

Of course, any other help is also useful,

thanks,
Per
----------------------

Ever wanted a list of logical drives? Check out
Per Erik Strandberg's DRIVES:
http://www.pererikstrandberg.se/projects/drives/



An interesting/annoying problem.
I created a small example to provoke an exception I keep getting.
Basically I have a C-struct (Container) with a function-pointer in it.
I perform repeated calls to the function in the container. I allocate
(and free in c) arrays to get garbage collection in both C and C#.
After a few seconds I get an exception.
I assume GarbageCollector moves the delegate (or collects is) and when
I don't find it in C (since it has moved) I get an exception. In my
real life problem this behaviour is not stopped by adding
GC.KeepAlive(X), but here it seems to be. Can the callback/delegate/
functionpointer still be moved by the garbage collector even if I add
GC.KeepAlive(X)?
I have somewhere seen that I can map a C-struct to a C#-struct but I
have had problems with it so that is not an option for me at this
point.
Can my problems be solved with the keyword fixed? If so how? I tried
fixed (DateCallBack* pdcb = &dcb) {...} but I am told that I cannot
take the address of, get the size of, or declare a pointer to a
managed type.
Any help is appreciated,
Per
//
// Contents of files below
//
// --------------------------------------------
/*
* File: CallBackExample.cs
*
* Compiled with:
* csc CallBackExample.cs
* Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
*
* Purpose:
* Illustrates problems with callback function
* when using it in combination with
* CallBackCaller.c
*
* Created 20070212
* by Per Erik Strandberg,www.pererikstrandberg.se
* for use in newsgroups
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace PER9000.CallBackExample
{
class CallBackExample
{
// delegate "class" to match my TimeStamper
delegate void DateCallBack(ref int y, ref int m, ref int d, ref
int h,
ref int min, ref int s, ref int ms);
// sets year, month, day, hour, minute, second and millisecond
public static void TimeStamper(ref int y, ref int m, ref int d,
ref int h, ref int min, ref int s, ref int ms)
{
DateTime dt = DateTime.Now;
y = dt.Year;
m = dt.Month;
d = dt.Day;
h = dt.Hour;
min = dt.Minute;
s = dt.Second;
ms = dt.Millisecond;
double[] tempArr = new double[y * (m + d)];
for (int i = 0; i < tempArr.Length; i++)
tempArr = Math.Sin(((double)y) / (d + m)) +
Math.Sqrt((double)(ms + s + min));
}

[DllImport("CallBackCaller.dll")]
extern static void InitStruct(DateCallBack func, ref IntPtr Ctr);
[DllImport("CallBackCaller.dll")]
extern static void KillStruct(IntPtr Ctr);
[DllImport("CallBackCaller.dll")]
extern static void RunFuncs(IntPtr Ctr, int i, int j);
// testprogram does the tests
unsafe static void Main(string[] args)
{
// say: Hell o World
Console.WriteLine("C# >> start");
// Create IntPtr to manage C-struct
IntPtr Ctr = new IntPtr();
// Create callback to put in Container
DateCallBack dcb = new DateCallBack(
PER9000.CallBackExample.CallBackExample.TimeStamper);
fixed (DateCallBack* pdcb = &dcb)
{
// ...
}
// create container
InitStruct(dcb, ref Ctr);
// Call RunFuncs
int i = 5;
int j = 5;
try
{
i = int.Parse(args[0]);
j = int.Parse(args[1]);
}
catch(Exception)
{
i = 5;
j = 5;
}
Console.WriteLine("C# >> calling with (i,j) == ({0},{1})", i,
j);
RunFuncs(Ctr, i, j);
// Kill Container
KillStruct(Ctr);
// keep alive and see the same (?) behaviour
// GC.KeepAlive(dcb);
// say that's it folks
Console.WriteLine("C# >> done");

/* CallBackExample.cs EOF */
// --------------------------------------------
/*
* File: CallBackCaller.c
*
* Compiled with:
* cl CallBackCaller.c /LD /Zi
* Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804
for 80x86
*
* Purpose:
* Illustrates movement (?) of callback function
* when using it in combination with
* CallBackExample.cs
*
* Created 20070212
* by Per Erik Strandberg,www.pererikstrandberg.se
* for use in newsgroups
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* should match C# delegate DateCallBack */
typedef void (__stdcall * DateCallBack)(int*, int*, int*, int*, int*,
int*, int*);
/* struct with function pointer */
typedef struct ContainerStruct
{
DateCallBack F;
} Container;
/* Creates a struct with a callback */
void __declspec(dllexport) InitStruct(DateCallBack Function,
Container ** Ctr)
{
*Ctr = (Container *) malloc(1 * sizeof(Container));
(*Ctr)->F = Function;
}
/* Frees the struct */
void __declspec(dllexport) KillStruct(Container * Ctr)
{
free(Ctr);
}
/* Evil tester - runs the callback very many times */
void __declspec(dllexport) RunFuncs(Container * Ctr, int i, int j)
{
/* p,q are loop counters */
int p, q;
/* year, month and so on */
int y, m, d, h, min, s, ms;
/* for creation of temporary array */
int l, len;
int * tempArr;
len = i+j+500;
/*
* 1 - call the callback and print result on console
* 2 - allocate memory (int-array)
* 3 - fill array with annoying stuff that
* a - takes time
* b - provoces memory management
* 4 - free memory
*/
for (p = 1; p < i; p++)
{
/* 1 */
Ctr->F(&y, &m, &d, &h, &min, &s, &ms);
printf("C >> (p,q) = (%d,%d) of (%d,%d): %d-%d-%d, %d:%d:%d,%d
\n",
p, q, i, j, y, m, d, h, min, s, ms);
fflush(stdout);
/* 2 */
tempArr = (int * ) malloc((len)*sizeof(int));
/* 3 */
for (l = 1; l < len; l++)
for (q = 1; q < j; q++)
tempArr[l] = (q+(l*l/l)/l+123)-(321+(l*l/l)/l)*l/l;
/* 4 */
free(tempArr);
}
}
/* CallBackCaller.c EOF */

Above code (C#) is missing a GC.KeepAlive(dcb), but seems like you know that.
The problem is not a "moving" delegate instance, in above case it's because of a pre-mature
collection of the delegate. The CLR is unaware of what the unmanaged code will do with the
delegate, the JIT reports the delegate as garbage after the call to "InitStruct", the next
run of the GC will collect the instance and at the next callback the interop layer will call
into invalid memory ....

Other than this, your C function signatures should all use the stdcall calling convention
like:
__declspec(dllexport) void __stdcall RunFuncs(Container * Ctr, int i, int j)

Don't know what exactly is you real-life problem though...

Willy.
 
W

Willy Denoyette [MVP]

per9000 said:
Hi Willy,

thanks for the hint on using stdcall, but it seems I have added that
it to the real-life problem.

My real life problem is pretty much a mix of .NET C# and MC++; and
native C and Fortran. I have methods written in C# that are stored in
structs (delegates to them anyway) in C (from objects in C#). C and
Fortran talks a lot and runs the methods from time to time given lots
of conditions.

I've had two major problems. The first one is the one you mention,
that is fixed by GC.KeepAlive(..), the typical exception was Unhandled
Exception: System.AccessViolationException.

The other one, that I suspected was caused by a moved delegate, is
that my program just shuts down - no exception or anything. A major
problem for me here is if this is caused by some old exit condition in
some obscure Fortran code that no one has touched for years or is it
caused by the delegate that has moved. My idea was the following: IF
the delegate has moved THEN any call to it (to some random memory I
guess) could do about anything - for example shut down. Also this
behavior only occurred after several seconds (almost a minute) or
loops that really torture my native code so I suspected the garbage
man.

In a blog at http://blogs.msdn.com/sebby1234/archive/2006/04/05/565090.aspx
I found (under Pinned object): "The garbage collector has the
possibility of physically moving the objects for which it is
responsible..." This is pretty much why I started suspecting the
garbage man.

So, my problem distills to a number of questions:
- When, why and how is the garbage man moving objects?
- Is pinning the answer to my prayers? Why/Why not?
- Can I "pin" managed objects? How/Why not?

Of course, any other help is also useful,

thanks,
Per


Moving delegates can never be a problem, so there is no need to pin, all you need to care
about is the life-time of your delegate(s), otherwise said, you need to make sure the
delegate(s) doesn't get GC'd. Make also sure that the "containers" in which you store the
delegate references don't get collected, this would also cause the delegates to get
collected (assumed there are no other references to the delegates).
Note also that what's passed to unmanaged code is a "function pointer", the function it
points to never moves (and doesn't go away with the delegate), code belongs to the 'type',
it's not part of the object!.

Willy.
 
P

per9000

This is interesting to me:
Note also that what's passed to unmanaged code is a "function pointer", the function it
points to never moves (and doesn't go away with the delegate), code belongs to the 'type',
it's not part of the object!.

Thanks for making that clear, it helps. I will accept my fate and try
to add GC.KeepAlive(...) and see if it helps.

But I still do not really understand when it is a good idea to use
"fixed"/"pinning" (and please don't say never even if it true :-D ).
Is it just a way to use C-style pointer arithmetic in C#?

/Per
 
L

Laura T.

What exception do you get?
Have any stack trace when it happens?

per9000 said:
An interesting/annoying problem.

I created a small example to provoke an exception I keep getting.
Basically I have a C-struct (Container) with a function-pointer in it.
I perform repeated calls to the function in the container. I allocate
(and free in c) arrays to get garbage collection in both C and C#.
After a few seconds I get an exception.

I assume GarbageCollector moves the delegate (or collects is) and when
I don't find it in C (since it has moved) I get an exception. In my
real life problem this behaviour is not stopped by adding
GC.KeepAlive(X), but here it seems to be. Can the callback/delegate/
functionpointer still be moved by the garbage collector even if I add
GC.KeepAlive(X)?

I have somewhere seen that I can map a C-struct to a C#-struct but I
have had problems with it so that is not an option for me at this
point.

Can my problems be solved with the keyword fixed? If so how? I tried
fixed (DateCallBack* pdcb = &dcb) {...} but I am told that I cannot
take the address of, get the size of, or declare a pointer to a
managed type.

Any help is appreciated,
Per

//
// Contents of files below
//
// --------------------------------------------

/*
* File: CallBackExample.cs
*
* Compiled with:
* csc CallBackExample.cs
* Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
*
* Purpose:
* Illustrates problems with callback function
* when using it in combination with
* CallBackCaller.c
*
* Created 20070212
* by Per Erik Strandberg, www.pererikstrandberg.se
* for use in newsgroups
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace PER9000.CallBackExample
{
class CallBackExample
{
// delegate "class" to match my TimeStamper
delegate void DateCallBack(ref int y, ref int m, ref int d, ref
int h,
ref int min, ref int s, ref int ms);

// sets year, month, day, hour, minute, second and millisecond
public static void TimeStamper(ref int y, ref int m, ref int d,
ref int h, ref int min, ref int s, ref int ms)
{
DateTime dt = DateTime.Now;
y = dt.Year;
m = dt.Month;
d = dt.Day;
h = dt.Hour;
min = dt.Minute;
s = dt.Second;
ms = dt.Millisecond;

double[] tempArr = new double[y * (m + d)];

for (int i = 0; i < tempArr.Length; i++)
tempArr = Math.Sin(((double)y) / (d + m)) +
Math.Sqrt((double)(ms + s + min));
}

[DllImport("CallBackCaller.dll")]
extern static void InitStruct(DateCallBack func, ref IntPtr Ctr);

[DllImport("CallBackCaller.dll")]
extern static void KillStruct(IntPtr Ctr);

[DllImport("CallBackCaller.dll")]
extern static void RunFuncs(IntPtr Ctr, int i, int j);


// testprogram does the tests
unsafe static void Main(string[] args)
{
// say: Hell o World
Console.WriteLine("C# >> start");

// Create IntPtr to manage C-struct
IntPtr Ctr = new IntPtr();

// Create callback to put in Container
DateCallBack dcb = new DateCallBack(
PER9000.CallBackExample.CallBackExample.TimeStamper);

fixed (DateCallBack* pdcb = &dcb)
{
// ...
}

// create container
InitStruct(dcb, ref Ctr);

// Call RunFuncs

int i = 5;
int j = 5;
try
{
i = int.Parse(args[0]);
j = int.Parse(args[1]);
}
catch(Exception)
{
i = 5;
j = 5;
}

Console.WriteLine("C# >> calling with (i,j) == ({0},{1})", i,
j);
RunFuncs(Ctr, i, j);

// Kill Container
KillStruct(Ctr);

// keep alive and see the same (?) behaviour
// GC.KeepAlive(dcb);

// say that's it folks
Console.WriteLine("C# >> done");

}
}
}

/* CallBackExample.cs EOF */

// --------------------------------------------

/*
* File: CallBackCaller.c
*
* Compiled with:
* cl CallBackCaller.c /LD /Zi
* Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804
for 80x86
*
* Purpose:
* Illustrates movement (?) of callback function
* when using it in combination with
* CallBackExample.cs
*
* Created 20070212
* by Per Erik Strandberg, www.pererikstrandberg.se
* for use in newsgroups
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/* should match C# delegate DateCallBack */
typedef void (__stdcall * DateCallBack)(int*, int*, int*, int*, int*,
int*, int*);


/* struct with function pointer */
typedef struct ContainerStruct
{
DateCallBack F;
} Container;


/* Creates a struct with a callback */
void __declspec(dllexport) InitStruct(DateCallBack Function,
Container ** Ctr)
{
*Ctr = (Container *) malloc(1 * sizeof(Container));
(*Ctr)->F = Function;
}


/* Frees the struct */
void __declspec(dllexport) KillStruct(Container * Ctr)
{
free(Ctr);
}


/* Evil tester - runs the callback very many times */
void __declspec(dllexport) RunFuncs(Container * Ctr, int i, int j)
{
/* p,q are loop counters */
int p, q;

/* year, month and so on */
int y, m, d, h, min, s, ms;

/* for creation of temporary array */
int l, len;
int * tempArr;
len = i+j+500;

/*
* 1 - call the callback and print result on console
* 2 - allocate memory (int-array)
* 3 - fill array with annoying stuff that
* a - takes time
* b - provoces memory management
* 4 - free memory
*/
for (p = 1; p < i; p++)
{
/* 1 */
Ctr->F(&y, &m, &d, &h, &min, &s, &ms);
printf("C >> (p,q) = (%d,%d) of (%d,%d): %d-%d-%d, %d:%d:%d,%d
\n",
p, q, i, j, y, m, d, h, min, s, ms);
fflush(stdout);

/* 2 */
tempArr = (int * ) malloc((len)*sizeof(int));

/* 3 */
for (l = 1; l < len; l++)
for (q = 1; q < j; q++)
tempArr[l] = (q+(l*l/l)/l+123)-(321+(l*l/l)/l)*l/l;

/* 4 */
free(tempArr);
}
}

/* CallBackCaller.c EOF */
 
W

Willy Denoyette [MVP]

per9000 said:
This is interesting to me:


Thanks for making that clear, it helps. I will accept my fate and try
to add GC.KeepAlive(...) and see if it helps.

But I still do not really understand when it is a good idea to use
"fixed"/"pinning" (and please don't say never even if it true :-D ).
Is it just a way to use C-style pointer arithmetic in C#?

/Per


When using PInvoke to call into unmanaged code there are very few case in which you need to
pin, the interop layer will pin all objects passed as argument in the call, for the
*duration* of the call.
The latter is important, for instance, say you pass an array to unmanaged, and unmanaged
keeps a pointer to the array to be used (say in another thread or in another call from
managed) after the call returned to managed code, then you MUST pin the array. Note that
this can be tricky, because managed has no idea for how long it should pin the array.
So, please search your unmanaged code to make sure you aren't using/referring to objects
passed to unmanaged after the call returned, if that's the case you need to pin this object
using GCHandle.

Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top