Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2347130
  • 博文数量: 527
  • 博客积分: 10343
  • 博客等级: 上将
  • 技术积分: 5565
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-26 23:05
文章分类

全部博文(527)

文章存档

2014年(4)

2012年(13)

2011年(19)

2010年(91)

2009年(136)

2008年(142)

2007年(80)

2006年(29)

2005年(13)

我的朋友

分类: WINDOWS

2008-06-05 20:59:25

如何把C#的函数传递给native 模块, 在native的C模块中作为一个函数指针来调用?

首先, 程序启动后第一个控制权在C#代码中, 所以native C代码需要export出来一个函数供C#代码调用, 此次调用目的仅是"安装"函数指针:
=========Native C=======

typedef char * (__stdcall * fp_T) (char *);

__declspec(dllexport) void __stdcall SetCallBack( fp_T fp );

static fp_T s_fp = NULL;
void __stdcall SetCallBack( fp_T fp )
{
    s_fp = fp;
}

char * Call_Csharp(char * key)
{
    if( s_fp != NULL)
    {
        return s_fp( key );
    }
    return NULL;
}


其中 __stdcall 是调用约定, 在下面详细讨论调用约定之前, 请记住: 在所有涉及PInvoke/Callback的事件中, 无论在C#端还是在native一端统一地全部使用 __stdcall总是安全的, 不管你是.NET 1.0, 1.1还是2.0

第一行的typedef为了避免语法上的混乱难读.

接下来的函数 SetCallBack 读是native模块export出来专供C#代码设置函数指针(C#中的delegate实例)用的. 函数很简单, 即使在实际的产品代码中, 也不过这一句话.

后面的 Call_Csharp 函数定义是模拟让Native 模块中来通过它间接调用C#函数的, 它只需要是模块内部可见的即可, 不必是被export出来的函数.

再看C#一端:
===========C# ===============

using System.Runtime.InteropSerivices;

public delegate string CallBack_4_Native(string key);

class Foo
{
    private string I_Will_be_called_by_unmanaged_code(string key)
    {
       return key += Environment.NewLine + "Kao!, I'm called by Foo(Hashcode = " + this.GetHashCode();
    }

    [DllImport("native.dll", CallingConvention= CallingConvention.StdCall) ]
    private static extern void SetCallBack( fp_T fp );
   
    private static SetCallBack s_callback = null;
    public static void Main()
    {
       Foo f1 = new Foo();
       //critical!!

        s_callback = new CallBack_4_Native(f1.I_Will_be_called_by_unmanaged_code);
       GC.KeepAlive(s_callback);

       SetCallBack( s_callback );
    }
}


1. 传递给native代码的delegate实例一定要保证长期存活, 具体说就是一定要在被 native代码调用时还没有被.NET的垃圾回收给收回去, 所以不能把它写成是一个临时对象的形式, 我死的很惨的做法是用了.NET 2.0 的自动推断delegate类型的语法:
SetCallBack( a_static_function_whose_prototype_matches_the_delegate );

结果, 调用的时候出现了下面的错误:

垃圾回收无从预期, 因为我的程序比较大比较复杂, 内存压力大, 所以设置了回调函数之后第一次调用时它就已经被回收了.

这之后在codeproject上搜到了一篇文章, 专门处理.NET 1.1中 delegate问题默认为StdCall调用协议的问题, 这篇文章本身对解决上述bug帮助不大, 有用的是下面的一篇回复:

直接击中要害是垃圾回收问题.

文章本身的URL:


作者这篇文章其实是费了老大劲解决了一个完全不必要的问题, 虽然.NET 1.1及以前版本在处理 delegate作为函数指针时固定为StdCall, 但完全可以迁就.NET, 把native模块改造成__stdcall.

.NET 2.0通过引入
UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl, CharSet = CharSet.Ansi)
这个Attribute来处理此问题, 这个Attribute类有一个有用的Property是CharSet , 可以用来指定在回调时如何处理字符串的转换, 例子中故意使用了一个传递char *和返回char *的函数原型, 目的就是演示这一点.

[ .NET and COM: The complete interoperability guide] 是关于PInvoke相关主题的大全, 十分有用, 象上面这两个看似极为偏僻的问题都在书里作为"Caution"给出:
1. 关于 delegate被Marshal成 固定的StdCall调用协议的(p815)

in Version 1.0 of the .NET Framework, delegates are always marshaled as function
pointers with the StdCall convention (also known as Winapi)....

2. 关于垃圾回收的(p814):

The number one mistake made when using delegates as unmanaged function pointers
is to allow them to be garbage collected before unmanaged code is finished using
them. Premature collection of delegates can result in several different symptoms,
depending on the implementation of the unmanaged code using the function pointers.
For example, it could cause an access violation, or it could cause your callback
methods to never be called while the rest of the program runs normally. An easy
way to prevent collection is to assign the delegate reference to a static field...

题外话, 第一次看到上面的错误对话框我对微软的这种做法十分赞赏, 不光报错, 还分析错误, 给出可能的建议, 只是, 呃.. 好事做的还不够彻底, 给出的分析错了!


阅读(1384) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~