2.接口

硕正的服务器专版和以往的纯客户端版本之间，无论是功能还是内部实现代码，都存在着千丝万缕的联系。对于开发者，要进行服务器端的开发，必须先熟悉常规的浏览器端的开发。例如在后端调用硕正接口的最常见的 C# 语句形式如：

     ...
     dll.func("build", ReportFilename);
     dll.func("SetSource", "reportdata/datacenter.do?pa1=23");
     dll.func("calc", "");
     dll.func("callfunc", "105\r\n type=htm;filename=" + TempFilename);
     ...

如果您熟悉硕正客户端的开发，看到这段代码肯定不会感到陌生，其函数名、参数形式和浏览器 js 的书写方法几乎一致, 连最外层 "func" 都一样。
没错，这是我们刻意这样做的，使您在客户端开发的知识技能还能对接得上。
硕正服务器专版的包中，最外层的接口暴露文件是 winface.dll，该接口共有4个 API，其标准的C++形式是 ：

    HANDLE APIENTRY OpenReportService(LPCWSTR para);
    void APIENTRY CloseService(HANDLE h);
    int APIENTRY GetActiveServices();
    LPCWSTR APIENTRY func(HANDLE h, LPCWSTR funcname, LPCWSTR para);

简单地说，这个接口所要做的就是打开报表服务、调用报表服务、关闭报表服务这几个简单的事情.
C#调用 winface.dll 接口，只能以非托管方式调用，我们用C#对该接口做了一个很规范的封装类，对32位和64位都适用，代码如下：

    public class DllInvoke
    {
     //操作系统函数
     [DllImport("kernel32.dll")]
     private extern static IntPtr LoadLibrary(String path);
     [DllImport("kernel32.dll")]
     private extern static IntPtr GetProcAddress(IntPtr lib, String funcName);
     [DllImport("kernel32.dll")]
     private extern static bool FreeLibrary(IntPtr lib);
     //4个API的委托声明
     private delegate int DllGetActiveServices( );
     private delegate IntPtr DllOpenReportService(IntPtr CreatePara);
     private delegate void DllCloseService(IntPtr h);
     private delegate IntPtr Dllfunc(IntPtr h, IntPtr funcname, IntPtr para);

     //构建函数, 参数DLLPath:  为winface.dll的全文件名
     public DllInvoke(String DLLPath) {
      //加载dll
      m_hService = IntPtr.Zero;
      m_hLib = LoadLibrary(DLLPath);
      if(m_hLib == IntPtr.Zero) return;

      //定位入口函数地址
      m_getactiveservices = (DllGetActiveServices)GetInvoke("GetActiveServices", typeof(DllGetActiveServices));
      m_openservice = (DllOpenReportService)GetInvoke("OpenReportService", typeof(DllOpenReportService));
      m_closeservice = (DllCloseService)GetInvoke("CloseService", typeof(DllCloseService));
      m_func = (Dllfunc)GetInvoke("func", typeof(Dllfunc));
      if(m_getactiveservices==null || m_openservice==null || m_closeservice==null || m_func==null) {
       //定位失败
       FreeLibrary(m_hLib);
       m_hLib = IntPtr.Zero;
      }
     }
     ~DllInvoke() {
      CloseService( );
      if(m_hLib != IntPtr.Zero) FreeLibrary(m_hLib);
     }

     //DLL句柄
     public IntPtr m_hLib;

     //函数：取得当前活动的服务数量
     public int GetActiveServices() {
      return m_getactiveservices();
     }
     //函数：打开服务
     public bool OpenReportService(string CreatePara) {
      if(m_hService != IntPtr.Zero) return true;	//已经打开着
      if(m_hLib == IntPtr.Zero) return false;
      IntPtr h1 = Marshal.StringToHGlobalUni(CreatePara);
      m_hService = m_openservice(h1);
      Marshal.FreeHGlobal(h1);
      return (m_hService == IntPtr.Zero) ? false : true;
     }
     //函数：关闭服务
     public void CloseService() {
      if(m_hService != IntPtr.Zero) {
       m_closeservice(m_hService);
       m_hService = IntPtr.Zero;
      }
     }
     //函数：调用服务中的方法
     public string func(string funcname, string para) {
      if(m_hService == IntPtr.Zero) return "";
      //参数 ==> Unicode串指针地址
      IntPtr h1 = Marshal.StringToHGlobalUni(funcname);
      IntPtr h2 = Marshal.StringToHGlobalUni(para);
      //调用
      IntPtr nRet = m_func(m_hService, h1, h2);
      //释放参数内存
      Marshal.FreeHGlobal(h1);
      Marshal.FreeHGlobal(h2);
      return Marshal.PtrToStringUni(nRet);
     }

     //private====================
     private Delegate GetInvoke(String APIName,Type t) {
      IntPtr api = GetProcAddress(m_hLib, APIName);
      return (Delegate)Marshal.GetDelegateForFunctionPointer(api,t);
     }
     //入口函数地址
     private DllGetActiveServices m_getactiveservices;
     private DllOpenReportService m_openservice;
     private DllCloseService m_closeservice;
     private Dllfunc m_func;
     //服务句柄
     private IntPtr m_hService;
    };

