huyi

BlogJava 首页 新随笔 联系 聚合 管理
  46 Posts :: 5 Stories :: 5 Comments :: 0 Trackbacks

2005年3月30日 #

find dirname -type f -exec egrep "from[ ]{0,}portfolio.*? " {} \;
posted @ 2005-04-14 17:51 HuYi's Blog 阅读(416) | 评论 (0)编辑 收藏

    .NET推崇这样一种思想:相对于框架而言,语言处于从属、次要的地位。CodeDom名称空间中包含的类是这一思想的集中体现。我们可以用CodeDom构造一个树或图,用System.CodeDom名称空间的类填充它,完成后,用对应各种.NET语言的CodeProvider对象将树结构转换成该种语言的代码。要更换一种语言,简单到只需更换一下最后用到的CodeProvider对象。
  
    设想一下,利用这一技术,我们至少能够:
  
    ·查询存储过程的元数据,构造出一个负责参数绑定的类。
  
    ·查询程序集的manifest,构造出一个对每个函数执行单元测试的类。
  
    ·为开发组用到的每一种语言生成样板代码。
  
    ·只需写一份范例代码,就可以让用户自由选择他们要查看哪一种语言的版本。
  
    ·自定义模板语法,经解析后生成任意语言的代码。
  
    ·如果要学习某种不熟悉的语言,可以生成该语言的代码,然后将它与熟悉的语言比较。
  
    一、基本操作
  
    System.CodeDom名称空间包含了许多以语言中立的形式描述常见程序结构的对象,每一种语言的细节则由与该种语言对应的CodeProvider对象负责处理。例如,CodeConditionStatement包含一个TrueStatements集合、一个FalseStatements集合和一个条件属性(Condition attribute),但不涉及条件语句块要用“end if”还是右花括号“}”结束,这部分细节由CodeProvider处理。有了这一层抽象,我们就可以描述待生成的代码结构,然后将它以任意语言的形式输出,却不必斤斤计较于各种与特定语言有关的细节问题。同时,这种抽象也为我们通过程序改变代码的结构带来了方便。例如,当我们发现某个方法需要增加一个参数时,就可以将参数加入到该方法的Parameters集合,根本无须改动已生成的代码逻辑。
  
    我们在本文中要用到的大部分对象来自System.CodeDom名称空间,其余对象主要来自各个与特定语言有关的名称空间,例如Microsoft.CSharp名称空间、Microsoft.VisualBasic名称空间、Microsoft.JScript名称空间和Microsoft.VJSharp名称空间。所有这些面向特定语言的名称空间都包含各自的CodeProvider对象。最后,System.CodeDom.Complier名称空间定义ICodeGenerator接口,后者用来把生成的代码输出到一个TextWriter对象。
  
    如果我们只要生成一些用于插件或宏的代码片断,可以利用CodeGenerator从Statement、Expression、Type等生成代码。反之,如果我们要生成的是一个完整的文件,则必须从CodeNameSpace对象入手。在本文的例子中,我们将从一个名称空间开始,示范如何加入import语句、声明类、声明方法、声明变量、实现一个循环结构、索引一个数组,最后,我们将这些技术结合起来,得到一个大家都熟悉的程序。
  
    1.1 初始化名称空间
  
    初始化名称空间的代码类似下面这种形式:
  
  private CodeNameSpace InitializeNameSpace(string Name)
  {
   // 初始化CodeNameSpace变量,指定名称空间的名称
   CodeNameSpace CurrentNameSpace = new CodeNamespace (Name);
   // 将一些名称空间加入到要导入的名称空间集合。
   // 各种语言如何导入名称空间的细节由每种语言对应
   // 的CodeProvider分别处理。
   CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System"));
   CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System.Text"));
   return CurrentNameSpace;
  }
  
  
  
    这段代码定义了一个新的名称空间,并导入System和System.Text名称空间。
  
    1.2 创建类
  
    声明一个新类的代码类似下面这种形式:
  
  private CodeTypeDeclaration CreateClass (string Name)
  {
   // 新建一个CodeTypeDeclaration对象,指定要创建的类的名称
   CodeTypeDeclaration ctd = new CodeTypeDeclaration (Name);
   // 指定这个CodeType是一个类,而不是一个枚举变量或struct
   ctd.IsClass = true;
   // 这个类的访问类型是public
   ctd.Attributes = MemberAttributes.Public;
   // 返回新创建的类
   return ctd;
  }
  
  
  
    CreateClass函数新建一个指定名称的类,做好为该类植入方法、属性、事件的准备。
  
    1.3 创建方法
  
    声明一个新函数的代码类似下面这种形式:
  
  private CodeEntryPointMethod CreateMethod()
  {
   // 创建一个方法
   CodeEntryPointMethod method = new CodeEntryPointMethod();
   // 指定该方法的修饰符:public,static
   method.Attributes = MemberAttributes.Public |
   MemberAttributes.Static;
   // 返回新创建的方法
   return method;
  }
  
  
  
    本例创建了一个CodeEntryPointMethod对象。CodeEntryPointMethod对象类似于CodeMemberMethod对象,两者的不同之处在于,CodeProvider会将CodeEntryPointMethod代表的方法作为类的入口点调用,例如作为Sub Main或void main等。对于CodeEntryPointMethod对象,方法的名称默认为Main;对于CodeMemberMethod,方法的名称必须显式指定。
  
    1.4 声明变量
  
    声明一个变量的代码类似下面这种形式:
  
  private CodeVariableDeclarationStatement
   DeclareVariables(System.Type DataType,
   string Name)
  {
   // 为将要创建的变量类型创建一个CodeTypeReference对象,
   // 这使得我们不必去关注该类数据在特定语言环境中的
   // 与数据类型有关的细节问题。
   CodeTypeReference tr = new CodeTypeReference (DataType );
   // CodeVariableDeclarationStatement对象使得我们不必纠缠于
   // 与特定语言有关的下列细节:在该语言的变量声明语句中,
   // 应该是数据类型在前,还是变量名称在前;声明变量时是
   // 否要用到Dim之类的关键词.
   CodeVariableDeclarationStatement Declaration =
   new CodeVariableDeclarationStatement(tr, Name);
   // CodeObjectCreateExpression负责处理所有调用构造器的细节。
   // 大多数情况下应该是new,但有时要使用New。但不管怎样,
   // 我们不必去关注这些由语言类型决定的细节.
   CodeObjectCreateExpression newStatement = new
   CodeObjectCreateExpression ();
   // 指定我们要调用其构造器的对象.
   newStatement.CreateType = tr;
   // 变量将通过调用其构造器的方式初始化.
   Declaration.InitExpression = newStatement;
   return Declaration;
  }
  
  
  
    每一种.NET语言都有其特定的数据类型名称,所有这些数据类型都被映射到公共的.NET语言类型。例如,对于C#中称为int的数据类型,在VB.NET中是Integer,公共的.NET类型是System.Int32。CodeTypeReference对象直接使用.NET公共数据类型,以后由每种语言的CodeProvider将它转换成符合各自语言规范的类型名称。
  
    1.5 初始化数组
  
    初始化一个数组的代码类似下面这种形式:
  
  private void InitializeArray (string Name,
   params char[] Characters )
  {
   // 从参数中传入的字符数组获得一个CodeTypeReference 对象,
   // 以便在生成的代码中复制该数据类型.
   CodeTypeReference tr = new CodeTypeReference (Characters.GetType());
   // 声明一个匹配原始数组的数组
   CodeVariableDeclarationStatement Declaration =
   new CodeVariableDeclarationStatement (tr, Name);
   // CodePrimitiveExpression代表“基本”或值数据类型,
   // 例如char、int、double等等。
   // 我们将用这类基本数据类型构成的一个数组来
   // 初始化我们正在声明的数组。
   CodePrimitiveExpression[] cpe = new
   CodePrimitiveExpression[Characters.Length];
   // 循环遍历原始字符数组,
   // 为CodePrimitiveExpression类型的数组创建对象。
   for (int i = 0; i < Name.Length ; i++)
   {
   // 每一个CodePrimitiveExpression将有一个字符的语言
   // 中立的表示。
   cpe[i] = new CodePrimitiveExpression (Characters[i]);
   }
   // CodeArrayCreateExpression负责调用数组中数据类型的
   // 默认构造器。
   // 由于我们还传入了一个CodePrimitiveExpression的数组,
   // 所以不必指定数组的大小,且数组中的每一个元素都将有
   // 合适的初值。
   CodeArrayCreateExpression array = new
   CodeArrayCreateExpression(tr, cpe);
   // 指定:该CodeArrayCreateExpression将初始化数组变量声明。
   Declaration.InitExpression = array;
   return Declaration;
  }
  
  
  
    1.6 定义循环结构
  
    声明一个循环结构的代码类似下面这种形式:
  
  private CodeIterationStatement CreateLoop(string LoopControlVariableName)
  {
   // 声明一个新的变量,该变量将作为
   // 循环控制变量
   CodeVariableDeclarationStatement Declaration;
   // 声明一个管理所有循环逻辑的CodeIterationStatement
   CodeIterationStatement forloop = new CodeIterationStatement();
   // 为动态声明的变量指定数据类型的另一种方法:
   // 用typeof函数获得该数据类型的Type对象,不必
   // 用到该类数据的变量
   Declaration = new CodeVariableDeclarationStatement(typeof (int),
   LoopControlVariableName);
   // 指定一个简单的初始化表达式:
   // 将新变量设置为0
   Declaration.InitExpression = new CodeSnippetExpression ("0");
   // 这个新声明的变量将用来初始化循环
   forloop.InitStatement = Declaration;
   // CodeAssignStatement用来处理赋值语句。
   // 这里使用的构造器要求提供两个表达式,第一个位于
   // 赋值语句的左边,第二个位于赋值语句的右边。
   // 另一种办法是:调用默认的构造器,然后分别显式设置
   // 左、右两个表达式。
   CodeAssignStatement assignment = new CodeAssignStatement(
   new CodeVariableReferenceExpression(LoopControlVariableName),
   new CodeSnippetExpression (LoopControlVariableName + " + 1" ));
   // 在循环迭代中使用赋值语句。
   forloop.IncrementStatement = assignment;
   // 当循环控制变量超出数组中的字符个数时,
   // 循环结束
   forloop.TestExpression = new CodeSnippetExpression
   (LoopControlVariableName + " < Characters.Length");
   return forloop;
  }
  
  
  
    注意,这里我们用typeof函数直接获得循环控制变量的数据类型的Type对象,而不是通过声明一个CodeTypeReference对象的方式。这是CodeVariableDeclartionStatement的又一个构造器,实际上其构造器的总数多达7种。
  
    1.7 索引数组
  
    索引一个数组的代码类似下面这种形式:
  
  private CodeArrayIndexerExpression
   CreateArrayIndex(string ArrayName, string IndexValue )
  {
   // 新建一个CodeArrayIndexerExpression
   CodeArrayIndexerExpression index = new CodeArrayIndexerExpression ();
   // Indices属性是一个能够支持多维数组的集合。不过这里我们只需要
   // 一个简单的一维数组。
   index.Indices.Add ( new CodeVariableReferenceExpression (IndexValue));
   // TargetObject指定了要索引的数组的名称。
   index.TargetObject = new CodeSnippetExpression (ArrayName);
   return index;
  }
  
  
  
    CodeArrayIndexerExpression对象处理数组索引方式的种种差异。例如,在C#中数组以ArrayName[IndexValue]的方式索引;但在VB.NET中,数组以ArrayName(IndexValue)的方式索引。CodeArrayIndexerExpression允许我们忽略这种差异,将注意力集中到其他更重要的问题,例如要索引哪一个数组、要访问第几个数组元素。
  
    二、装配出树结构
  
    我们可以把前面定义的所有函数加入到一个类,通过构造器初始化,例如:
  
  public CodeDomProvider()
  {
   CurrentNameSpace = InitializeNameSpace("TestSpace");
   CodeTypeDeclaration ctd = CreateClass ("HelloWorld");
   // 把类加入到名称空间
   CurrentNameSpace.Types.Add (ctd);
   CodeEntryPointMethod mtd = CreateMethod();
   // 把方法加入到类
   ctd.Members.Add (mtd);
   CodeVariableDeclarationStatement VariableDeclaration =
   DeclareVariables (typeof (StringBuilder), "sbMessage");
   // 把变量声明加入到方法
   mtd.Statements.Add (VariableDeclaration);
   CodeVariableDeclarationStatement array = InitializeArray
   ("Characters", 'H', 'E', 'L', 'L', 'O', ' ',
   'W', 'O', 'R', 'L', 'D');
   // 把数组加入到方法
   mtd.Statements.Add (array);
   CodeIterationStatement loop = CreateLoop("intCharacterIndex");
   // 把循环加入到方法
   mtd.Statements.Add (loop);
   // 数组索引
   CodeArrayIndexerExpression index = CreateArrayIndex("Characters",
   "intCharacterIndex");
   // 加入一个语句,它将调用sbMessage对象的“Append”方法
   loop.Statements.Add (new CodeMethodInvokeExpression (
   new CodeSnippetExpression ("sbMessage"),"Append",
   index));
   // 循环结束后,输出所有字符追加到sbMessage对象
   // 后得到的结果
   mtd.Statements.Add (new CodeSnippetExpression
   ("Console.WriteLine (sbMessage.ToString())"));
  }
  
  
  
    构造器的运行结果是一个完整的CodeDom树结构。可以看到,至此为止我们的所有操作都独立于目标语言。最后生成的代码将以属性的形式导出。
  
    三、输出生成结果
  
    构造好CodeDom树之后,我们就可以较为方便地将代码以任意.NET语言的形式输出。每一种.NET语言都有相应的CodeProvider对象,CodeProvider对象的CreateGenerator方法能够返回一个实现了ICodeGenerator接口的对象。ICodeGenerator接口定义了用来生成代码的所有方法,而且允许我们定义一个用来简化属性输出的辅助方法。下面的辅助方法GenerateCode负责设置好合适的TextWriter以供输出代码,以字符串的形式返回结果文本。
  
  private string GenerateCode (ICodeGenerator CodeGenerator)
  {
   // CodeGeneratorOptions允许我们指定各种供代码生成器
   // 使用的格式化选项
   CodeGeneratorOptions cop = new CodeGeneratorOptions();
   // 指定格式:花括号的位置
   cop.BracingStyle = "C";
   // 指定格式:代码块的缩进方式
   cop.IndentString = " ";
   // GenerateCodeFromNamespace要求传入一个TextWriter以
   // 容纳即将生成的代码。这个TextWriter可以是一个StreamWriter、
   // 一个StringWriter或一个IndentedTextWriter。
   // StreamWriter可用来将代码输出到文件。
   // StringWriter可绑定到StringBuilder,后者可作为一个变量引用。
   // 在这里,我们把一个StringWriter绑定到StringBuilder sbCode。
   StringBuilder sbCode = new StringBuilder();
   StringWriter sw = new StringWriter(sbCode);
  
   // 生成代码!
   CodeGenerator.GenerateCodeFromNamespace(CurrentNameSpace, sw,cop);
   return sbCode.ToString();
  }
  
  
  
    有了这个辅助函数,要获取各种语言的代码就相当简单了:
  
  public string VBCode
  {
   get
   {
   VBCodeProvider provider = new VBCodeProvider ();
   ICodeGenerator codeGen = provider.CreateGenerator ();
   return GenerateCode (codeGen);
   }
  
  }
  
  public string JScriptCode
  {
   get
   {
   JScriptCodeProvider provider = new JScriptCodeProvider ();
   ICodeGenerator codeGen = provider.CreateGenerator ();
   return GenerateCode(codeGen);
   }
  
  }
  
  public string JSharpCode
  {
   get
   {
   VJSharpCodeProvider provider = new VJSharpCodeProvider ();
   ICodeGenerator codeGen = provider.CreateGenerator ();
   return GenerateCode (codeGen);
   }
  
  }
  
  public string CSharpCode
  {
   get
   {
   CSharpCodeProvider provider = new CSharpCodeProvider();
   ICodeGenerator codeGen = provider.CreateGenerator ();
   return GeneratorCode (codeGen);
   }
  
  }
  
  
  
    四、显示出生成的代码
  
    为输出代码,我们要用到一个简单的.aspx文件,它有四个标签,分别对应一种.NET语言:
  
  <table width="800" border="1">
   <tr>
   <th>VB.NET代码</th>
   </tr>
   <tr >
   <td>
   <asp:Label ID="vbCode" Runat="server" CssClass="code">
   </asp:Label>
   </td>
   </tr>
   <tr>
   <th>
   C#代码</th></tr>
   <tr>
   <td><asp:Label ID="csharpcode" Runat="server" CssClass="code">
   </asp:Label></td>
   </tr>
   <tr>
   <th>J#代码</th></tr>
   <tr >
   <td>
   <asp:Label ID="JSharpCode" Runat="server" CssClass="code">
   </asp:Label>
   </td>
   </tr>
   <tr>
   <th>JScript.NET代码</th>
   </tr>
   <tr>
   <td><asp:Label ID="JScriptCode" Runat="server" CssClass="code">
   </asp:Label></td>
   </tr>
  </table>
  
  
  
    在后台执行的代码中,我们实例化一个前面创建的CodeDomProvider类的实例,把它生成的代码赋值给.aspx页面的相应标签的Text属性。为了使Web页面中显示的代码整齐美观,有必要做一些简单的格式化,替换换行符号、空格等,如下所示:
  
  private string FormatCode (string CodeToFormat)
  {
   string FormattedCode = Regex.Replace (CodeToFormat, "\n", "<br>");
   FormattedCode = Regex.Replace (FormattedCode, " " , " ");
   FormattedCode = Regex.Replace (FormattedCode, ",", ", ");
   return FormattedCode;
  }
  
  
  
    下面把生成的代码显示到Web页面:
  
  private void Page_Load(object sender, System.EventArgs e)
  {
  
   HelloWorld.CodeDomProvider codegen = new HelloWorld.CodeDomProvider ();
   vbCode.Text = FormatCode (codegen.VBCode);
   csharpcode.Text = FormatCode (codegen.CSharpCode);
   JScriptCode.Text = FormatCode (codegen.JScriptCode);
   JSharpCode.Text = FormatCode (codegen.JSharpCode);
   Page.EnableViewState = false;
  }
  
  
  
    输出结果如下:
  
  VB.NET代码
  
  Imports System
  Imports System.Text
  
  Namespace HelloWorld
  
   Public Class Hello_World
  
   Public Shared Sub Main()
   Dim sbMessage As System.Text.StringBuilder = _
   New System.Text.StringBuilder
   Dim Characters() As Char = New Char() {_
   Microsoft.VisualBasic.ChrW(72), _
   Microsoft.VisualBasic.ChrW(69), _
   Microsoft.VisualBasic.ChrW(76), _
   Microsoft.VisualBasic.ChrW(76), _
   Microsoft.VisualBasic.ChrW(79), _
   Microsoft.VisualBasic.ChrW(32), _
   Microsoft.VisualBasic.ChrW(87), _
   Microsoft.VisualBasic.ChrW(79), _
   Microsoft.VisualBasic.ChrW(82), _
   Microsoft.VisualBasic.ChrW(76), _
   Microsoft.VisualBasic.ChrW(68)}
   Dim intCharacterIndex As Integer = 0
   Do While intCharacterIndex < Characters.Length
   sbMessage.Append(Characters(intCharacterIndex))
   intCharacterIndex = intCharacterIndex + 1
   Loop
   Console.WriteLine (sbMessage.ToString())
   End Sub
   End Class
  End Namespace
  
  C#代码
  
  namespace HelloWorld
  {
   using System;
   using System.Text;
  
   public class Hello_World
   {
   public static void Main()
   {
   System.Text.StringBuilder sbMessage = new
   System.Text.StringBuilder();
   char[] Characters = new char[] {
   'H',
   'E',
   'L',
   'L',
   'O',
   ' ',
   'W',
   'O',
   'R',
   'L',
   'D'};
   for (int intCharacterIndex = 0;
   intCharacterIndex < Characters.Length;
   intCharacterIndex = intCharacterIndex + 1)
   {
   sbMessage.Append(Characters[intCharacterIndex]);
   }
   Console.WriteLine (sbMessage.ToString());
   }
   }
  }
  
  J#代码
  
  package HelloWorld;
  import System.*;
  import System.Text.*;
  
  
  public class Hello_World
  {
   public static void main(String[] args)
   {
   System.Text.StringBuilder sbMessage = new
   System.Text.StringBuilder();
   char[] Characters = new char[]
   {
   'H',
   'E',
   'L',
   'L',
   'O',
   ' ',
   'W',
   'O',
   'R',
   'L',
   'D'}
   ;
   for (int intCharacterIndex = 0;
   intCharacterIndex < Characters.Length;
   intCharacterIndex = intCharacterIndex + 1)
   {
   sbMessage.Append(Characters[intCharacterIndex]);
   }
   Console.WriteLine (sbMessage.ToString());
   }
  }
  
  
  JScript.NET代码
  
  
  //@cc_on
  //@set @debug(off)
  
  import System;
  import System.Text;
  
  package HelloWorld
  {
  
   public class Hello_World
   {
  
   public static function Main()
   {
   var sbMessage : System.Text.StringBuilder =
   new System.Text.StringBuilder();
   var Characters : char[] =
   ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D'];
   for (var intCharacterIndex : int = 0;
   ; intCharacterIndex < Characters.Length;
   intCharacterIndex = intCharacterIndex + 1)
   {
   sbMessage.Append(Characters[intCharacterIndex]);
   }
   Console.WriteLine (sbMessage.ToString());
   }
   }
  }
  HelloWorld.Hello_World.Main();
  
  
  
    总结:CodeDom体现了.NET中语言的重要性不如框架的思想。本文示范了如何运用一些常用的CodeDom类,几乎每一个使用CodeDom技术的应用都要用到这些类。作为一种结构化的代码生成技术,CodeDom有着无限的潜能,唯一的约束恐怕在于人们的想象力。
