一直以来, 在C#中使用 native DLL都只有一种方式, 那就是调用DLL中的导出函数.
后来, 发现了这样的需求, 在C中也需要回调C#中的函数, 主要原因可能有: 1. 应用系统中的某个功能C#已经提供在先, 不应该通过C再实现一遍, 这违反了最重要的软件工程原则之一: DRY(也是我个人高度赞同的原则). 2. 某些功能通过C#的类库实现更方便, 比如对XML的处理. 我的其它文章中讨论过关于C#向C中安装/注册回调函数及C中回调C#.
再后来, 在尝试用C#作一个测试pcre的前端UI时发现, 偶尔的偶尔, 还会有这样的需求: native DLL中不是导出了一个函数, 而是导出了一个变量的符号, 这个变量恰巧又是一个函数指针. 我先是没注意这个问题, 以为 pcre_free跟pcre_compile一样是导出函数, 后来在UI中一运行pcre_free就导出程序容易退出. 逐渐查到真正的原因是pcre_free乃一变量, 而非函数.
但它是PCRE API的一部分, 原型如下:
void (*pcre_free)(void *);
要使用它, 首先要有办法从DLL中得到这个变量的地址, 然后从其地址取得该地址存放的内容, 这个内容才是真正的函数指针:
[DllImport("kernel32.dll")]
private static extern int GetProcAddress(int hModule, string lpProcName);
[DllImport("kernel32.dll", EntryPoint = "LoadLibraryA")] private static extern int LoadLibrary(string lpLibFileName);
|
C#代码中需要这两个导入函数, 目的是取得 pcre_free的地址(而非内容)
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl) ]
delegate void pcre_free_FP(IntPtr compiled_pcre );
private static pcre_free_FP s_pcre_free_func = null;
///
/// Free the returned internal data from pcre_compile
///
/// the return value of pcre_compile
private static void pcre_free(IntPtr compied_pcre)
{
if(s_pcre_free_func == null)
{
try
{
int h = LoadLibrary("pcre.dll");
if( h == 0)
{
Trace.Assert(false, @"Fail to call LoadLibrary(""pcre.dll"")");
return;
}
int addr = GetProcAddress(h, "pcre_free");
if( addr == 0)
{
Trace.Assert(false, @"Fail to call GetProcAddress(h, ""pcre_free"")");
return;
}
IntPtr addr_val = Marshal.ReadIntPtr( (IntPtr)addr);
s_pcre_free_func = Marshal.GetDelegateForFunctionPointer(
addr_val, typeof(pcre_free_FP)) as pcre_free_FP;
}
catch (Exception ex)
{
Trace.Assert(false, ex.ToString());
return ;
}
}
s_pcre_free_func.Invoke(compied_pcre);
}
|
我几乎从没想过这件事: GetProcAddress 也可以得一个变量的地址, 它其实只是得到一个导出符号的地址. 尽管函数名字以偏概全, 而且MSDN上也不提及这一点.
Marshal.ReadIntPtr( (IntPtr)addr); 很重要, 它的返回值才是真正的函数地址, 通过这个函数地址, 才能构造出来一个delegate类型的实例来作为调用pcre_free的代理.
阅读(1337) | 评论(0) | 转发(0) |