浏览这个类，它在创建时就加载winface.dll了，如果加载失败，其成员变量 m_hLib 为零，报表服务也就不可用。

为什么我们要采用操作系统 LoadLibrary 函数、代理方式调用硕正的dll呢？原因是部署后 DLL 的位置是不固定的.

为了方便讨论，我们在下面的文档中，都假定这个类的实例名为 "dll":

     ...
     DllInvoke dll = new DllInvoke(WinFacePathname);
     if(dll.m_hLib == IntPtr.Zero) {
      Response.Write("winface.dll加载失败");
      Response.End();
      return;
     }
     ...

并都以该类的4个同名封装函数为准，不再深究 winface.dll 的原始API。
下面我们逐个说明这4个函数的具体使用方法:

函数 | int GetActiveServices( )
--- | ---
用途 | 取得当前进程中活动的服务数量
返回值 | 整数, 大于等于零

dll 创建成功后，您应该先调用这个函数，如果其返回值不为0，表示已经有其它登录用户正打开报表服务处理中，因为 aspx 是典型的多线程服务，此时我们建议您给出“服务器繁忙，请稍后再试”的页面提示并返回.
在后面稍高深的“线程模型”、“进程模型”文档中，我们会继续深入讨论这个并发访问数量的问题.

函数 | bool OpenReportService(string CreatePara)
--- | ---
用途 | 打开报表服务

参数

";"分隔的 名-值对 串, 例如:
TempDir=c:\\website\\temp; LogSize=1000; LogLevel=2; BaseDir=http://localhost/supcan
可用属性及其解释说明如下:
TempDir - 临时目录的全名, 非常重要。因为硕正报表服务在运行过程中会产生一些临时缓存文件，而Web服务器对目录的写权限有很严格的控制，为此您必须提供一个匿名访问者都能写的物理目录;
BaseDir - 为后继的函数中可能出现的相对URL提供统一的参考路径。在浏览器端的开发中，"相对URL" 通常是相对于页面的，而在后端开发中，这个相对URL的参考点就必须指定，因为报表服务根本不知道当前aspx的实际URL;
LogLevel - 日志级别， 为0/1/2, 默认是0, 0表示不需要日志，1表示需要简单的日志， 2需要更详细的日志. 日志文件名为 @Log.txt, 会自动产生于临时目录 (即 TempDir 指向的物理目录) 中;
LogSize - 日志文件最大尺寸, 单位为kb. 防止日志文件无限膨胀.

备注 | 1.TempDir 必须要设定，否则本函数调用会失败; 2.TempDir 指向的物理目录必须给予最高的访问权限，例如 everyone 的读写权
返回值 | true/false, 失败的原因通常为DLL文件位置不正确、或 TempDir 未指定

函数 | string func(string funcname, string para)
--- | ---
用途 | 调用报表服务
参数 | 和硕正套件常规的用法完全一致，请参考硕正常规的开发文档
返回值 | 串, 也和套件的常规的用法一致

报表的客户端控件有上百个函数，大部分函数您都能在此调用，少数函数例如能导致打开对话框的函数肯定不能用，因为这是后端执行，不可视的.

函数 | void CloseService( )
--- | ---
用途 | 关闭报表服务
返回值 | 无

您最终必须关闭报表服务，这一点非常非常重要，如果您忘记关闭就退出程序了，迟早会出问题的，尽管在封装类的析构函数中有自动关闭服务的语句：

     ~DllInvoke() {
      CloseService( );
      if(m_hLib != IntPtr.Zero) FreeLibrary(m_hLib);
     }

但我们发现，这个 CloseService( )不一定会被执行到，可能和非托管有关，最终结果就是应用程序池资源耗尽，导致http请求响应异常，产生大量 httpStatusCode 为 503 的错误，甚至IIS重启都困难. 切记!