posted @ 2005-04-13 11:16 HuYi's Blog 阅读(502) | 评论 (0)编辑 收藏

     一、发生的背景
    在开发新项目中使用了新的语言开发 C# 和新的技术方案 WEB Service,但是在新项目中,一些旧的模块需要继续使用,一般是采用 C 或 C++ 或 Delphi 编写的,如何利用旧模块对于开发人员来说,有三种可用方法供选择:第一、将 C 或 C++ 函数用 C# 彻底改写一遍,这样整个项目代码比较统一,维护也方便一些。但是尽管微软以及某些书籍说,C# 和 C++ 如何接近,但是改写起来还是很痛苦的事情,特别是 C++ 里的指针和内存操作;第二、将 C 或 C++ 函数封装成 COM,在 C# 中调用COM 比较方便,只是在封装时需要处理 C 或 C++ 类型和 COM 类型之间的转换,也有一些麻烦,另外COM 还需要注册,注册次数多了又可能导致混乱;第三、将 C 或 C++ 函数封装成动态链接库,封装的过程简单,工作量不大。因此我决定采用加载动态链接库的方法实现,于是产生了在 C# 中如何调用自定义的动态链接库问题,我在网上搜索相关主题,发现一篇调用系统 API 的文章,但是没有说明如何解决此问题,在 MSDN 上也没有相关详细说明。基于此,我决定自己从简单出发,逐步试验,看看能否达到自己的目标。
    (说明一点:我这里改写为什么很怕麻烦,我改写的代码是变长加密算法函数,代码有600多行,对算法本身不熟悉,算法中指针和内存操作太多,要想保证算法正确,最可行的方法就是少动代码,否则只要有一点点差错,就不能肯定算法与以前兼容)
  
  二、技术实现
    下面看看如何逐步实现动态库的加载,类型的匹配,动态链接库函数导出的定义,这个不需要多说,大家参考下面宏定义即可:
  
  #define LIBEXPORT_API extern "C" __declspec(dllexport)
  第一步,我先从简单的调用出发,定义了一个简单的函数,该函数仅仅实现一个整数加法求和:
  
  LIBEXPORT_API int mySum(int a,int b){ return a+b;}
  C# 导入定义:
  
  public class RefComm
  {
  [DllImport("LibEncrypt.dll",
      EntryPoint=" mySum ",
      CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
      public static extern int mySum (int a,int b);
  }
  在C#中调用测试:
  
  int iSum = RefComm.mySum(2,3);
  运行查看结果iSum为5,调用正确。第一步试验完成,说明在C#中能够调用自定义的动态链接库函数。
  
  第二步,我定义了字符串操作的函数(简单起见,还是采用前面的函数名),返回结果为字符串:
  
  LIBEXPORT_API char *mySum(char *a,char *b){sprintf(b,"%s",a); return a;}
  C# 导入定义:
  
  public class RefComm
  {
  [DllImport("LibEncrypt.dll",
       EntryPoint=" mySum ",
       CharSet=CharSet.Auto,
       CallingConvention=CallingConvention.StdCall)]
       public static extern string mySum (string a, string b);
  }
  在C#中调用测试:
  
  string strDest="";
  string strTmp= RefComm.mySum("12345", strDest);
  运行查看结果 strTmp 为"12345",但是strDest为空。我修改动态链接库实现,返回结果为串b:
  
  LIBEXPORT_API char *mySum(char *a,char *b){sprintf(b,"%s",a) return b;}
  修改 C# 导入定义,将串b修改为ref方式:
  
  public class RefComm
  {
  [DllImport("LibEncrypt.dll",
       EntryPoint=" mySum ",
       CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
       public static extern string mySum (string a, ref string b);
  }
  在C#中再调用测试:
  
  string strDest="";
  string strTmp= RefComm.mySum("12345", ref strDest);
    运行查看结果 strTmp 和 strDest 均不对,含不可见字符。再修改 C# 导入定义,将CharSet从Auto修改为Ansi:
  
  public class RefComm
  {
  [DllImport("LibEncrypt.dll",
       EntryPoint=" mySum ",
       CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
       public static extern string mySum (string a, string b);
  }
  在C#中再调用测试:
  
  string strDest="";
  string strTmp= RefComm. mySum("12345", ref strDest);
    运行查看结果 strTmp 为"12345",但是串 strDest 没有赋值。第二步实现函数返回串,但是在函数出口参数中没能进行输出。再次修改 C# 导入定义,将串b修改为引用(ref):
  
  public class RefComm
  {
  [DllImport("LibEncrypt.dll",
       EntryPoint=" mySum ",
       CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
       public static extern string mySum (string a, ref string b);
  }
  运行时调用失败,不能继续执行。
  
  第三步,修改动态链接库实现,将b修改为双重指针:
  
  LIBEXPORT_API char *mySum(char *a,char **b){sprintf((*b),"%s",a); return *b;}
  C#导入定义:
  
  public class RefComm
  {
  [DllImport("LibEncrypt.dll",
       EntryPoint=" mySum ",
       CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
       public static extern string mySum (string a, ref string b);
  }
  在C#中调用测试:
  
  string strDest="";
  string strTmp= RefComm. mySum("12345", ref strDest);
    运行查看结果 strTmp 和 strDest 均为"12345",调用正确。第三步实现了函数出口参数正确输出结果。
  
  第四步,修改动态链接库实现,实现整数参数的输出:
  
  LIBEXPORT_API int mySum(int a,int b,int *c){ *c=a+b; return *c;}
  C#导入的定义:
  
  public class RefComm
  {
  [DllImport("LibEncrypt.dll",
       EntryPoint=" mySum ",
       CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
       public static extern int mySum (int a, int b,ref int c);
  }
  在C#中调用测试:
  
  int c=0;
  int iSum= RefComm. mySum(2,3, ref c);
  运行查看结果iSum 和c均为5,调用正确。
    经过以上几个步骤的试验,基本掌握了如何定义动态库函数以及如何在 C# 定义导入,有此基础,很快我实现了变长加密函数在 C# 中的调用,至此目标实现。
  
  三、结论
    在 C# 中调用 C++ 编写的动态链接库函数,如果需要出口参数输出,则需要使用指针,对于字符串,则需要使用双重指针,对于 C# 的导入定义,则需要使用引用(ref)定义。
    对于函数返回值,C# 导入定义和 C++ 动态库函数声明定义需要保持一致,否则会出现函数调用失败。定义导入时,一定注意 CharSet 和 CallingConvention 参数,否则导致调用失败或结果异常。运行时,动态链接库放在 C# 程序的目录下即可,我这里是一个 C# 的动态链接库,两个动态链接库就在同一个目录下运行。
  
  原文出处::http://windend.blogchina.com
posted @ 2005-04-13 11:13 HuYi's Blog 阅读(601) | 评论 (0)编辑 收藏

     摘要: 原文地址http://www.theserverside.com/articles/article.tss?l=SpringFramework You may have heard the buzz this summer around the Spring Framework. In this article, I'll try to explain what Spring sets out ...  阅读全文
posted @ 2005-04-04 10:35 HuYi's Blog 阅读(769) | 评论 (0)编辑 收藏

一、动态加载数据源
1、通过修改注册表加载数据源:
·用户数据源:HKEY_CURRENT_USERSOFTWAREODBCODBC.INI
·系统数据源:HKEY_LOCAL_MACHINESOFTWAREODBCODBC.INI
对于不同类型的数据源,注册表的修改也不同,但基本上要修改两个地方,一个是在ODBC.INI子键下建立一个与数据源描述名同名的子键,并在该子键下建立与数据源配置相关的项;另一个是在\ODBC.INIODBC Data Sources子键下建立一个新项以便告诉驱动程序管理器ODBC数据源的类型。
2、通过ODBC API加载:Windows系统子目录下的动态链接库Odbcinst.dll提供了一个可以动态增加、修改和删除数据源的函数SQLConfigDataSource,由于VC的默认库文件中不包含此函数,因此使用前需将Odbcinst.h文件包含在工程的头文件中,在工程的setting属性框Link页的Object/library module编辑框中增加Odbc32.lib,同时保证系统目录system32下有文件Odbccp32.dll
3、文件数据源的连接:除了ODBC管理器,还可以通过SQLDriverConnect来添加文件数据源。
二、ODBC  API编程
如果一个ODBC API函数执行成功,则返回SQL_SUCCESSSQL_SUCCESS_WITH_INFOSQL_SUCCESS指示可通过诊断记录获取有关操作的详细信息,SQL_SUCCESS_WITH_INFO指示应用程序执行结果带有警告信息,可通过诊断记录获取详细信息。如果函数调用失败,返回码为SQL_ERROR
一般,编写ODBC程序主要有一下几个步骤:
1、   分配环境句柄:声明一个SQLHENV的变量,调用函数SQLAllocHandle
设置环境属性:完成环境分配后,用函数SQLSetEnvAttr设置环境属性,注册ODBC版本号。
释放环境句柄:完成数据访问任务时,应调用SQLFreeHandle释放前面分配的环境。
2、       分配连接句柄:声明一个SQLHDBC类型的变量,调用SQLAllocHandle函数分配句柄。
设置连接属性:所有连接属性都可通过函数SQLSetConnectAttr设置,调用函数SQLGetConnectAttr可获取这些连接属性的当前设置值。
3、   连接数据源:对于不同的程序和用户接口,可以用不同的函数建立连接
SQLConnect:该函数只要提供数据源名称、用户ID和口令,就可以进行连接了。
SQLDriverConnect:该函数用一个连接字符串建立至数据源的连接,它可以让用户输入必要的连接信息,使用系统中还没定义的数据源。
SQLBrowseConnect:该函数支持以一种迭代的方式获取到数据源的连接,直到最后建立连接,它基于客户机/服务器体系结构,因此本地数据库不支持该函数。
4、   准备并执行SQL语句
A、  分配语句句柄:语句句柄是通过调用SQLAllocHandle函数分配的。
函数SQLGetStmrrAttrSQLSetStmrrAttr用来获取和设置一个语句句柄的选项,使用完,调用SQLFreeHandle释放该句柄。
B、  执行SQL语句
SQLExecDirect:该函数直接执行SQL语句,对于只执行一次的SQL语句来说,该函数是执行最快的方法。
SQLPrepareSQLExecute:对于需要多次执行的SQL语句来说,可先调用SQLPrepare准备SQL语句的执行,用SQLExecute执行准备好的语句。
C、  使用参数:使用参数可以使一条SQL语句多次执行,得到不同的结果。
函数SQLBindParameter负责为参数定义变量,将一段SQL语句中的一个参数标识符("?")捆绑在一起,实现参数值的传递。
5   获取记录集
A   绑定列:首先必须分配与记录集中字段相对应的变量,然后通过函数SQLBindCol将记录字段同程序变量绑定在一起,对于长记录字段,可以通过调用函数SQLGetData直接取回数据。
绑定字段可以根据自己的需要全部绑定,也可以绑定其中的某几个字段。
通过调用函数SQLBindCol将变量地址值赋为NULL,可以结束对一个记录字段的绑定,通过调用函数SQLFreeStmt,将其中选项设为SQL_UNBIND,或者直接释放句柄,都会结束所有记录字段的绑定。
BSQLFetch:该函数用于将记录集的下一行变成当前行,并把所有捆绑过的数据字段的数据拷贝到相应的缓冲区。
C 光标:应用程序获取数据是通过光标(Cursor)来实现的,在ODBC中,主要有3种类型的光标:单向光标、可滚动光标和块光标。
有些应用程序不支持可滚动光标和块光标,ODBC SDK提供了一个光标库(ODBCCR32.DLL),在应用程序中可通过设置连接属性(SQL_STTR_ODBC_CURSOR)激活光标库。
6  记录的添加、删除和更新:数据源数据更新可通过3种方式:通过SQLExecDirect函数使用相应的SQL语句;调用SQLSetPos函数实现记录集定义更新;调用SQLBulkOperations函数实现数据更新。
第一种方式适用于任何ODBC数据源,后两种方式有的数据源不支持,可调用SQLGetInfo确定数据源。
SQLBulkOperations:该函数操作基于当前行集,调用前,须先调用SQLFetchSQLFetchScroll获取。
函数调用后,块光标的位置变为未定义状况,因此,应该先调用函数SQLFetchScroll设定光标位置。
7、错误处理:每个ODBC API函数都能产生一系列反映操作信息的诊断记录,可以用SQLGetDiagField函数获取诊断记录中特定的域,另外,可以使用SQLGetDiagRec获取诊断记录中一些常用的域。
8、事务处理:事务提交有两种方式:自动提交模式和手动提交模式。应用程序可通过调用函数SQLSetConnectAttr设定连接属性SQL_ATTR_AUTOCOMMIT,自动提交模式是默认的连接属性设置,对于所有的ODBC驱动程序都能适应这种模式下,所有语句都是作为一个独立的事务进行处理的。
手动提交模式把一组SQL语句放入一个事务中,程序必须调用函数SQLEenTran明确地终止一个事务。若使用多个激活的事务,就必须建立多个连接,每一个连接包含一个事务。
9、断开数据连接并释放环境句柄:完成数据库操作后,可调用SQLDisconnect函数关闭同数据库的连接。
posted @ 2005-04-04 09:55 HuYi's Blog 阅读(839) | 评论 (0)编辑 收藏

1 Overview

1. What is AspectJ?

AspectJ(tm) is a simple and practical extension to the Java(tm) programming language that adds to Java aspect-oriented programming (AOP) capabilities. AOP allows developers to reap the benefits of modularity for concerns that cut across the natural units of modularity. In object-oriented programs like Java, the natural unit of modularity is the class. In AspectJ, aspects modularize concerns that affect more than one class.

You compile your program using the AspectJ compiler (perhaps using the supported development environments) and then run it, supplying a small (< 100K) runtime library.

The AspectJ technologies include a compiler (ajc), a debugger (ajdb), a documentation generator (ajdoc), a program structure browser (ajbrowser), and integration with Eclipse, Sun-ONE/Netbeans, GNU Emacs/XEmacs, JBuilder, and Ant.

2. What are the benefits of using AspectJ?

AspectJ can be used to improve the modularity of software systems.

Using ordinary Java, it can be difficult to modularize design concerns such as

  • system-wide error-handling

  • contract enforcement

  • distribution concerns

  • feature variations

  • context-sensitive behavior

  • persistence

  • testing

The code for these concerns tends to be spread out across the system. Because these concerns won't stay inside of any one module boundary, we say that they crosscut the system's modularity.

AspectJ adds constructs to Java that enable the modular implementation of crosscutting concerns. This ability is particularly valuable because crosscutting concerns tend to be both complex and poorly localized, making them hard to deal with.

3. Can AspectJ work with any Java program?

AspectJ has been designed as a compatible extension to Java. By compatible, we mean

upward compatible All legal Java programs are legal AspectJ programs.
platform compatible All legal AspectJ programs run on standard Java virtual machines.
tool compatible Existing tools can be extended to work with AspectJ.
programmer compatible Programming in AspectJ feels natural to Java programmers.

The AspectJ tools run on any Java 2 Platform compatible platform. The AspectJ compiler produces classes that run on any Java 1.1 (or later) compatible platform.

4. How is AspectJ licensed?

AspectJ 1.1 source code and documentation is available under the Common Public License 1.0.

The AspectJ 1.0 tools are open-source software available under the Mozilla Public License 1.1. That documentation is available under a separate license that precludes for-profit or commercial redistribution.

Most users only want to use AspectJ to build programs they distribute. There are no restrictions here. When you distribute your program, be sure to include all the runtime classes from the aspectjrt.jar for that version of AspectJ. When distributing only the runtime classes, you need not provide any notice that the program was compiled with AspectJ or includes binaries from the AspectJ project, except as necessary to preserve the warranty disclaimers in our license.

5. What is the AspectJ Project?

AspectJ is based on over ten years of research at Xerox Palo Alto Research Center as funded by Xerox, a U.S. Government grant (NISTATP), and a DARPA contract.

It has evolved through open-source releases to a strong user community and now operates as an open source project at http://eclipse.org/aspectj The AspectJ team works closely with the community to ensure AspectJ continues to evolve as an effective aspect-oriented programming language and tool set.

The latest release is 1.2 which can be downloaded from the AspectJ project page, including sources as described Q:How do I get and compile the source code for AspectJ?. Development is focused on supporting applications, improving quality and performance, enhancing integration with IDE's, and building the next generations of the language.

2 Quick Start

1. What Java versions does AspectJ require and support?

The AspectJ compiler produces programs for any released version of the Java platform (jdk1.1 and later). When running, your program classes must be able to reach classes in the small (< 100K) runtime library (aspectjrt.jar) from the distribution. The tools themselves require J2SE 1.3 or later to run, but the compiler can produce classes for any 1.1-compliant version of the Java platform.

2. How do I download and install AspectJ?

From AspectJ's web page , download the AspectJ distribution. The jar file is installed by executing

            java -jar jar file name
          

Do not try to extract the jar file contents and then attempt to execute java org.aspectj.tools.Main. (A NoClassDefFoundError exception will be thrown.) The AspectJ distribution is not designed to be installed this way. Use the java -jar form shown above.

To uninstall, remove the files the installer wrote in your file system. In most cases, you can delete the top-level install directory (and all contained files), after you remove any new or updated files you want to keep. On Windows, no registry settings were added or changed, so nothing needs to be undone. Do not install over prior versions, which might have different files. Delete the prior version first.

3. How should I start using AspectJ?

Many users adopt AspectJ incrementally, first using it to understand and validate their systems (relying on it only in development) and then using it to implement crosscutting concerns in production systems. AspectJ has been designed to make each step discrete and beneficial.

In order of increasing reliance, you may use AspectJ:

  • In the development process Use AspectJ to trace or log interesting information. You can do this by adding simple AspectJ code that performs logging or tracing. This kind of addition may be removed ("unplugged") for the final build since it does not implement a design requirement; the functionality of the system is unaffected by the aspect.

  • As an ancillary part of your system Use AspectJ to more completely and accurately test the system. Add sophisticated code that can check contracts, provide debugging support, or implement test strategies. Like pure development aspects, this code may also be unplugged from production builds. However, the same code can often be helpful in diagnosing failures in deployed production systems, so you may design the functionality to be deployed but disabled, and enable it when debugging.

  • As an essential part of your system Use AspectJ to modularize crosscutting concerns in your system by design. This uses AspectJ to implement logic integral to a system and is delivered in production builds.

一:总揽

1什么是AspectJ
AspectJ是JAVA程序语言的简单并且实用的一种扩充。它增加了JAVA aspect-oriented progamming(AOP)即面向方面编程的工能。它能使程序员从模块单元的横切关系上获知益。像面向对象编语言JAVA,模块之间的一种自然的关联是建立在类。也就是class上的。在AspectJ中,这种关联会涉及到一多个模块。

你可以用aspectJ进行你的程序开发(不包括运行环境),一个运行库大约不到100k.

AspectJ 技术包括一个编译器ajc,一个调式器ajdb,一个通用文档ajdoc,一个程序结构浏览器ajbrowser,还有一个支持这个架构的运行环境,Eclipse,SUN-ONE/Netbeans,GUNEmacs/XEmacs,JBuilder,和Ant.

2使用AspectJ的好处


AspectJ可以用于改进软件模块的性能。
使用JAVA语言,在 处理系统异常(system-wide error-handling),契约式编程(contract enforcement),分布式(distribution concerns),可变性(feature variations),上下文相关行为(context-sensitive behavior),持久化(persistence),测试(testing)在这些方面,模块之间的关联很难设计。在这些方面,代码不能分布在整个系统中,而不能封装在一个类中,这便是系统中的一种横切关系。

AspectJ提供的构架使模块能够实现横切关系。实现系统中的横切关系是很有价值并且必要的,因为系统中这些分散于各个模块的代码,使我们的设计变得复杂,局部混乱,并且乱于管理与维护。

3AspectJ能应该于任何系统中吗?


AspectJ可以和任何JAVA程序兼容。这种兼容是包括以下几个方面

向上兼容:所有标准的JAVA程序,都是标准的Aspect程序。
平台兼容:所有标准的Aspect程序,都是运行于JAVA虚拟机上。
工具兼容:现有工具可以被延用到AspectJ中使用。
程序兼容:在AspectJ中编写的程序,对于JAVA程序来说,感觉也会很自然,很熟悉。


二:快速入门

1.AspectJ要求并支持的JAVA版本?

AspectJ可以为JAVA平台1.1以后的版本发布代码。运行时,你要使自己程序的类可以访问到AspectJ运行类aspectj.jar(这个包小于100k).

2.怎样下载AspectJ的安装文件?

从AspectJ的网页上,下载AspectJ的正式版。java-jar×××解压后,安装。


不要尝试分解jar文件的内容,或者分离工具包中的类。(NoClassDefFoundError exception 没有创建的类 这个异常会被抛出。 )不能这样安装.还是要按着解压后安装的方式,进行安装。

卸载时,要删除掉已经写入的文件。在大多情况下,再你把要保存的新的或刚刚更新过的文件移开时,你可以删除最上层的安装目录。在windows下,不用注册,所以不用做什么其它事情就可以正常使用了。不要覆盖安装当前版本,因为可能出现新的文件。安装新版本前,请删除原来的文件。

3怎样开始使用AspectJ

许多用户常常会先用AspectJ来了解验证自己的系统,然后再去用AspectJ去执行横切。AspectJ设计本意使设计阶段连续不间段的获利。

在以下几个方面你可以自信的使用AspectJ:

1在系统开发过程中:使用AspectJ来描述或记录特殊信息。你可以通过简单的AspectJ代码来实现日志记录和信息描述。如果系统不需要执行这些功能的时候,也可以把这个新增的功能最终的系统中拿掉,而原来的系统不会受任何的影响。

2系统的辅助部分:使用AspectJ来完善并且精确的测试你的系统。AspectJ增加了精典的代码来检查系统规范,支持检查系统漏洞,或者执行系统测试。就像AOP的本质一样,这些代码一样可以被从最终的系统中删除。这些代码在检查现有系统的问题的时候还是很有帮助的,所以我们可以把这功能设计出来,但是不在系统运行时,有实际的作用,而只在测试时产生作用。

3系统核心部分:使用AspectJ来对模块进行横切设计。这种使用AspectJ来对系统执行的逻辑规范将被包括在系统中。

posted @ 2005-04-01 15:58 HuYi's Blog 阅读(1010) | 评论 (0)编辑 收藏

  摘要: 本文针对HOOK技术在VC编程中的应用进行讨论,并着重对应用比较广泛的全局HOOK做了阐述。

  引言

  Windows操作系统是建立在事件驱动机制之上的,系统各部分之间的沟通也都是通过消息的相互传递而实现的。但在通常情况下,应用程序只能处理来自进程内部的消息或是从其他进程发过来的消息,如果需要对在进程外传递的消息进行拦截处理就必须采取一种被称为HOOK(钩子)的技术。钩子是Windows操作系统中非常重要的一种系统接口,用它可以轻松截获并处理在其他应用程序之间传递的消息,并由此可以完成一些普通应用程序难以实现的特殊功能。基于钩子在消息拦截处理中的强大功能,本文即以VC++ 6.0为编程背景对钩子的基本概念及其实现过程展开讨论。为方便理解,在文章最后还给出了一个简单的有关鼠标钩子的应用示例。

  钩子的基本原理

  钩子的本质是一段用以处理系统消息的程序,通过系统调用,将其挂入到系统。钩子的种类有很多,每一种钩子负责截获并处理相应的消息。钩子机制允许应用程序截获并处理发往指定窗口的消息或特定事件,其监视的窗口即可以是本进程内的也可以是由其他进程所创建的。在特定的消息发出,并在到达目的窗口之前,钩子程序先行截获此消息并得到对其的控制权。此时在钩子函数中就可以对截获的消息进行各种修改处理,甚至强行终止该消息的继续传递。

  任何一个钩子都由系统来维护一个指针列表(钩子链表),其指针指向钩子的各个处理函数。最近安装的钩子放在链的开始,最早安装的钩子则放在最后,当钩子监视的消息出现时,操作系统调用链表开始处的第一个钩子处理函数进行处理,也就是说最后加入的钩子优先获得控制权。在这里提到的钩子处理函数必须是一个回调函数(callback function),而且不能定义为类成员函数,必须定义为普通的C函数。在使用钩子时可以根据其监视范围的不同将其分为全局钩子和线程钩子两大类,其中线程钩子只能监视某个线程,而全局钩子则可对在当前系统下运行的所有线程进行监视。显然,线程钩子可以看作是全局钩子的一个子集,全局钩子虽然功能强大但同时实现起来也比较烦琐:其钩子函数的实现必须封装在动态链接库中才可以使用。

  钩子的安装与卸载

  由于全局钩子具有相当的广泛性而且在功能上完全覆盖了线程钩子,因此下面就主要对应用较多的全局钩子的安装与使用进行讨论。前面已经提过,操作系统是通过调用钩子链表开始处的第一个钩子处理函数而进行消息拦截处理的。因此,为了设置钩子,只需将回调函数放置于链首即可,操作系统会使其首先被调用。在具体实现时由函数SetWindowsHookEx()负责将回调函数放置于钩子链表的开始位置。SetWindowsHookEx()函数原型声明如下:

HHOOK SetWindowsHookEx(int idHook;
HOOKPROC lpfn;
HINSTANCE hMod;
DWORD dwThreadId);

  其中:参数idHook 指定了钩子的类型,总共有如下13种:

   WH_CALLWNDPROC 系统将消息发送到指定窗口之前的"钩子"
   WH_CALLWNDPROCRET 消息已经在窗口中处理的"钩子"
   WH_CBT 基于计算机培训的"钩子"
   WH_DEBUG 差错"钩子"
   WH_FOREGROUNDIDLE 前台空闲窗口"钩子"
   WH_GETMESSAGE 接收消息投递的"钩子"
   WH_JOURNALPLAYBACK 回放以前通过WH_JOURNALRECORD"钩子"记录的输入消息
   WH_JOURNALRECORD 输入消息记录"钩子"
   WH_KEYBOARD 键盘消息"钩子"
   WH_MOUSE 鼠标消息"钩子"
   WH_MSGFILTER 对话框、消息框、菜单或滚动条输入消息"钩子"
   WH_SHELL 外壳"钩子"
   WH_SYSMSGFILTER 系统消息"钩子"

  参数lpfn为指向钩子处理函数的指针,即回调函数的首地址;参数hMod则标识了钩子处理函数所处模块的句柄;第四个参数dwThreadId 指定被监视的线程,如果明确指定了某个线程的ID就只监视该线程,此时的钩子即为线程钩子;如果该参数被设置为0,则表示此钩子为监视系统所有线程的全局钩子。此函数在执行完后将返回一个钩子句柄。

  虽然对于线程钩子并不要求其象全局钩子一样必须放置于动态链接库中,但是推荐其也在动态链接库中实现。因为这样的处理不仅可使钩子可为系统内的多个进程访问,也可以在系统中被直接调用,而且对于一个只供单进程访问的钩子,还可以将其钩子处理过程放在安装钩子的同一个线程内,此时SetWindowsHookEx()函数的第三个参数也就是该线程的实例句柄。

  在SetWindowsHookEx()函数完成对钩子的安装后,如果被监视的事件发生,系统马上会调用位于相应钩子链表开始处的钩子处理函数进行处理,每一个钩子处理函数在进行相应的处理时都要考虑是否需要把事件传递给下一个钩子处理函数。如果要传递,就通过函数CallNestHookEx()来解决。尽管如此,在实际使用时还是强烈推荐无论是否需要事件传递而都在过程的最后调用一次CallNextHookEx( )函数,否则将会引起一些无法预知的系统行为或是系统锁定。该函数将返回位于钩子链表中的下一个钩子处理过程的地址,至于具体的返回值类型则要视所设置的钩子类型而定。该函数的原型声明如下:

LRESULT CallNextHookEx(HHOOK hhk;int nCode;WPARAM wParam;LPARAM lParam);

  其中,参数hhk为由SetWindowsHookEx()函数返回的当前钩子句柄;参数nCode为传给钩子过程的事件代码;参数wParam和lParam 则为传给钩子处理函数的参数值,其具体含义同设置的钩子类型有关。

  最后,由于安装钩子对系统的性能有一定的影响,所以在钩子使用完毕后应及时将其卸载以释放其所占资源。释放钩子的函数为UnhookWindowsHookEx(),该函数比较简单只有一个参数用于指定此前由SetWindowsHookEx()函数所返回的钩子句柄,原型声明如下:

BOOL UnhookWindowsHookEx(HHOOK hhk);

  鼠标钩子的简单示例

  最后,为更清楚展示HOOK技术在VC编程中的应用,给出一个有关鼠标钩子使用的简单示例。在钩子设置时采用的是全局钩子。下面就对鼠标钩子的安装、使用以及卸载等过程的实现进行讲述:

  由于本例程需要使用全局钩子,因此首先构造全局钩子的载体--动态链接库。考虑到 Win32 DLL与Win16 DLL存在的差别,在Win32环境下要在多个进程间共享数据,就必须采取一些措施将待共享的数据提取到一个独立的数据段,并通过def文件将其属性设置为读写共享:

#pragma data_seg("TestData")
HWND glhPrevTarWnd=NULL; // 窗口句柄
HWND glhHook=NULL; // 鼠标钩子句柄
HINSTANCE glhInstance=NULL; // DLL实例句柄
#pragma data_seg()
……
SECTIONS // def文件中将数据段TestData设置为读写共享
TestData READ WRITE SHARED

  在安装全局鼠标钩子时使用函数SetWindowsHookEx(),并设定鼠标钩子的处理函数为MouseProc(),安装函数返回的钩子句柄保存于变量glhHook中:

void StartHook(HWND hWnd)
{
……
glhHook=(HWND)SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0);
}

  鼠标钩子安装好后,在移动、点击鼠标时将会发出鼠标消息,这些消息均经过消息处理函数MouseProc()的拦截处理。在此,每当捕获到系统各线程发出的任何鼠标消息后首先获取当前鼠标所在位置下的窗口句柄,并进一步通过GetWindowText()函数获取到窗口标题。在处理函数完成后,通过CallNextHookEx()函数将事件传递到钩子列表中的下一个钩子处理函数:

LRESULT WINAPI MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lParam;
if(nCode>=0)
{
HWND glhTargetWnd=pMouseHook->hwnd;
//取目标窗口句柄
HWND ParentWnd=glhTargetWnd;
while(ParentWnd !=NULL)
{
glhTargetWnd=ParentWnd;
//取应用程序主窗口句柄
ParentWnd=GetParent(glhTargetWnd);
}
if(glhTargetWnd!=glhPrevTarWnd)
{
char szCaption[100];
//取目标窗口标题
GetWindowText(glhTargetWnd,szCaption,100);
……
}
}
//继续传递消息
return CallNextHookEx((HHOOK)glhHook,nCode,wParam,lParam);
}

  最后,调用UnhookWindowsHookEx()函数完成对钩子的卸载:

void StopHook()
{
……
UnhookWindowsHookEx((HHOOK)glhHook);
}

  现在完成的是鼠标钩子的动态链接库,经过编译后需要经应用程序的调用才能实现对当前系统下各线程间鼠标消息的拦截处理。这部分同普通动态链接库的使用没有任何区别,在将其加载到进程后,首先调用动态链接库的StartHook()函数安装好钩子,此时即可对系统下的鼠标消息实施拦截处理,在动态链接库被卸载即终止鼠标钩子时通过动态链接库中的StopHook()函数卸载鼠标钩子。

  经上述编程,在安装好鼠标钩子后,鼠标在移动到系统任意窗口上时,马上就会通过对鼠标消息的拦截处理而获取到当前窗口的标题。实验证明此鼠标钩子的安装、使用和卸载过程是正确的。

  小结

  钩子,尤其是系统钩子具有相当强大的功能,通过这种技术可以对几乎所有的Windows系统消息和事件进行拦截处理。这种技术广泛应用于各种自动监控系统对进程外消息的监控处理。本文只对钩子的一些基本原理和一般的使用方法做了简要的探讨,感兴趣的读者完全可以在本文所述代码基础之上用类似的方法实现对诸如键盘钩子、外壳钩子等其他类型钩子的安装与使用。本文所述代码在Windows 98下由Microsoft Visual C++ 6.0编译通过。

posted @ 2005-03-30 20:55 HuYi's Blog 阅读(824) | 评论 (0)编辑 收藏

一、APIHOOK之dll部分
 
//////////////////////////////// APIHook_Dll.cpp ////////////////////////////////////////
//                             rivershan写于2002.9.23                                  //
/////////////////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "APIHook_Dll.h"

#include <ImageHlp.h>
#include <tlhelp32.h>

#pragma comment(lib,"ImageHlp") //定义全局共享数据段

#pragma data_seg("Shared")
HMODULE hmodDll=NULL;
HHOOK hHook=NULL;

#pragma data_seg()

#pragma comment(linker,"/Section:Shared,rws") //设置全局共享数据段的属性

///////////////////////////////////// DllMain 函数 /////////////////////////////////////////
//dll的入口点
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
 switch(ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:
  //if(sHook)  
  
 case DLL_PROCESS_DETACH:
  UnInstallHook();
  break;
 }
 hmodDll=hModule;
    return TRUE;
}

///////////////////////////////////// HookOneAPI 函数 /////////////////////////////////////////
//进行IAT转换的关键函数,其参数含义:
//pszCalleeModuleName:需要hook的模块名
//pfnOriginApiAddress:要替换的自己API函数的地址
//pfnDummyFuncAddress:需要hook的模块名的地址
//hModCallerModule:我们要查找的模块名称,如果没有被赋值,
//     将会被赋值为枚举的程序所有调用的模块

void WINAPI HookOneAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule)
{
 ULONG size;

 //获取指向PE文件中的Import中IMAGE_DIRECTORY_DESCRIPTOR数组的指针

 PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
  ImageDirectoryEntryToData(hModCallerModule,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);

 if (pImportDesc == NULL)
  return;

 //查找记录,看看有没有我们想要的DLL

 for (;pImportDesc->Name;pImportDesc++)
 {
  LPSTR pszDllName = (LPSTR)((PBYTE)hModCallerModule+pImportDesc->Name);
  if (lstrcmpiA(pszDllName,pszCalleeModuleName) == 0)
   break;
 }

 if (pImportDesc->Name == NULL)
 {
  return;
 }

 //寻找我们想要的函数

 PIMAGE_THUNK_DATA pThunk =
  (PIMAGE_THUNK_DATA)((PBYTE)hModCallerModule+pImportDesc->FirstThunk);//IAT
 for (;pThunk->u1.Function;pThunk++)
 {
  //ppfn记录了与IAT表项相应的函数的地址

  PROC * ppfn= (PROC *)&pThunk->u1.Function;  
  if (*ppfn == pfnOriginApiAddress)
  {
   //如果地址相同,也就是找到了我们想要的函数,进行改写,将其指向我们所定义的函数

   WriteProcessMemory(GetCurrentProcess(),ppfn,&(pfnDummyFuncAddress),
    sizeof(pfnDummyFuncAddress),NULL);
   return;
  }
 }
}

//查找所挂钩的进程所应用的dll模块的

BOOL WINAPI HookAllAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule)
{
 if (pszCalleeModuleName == NULL)
 {
  return FALSE;
 }
 if (pfnOriginApiAddress == NULL)
 {
  return FALSE;
 }
 //如果没传进来要挂钩的模块名称,枚举被挂钩进程的所有引用的模块,
 //并对这些模块进行传进来的相应函数名称的查找
 
 if (hModCallerModule == NULL)
 {
  MEMORY_BASIC_INFORMATION mInfo;
  HMODULE hModHookDLL;
  HANDLE hSnapshot;
  MODULEENTRY32 me = {sizeof(MODULEENTRY32)};
  //MODULEENTRY32:描述了一个被指定进程所应用的模块的struct

  VirtualQuery(HookOneAPI,&mInfo,sizeof(mInfo));
  hModHookDLL=(HMODULE)mInfo.AllocationBase;
  
  hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,0);
  BOOL bOk = Module32First(hSnapshot,&me);
  while (bOk)
  {
   if (me.hModule != hModHookDLL)
   {
    hModCallerModule = me.hModule;//赋值
    //me.hModule:指向当前被挂钩进程的每一个模块
    HookOneAPI(pszCalleeModuleName,pfnOriginApiAddress,
     pfnDummyFuncAddress,hModCallerModule);
   }
   bOk = Module32Next(hSnapshot,&me);
  }
  return TRUE;  
 }
 //如果传进来了,进行查找
 else
 {
  HookOneAPI(pszCalleeModuleName,pfnOriginApiAddress,
    pfnDummyFuncAddress,hModCallerModule);
  return TRUE;
 }
 return FALSE;
}

//////////////////////////////////// UnhookAllAPIHooks 函数 /////////////////////////////////////
//通过使pfnDummyFuncAddress与pfnOriginApiAddress相等的方法,取消对IAT的修改
BOOL WINAPI UnhookAllAPIHooks(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
         PROC pfnDummyFuncAddress,HMODULE hModCallerModule)
{
 PROC temp;
 temp = pfnOriginApiAddress;
 pfnOriginApiAddress = pfnDummyFuncAddress;
 pfnDummyFuncAddress = temp;
 return HookAllAPI(pszCalleeModuleName,pfnOriginApiAddress,
  pfnDummyFuncAddress,hModCallerModule);
}

////////////////////////////////// GetMsgProc 函数 ////////////////////////////////////////
//钩子子程。与其它钩子子程不大相同,没做什么有意义的事情,继续调用下一个钩子子程,形成循环
LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam)
{
 return CallNextHookEx(hHook,code,wParam,lParam);
}

//////////////////////////////////// InstallHook 函数 /////////////////////////////////////
//安装或卸载钩子,BOOL IsHook参数是标志位
//对要钩哪个API函数进行初始化
//我们这里装的钩子类型是WH_GETMESSAGE
void __declspec(dllexport) WINAPI InstallHook(BOOL IsHook,DWORD dwThreadId)
{
 if(IsHook)
 {
 hHook=SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)GetMsgProc,hmodDll,dwThreadId);
 
 //GetProcAddress(GetModuleHandle("GDI32.dll"),"ExtTextOutA"):取得要钩的函数在所在dll中的地址
 
 HookAllAPI("GDI32.dll",GetProcAddress(GetModuleHandle("GDI32.dll"),
  "TextOutW"),(PROC)&H_TextOutW,NULL);
 HookAllAPI("GDI32.dll",GetProcAddress(GetModuleHandle("GDI32.dll"),
  "TextOutA"),(PROC)&H_TextOutA,NULL);
 }
 else
 {
  UnInstallHook();
  UnhookAllAPIHooks("GDI32.dll",GetProcAddress(GetModuleHandle("GDI32.dll"),
   "TextOutW"),(PROC)&H_TextOutW,NULL);
  UnhookAllAPIHooks("GDI32.dll",GetProcAddress(GetModuleHandle("GDI32.dll"),
   "TextOutA"),(PROC)&H_TextOutA,NULL);
 }
}

///////////////////////////////////// UnInstallHook 函数 ////////////////////////////////////
//卸载钩子
BOOL WINAPI UnInstallHook()
{
 UnhookWindowsHookEx(hHook);
 return TRUE;
}

///////////////////////////////////// H_TextOutA 函数 /////////////////////////////////////////
//我们的替换函数,可以在里面实现我们所要做的功能
//这里我做的是显示一个对话框,指明是替换了哪个函数
BOOL WINAPI H_TextOutA(HDC hdc,int nXStart,int nYStart,LPCSTR lpString,int cbString)
{
 MessageBox(NULL,"TextOutA","APIHook_Dll ---rivershan",MB_OK);
 TextOutA(hdc,nXStart,nYStart,lpString,cbString);//返回原来的函数,以显示字符
 return TRUE;
}

///////////////////////////////////// H_TextOutW 函数 /////////////////////////////////////////
//同上
BOOL WINAPI H_TextOutW(HDC hdc,int nXStart,int nYStart,LPCWSTR lpString,int cbString)
{
 MessageBox(NULL,"TextOutW","APIHook_Dll ---rivershan",MB_OK);
 TextOutW(hdc,nXStart,nYStart,lpString,cbString);//返回原来的函数,以显示字符
 return TRUE;
}

**********************************************************************************************
**********************************************************************************************

//////////////////////////////// APIHook_Dll.h ////////////////////////////////////////
//                             rivershan写于2002.9.23                                  //
/////////////////////////////////////////////////////////////////////////////////////////

//dll头文件,用于声明函数

void __declspec(dllexport) WINAPI InstallHook(BOOL,DWORD);
BOOL WINAPI UnInstallHook();
LRESULT CALLBACK GetMsgProC(int code,WPARAM wParam,LPARAM lParam);

void WINAPI HookOneAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule);
BOOL WINAPI HookAllAPI(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
        PROC pfnDummyFuncAddress,HMODULE hModCallerModule);
BOOL WINAPI UnhookAllAPIHooks(LPCTSTR pszCalleeModuleName,PROC pfnOriginApiAddress,
         PROC pfnDummyFuncAddress,HMODULE hModCallerModule);

BOOL WINAPI H_TextOutA(HDC, int, int, LPCSTR, int);
BOOL WINAPI H_TextOutW(HDC, int, int, LPCWSTR, int);
BOOL WINAPI H_ExtTextOutA(HDC, int, int, UINT, CONST RECT *,LPCSTR, UINT, CONST INT *);
BOOL WINAPI H_ExtTextOutW(HDC, int, int, UINT, CONST RECT *,LPCWSTR, UINT, CONST INT *);

**********************************************************************************************
**********************************************************************************************

;APIHook_Dll之def文件
LIBRARY APIHook_Dll
EXPORTS
  InstallHook @1
 
二、APIHOOK之exe部分

//////////////////////////// APIHook_EXEDlg.cpp /////////////////////////////////////////
//                             rivershan写于2002.9.23                                  //
/////////////////////////////////////////////////////////////////////////////////////////


#include "stdafx.h"
#include "APIHook_EXE.h"
#include "APIHook_EXEDlg.h"
#include "APIHook_Dll.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAPIHook_EXEDlg dialog

CAPIHook_EXEDlg::CAPIHook_EXEDlg(CWnd* pParent /*=NULL*/)
: CDialog(CAPIHook_EXEDlg::IDD, pParent)
{
 //{{AFX_DATA_INIT(CAPIHook_EXEDlg)
 // NOTE: the ClassWizard will add member initialization here
 //}}AFX_DATA_INIT
 // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CAPIHook_EXEDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(CAPIHook_EXEDlg)
 // DDX_Control(pDX, IDC_EDIT1, m_Edit);
 //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAPIHook_EXEDlg, CDialog)
//{{AFX_MSG_MAP(CAPIHook_EXEDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
 ON_BN_CLICKED(IDC_BUTTON_OUT, OnButtonOut)
 ON_BN_CLICKED(IDC_BUTTON_BEGIN, OnButtonBegin)
 ON_BN_CLICKED(IDC_BUTTON_STOP, OnButtonStop)
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CAPIHook_EXEDlg message handlers

BOOL CAPIHook_EXEDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 
 // Set the icon for this dialog.  The framework does this automatically
 // when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 // TODO: Add extra initialization here
 
 return TRUE;  // return TRUE  unless you set the focus to a control
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CAPIHook_EXEDlg::OnPaint()
{
 if (IsIconic())
 {
  CPaintDC dc(this); // device context for painting
  
  SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
  
  // Center icon in client rectangle
  int cxIcon = GetSystemMetrics(SM_CXICON);
  int cyIcon = GetSystemMetrics(SM_CYICON);
  CRect rect;
  GetClientRect(&rect);
  int x = (rect.Width() - cxIcon + 1) / 2;
  int y = (rect.Height() - cyIcon + 1) / 2;
  
  // Draw the icon
  dc.DrawIcon(x, y, m_hIcon);
 }
 else
 {
  CDialog::OnPaint();
 }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CAPIHook_EXEDlg::OnQueryDragIcon()
{
 return (HCURSOR) m_hIcon;
}
///////////////////////////////////// OnButtonOut 函数 //////////////////////////////////////
//使用TextOut函数
void CAPIHook_EXEDlg::OnButtonOut()
{
 // TODO: Add your control notification handler code here
 HDC hdc = ::GetDC(GetSafeHwnd());
 ::TextOutA(hdc,0,0,"APIHOOK_EXE ---rivershan",30);
 UpdateWindow();
}

///////////////////////////////////// OnButtonBegin 函数 ////////////////////////////////////
//开始挂钩,这里我们挂的是自身这个APIHook_EXE这个程序
void CAPIHook_EXEDlg::OnButtonBegin()
{
 DWORD dwThreadId = GetWindowThreadProcessId(m_hWnd,NULL);//获得自身进程ID
 InstallHook(TRUE,dwThreadId);
}

///////////////////////////////////// OnButtonStop 函数 ////////////////////////////////////
//取消挂钩
void CAPIHook_EXEDlg::OnButtonStop()
{
 InstallHook(FALSE,0);
}

三、APIHOOK之集成

1. 用 VC++新建一个 Win32 Dynamic-Link Library 程序,命名为 APIHook_Dll。接下来选择第二项 A Simple DLL Project;
2. 新建一头文件,命名为 APIHook_Dll.h。删除工程中 APIHook_Dll.cpp文件中原来的内容,然后把上面的 APIHook_Dll.cpp 和 APIHook_Dll.h文件的内容全部复制到新建的这个工程的 .cpp及 .h文件中来;
3. 新建一 Text文件,命名为 APIHook_Dll.def。复制上面的def文件内容。
4. 编译;
5. 新建一 MFC APPWizard(exe)程序,命名为 APIHook_EXE。接着选择第三项,基于对话框的程序,其它默认;
6. 删除原来对话框上的控件,然后新建三个按钮ID分别为:IDC_BUTTON_BEGIN、IDC_BUTTON_STOP、IDC_BUTTON_OUT,Caption分别为:Bigin Hook、Stop Hook、Text Out。不要让这三个按钮出于对话框客户区的最上面就行;
7. 拷贝 APIHook_Dll.h文件到 APIHook_EXE程序目录下,然后加到 APIHook_EXE的头文件夹中。
8. 删除工程中 APIHook_EXEDlg.cpp文件中原来的内容,然后把上面的 APIHook_EXEDlg.cpp文件的内容全部复制到新建的这个工程的 .cpp文件中来;
9. 打开 Project->Setting菜单,选择第四项link,在 Object/library moduls里添加我们的dll的lib文件的路径:..\APIHook_Dll\Debug\APIHook_Dll.lib;
10. 编译;
11. 把 APIHook_Dll.dll文件放在 APIHook_Dll.exe程序的同一个文件夹内;
12. 运行程序,点击 Bigin Hook按钮,开始挂钩。再点击 Text Out按钮会跳出对话框并且会在程序中显示所要显示的字。点击 Stop Hook然后在点击 Text Out按钮就没有对话框出现了。

四、一些说明

1、我这个 HookAPI是使用了 Jeffrey Richter的改写程序的 IAT来实现的,也可以用跳转函数入口点的方法来实现,这个我没做研究。:)

2、我的一些心得:

 所谓 HookAPI,就是改写程序的 IAT,再调用我自己写的用于替换原API函数的函数。在我们自己写的API函数中,我们可以进行我们想要的工作。之后呢,可以把原来的函数传回去,也可以不传回去,只要你设计好了就行。

 而所谓调用自己的函数,就是把原函数参数都传给我的替换函数。我们就可以利用这些参数去干我们想做的事。而系统呢,我想由于微软设置的这个钩子的目的(我这么认为的),所以不会去检查替换函数是否就是原函数,只要参数、返回值符合条件就行,要不会出错。替换函数的返回值最好是原函数,否则有可能会出错

 HookAPI时,exe程序起到的作用就是进行Hook,把dll注入到要Hook的程序,并且传回要挂接的进程的ID或者全局钩子,以便查询所要挂接的模块的IAT。如果不注入进去,系统不会让你去查询IAT的。DLL做的事情是确定要挂接哪个函数和这个函数在哪个DLL中等。

posted @ 2005-03-30 20:45 HuYi's Blog 阅读(732) | 评论 (0)编辑 收藏