好象有一个VFP高级服务中有过这些东西吧,我不太会这些,你自己看看吧
高级VFP服务 C++ 演示: 需要多少代码来创建一个可以使用我们的 VFP Myserver COM 服务程序 C++ 程序? 让我们试试. 启动 VC, 选择文件>新建>项目. 打入 Mytest 作为项目名称, 并选择 Win32 Application 作为项目类型. 选择文件>新建>文件>C++ 源文件, 添加到项目, 并命名为 myserver.cpp. 粘贴入以下代码: #include "windows.h" #import "c:\fox\test\myserver.tlb" //使用完整的服务程序 tlb 路径名 int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR , int) { using namespace myserver; // 使用 myserver namespace ImyclassPtr pmyclass; // 申明一个 smart ptr 到服务程序 CoInitialize(0); // initialize COM pmyclass.CreateInstance("myserver.myclass"); //创建一个实例 _variant_t myvar, vresult; // 申明两个 variant 变量 _bstr_t res; // 申明一个 bstr 变量 myvar = "version(1)"; // 指定变量 vresult = pmyclass->myeval(&myvar); //调用方法 res = vresult.bstrVal; // 取得 Unicode 结果 MessageBox(0,(char *)res,"",0); //显示它 pmyclass = 0; // 释放服务程序 CoUninitialize(); //uninitialize COM return 0; } 按 F5 运行程序, 你会看到 messagebox 中显示 version(1) 的结果! 如果你小心注意, #import 行实际做了很多工作. 它使用 TLB 并翻译它们为 MyServer.TLH 和 MyServer.TLI 文件, 两者都是 C 的包含入的头文件. 你将注意到 VB 编辑器可以在你输入时实际提示你. 当你打入 ‘.’时 "ox", myclass 的可能的成员显示在一个下拉列表框中. 因为 VB 是一个强类型语言, 并因为你指定了类型库作为一个引用, 在你输入一个语句时提示完成的信息是可用的. 演示: Java 要在一个 java 程序中使用你的 myserver COM 服务程序, 运行 Java 类型库向导 (从 Devstudio 工具菜单中), 并选择导入 Myserver 类型库. 选择文件>新建>项目并使用 Java Applet Wizard 并命名它为 Jmyserver. 在 Java Applet Wizard 中选择所有默认选项. 在文件 jmyserver.java 中, 开始附近的 imports 节, 放入 import myserver.*; 在稍后面的类定义中, 放入 private Imyclass m_pf; Just before the while (true) for the animation, put in the following com.ms.com.Variant v1 = new com.ms.com.Variant(); com.ms.com.Variant v2 = new com.ms.com.Variant(); String mystr; v1.VariantClear(); m_pf = (Imyclass) new myclass(); v1.putString("version(1)"); // fox expression v2 =m_pf.myeval(v1); mystr = v2.getString(); 在 "displayImage(m_Graphics)" 行后的 While (true) 循环中, 添加 m_Graphics.drawString(mystr,10,10); 运行该 applet. 你会看到 fox 版本作为串显示出来. 顺便说一句, 一些版本的 VJ 运行演示后退出 IE 时崩溃. 这不是我们添加的代码造成的. 类成员可视性 如果你查看从上面我们的小示例生成的 myserver.TLB 类型库, 你会看到除了我们添加的两个成员函数 MyDocmd 和 MyEval 外, 还有许多属性和方法在类型库中. AddObject, BaseClass, Class, ClassLibrary, .... 它们都是 OLE Public 类 MyClass 的成员. 这些成员在默认情况下是公共的, 因此发布在类型库中对于所有 COM 客户都是可见的. 全部发布它们不是好的 OOP 编程方法, 因此让这些属性隐藏或受保护是一个好的办法. 在 VCX 类属性对话框中, 你可以多选一个属性组并修改它们的可视性. GUIDS 在处理 COM 服务程序时, 你不能得到帮助但注意所有这些奇怪的串如 "7E5A5C95-6DE2-11D1-BEFF-0080C74BBCD5" 它们被称为 GUIDs, 它是 Globally Unique Identifier 的首字母缩写. (有时它们又被称为 UUID 或 Universally Unique Identifier). GUID 实际上是保证时间和空间上的唯一. 换句话说, 如果你生成一个 GUID, 没有人在过去或将来, 或在一个不同的机器上可以生成相同的 GUID. GUIDs 是作为 ID 用于 COM 的方面的唯一需要. 如果 Widget Software Inc 创建了一个叫做你的机器上需要的 IFooBar 的 COM 接口, 它会与你可能在你的机器上创建的名为 IFooBar 的接口冲突. 可以在你创建一个 VFP COM 服务程序时看到生成的 GUIDs. 查看由上面的示例 VFP COM 服务程序生成的 MYSERVER.VBR 文件. VFP5 将与 VFP6 有一小点区别. 该文件可用于远程自动管理来从创建该服务程序的机器上注册一个服务程序到不同的机器上. 在你联编一个 VFP 自动服务程序时, 它自动在你的机器上注册. 要部署到不同的机器上, 你可以用 VBR 文件或你可以用稍后描述的自注册技术. 下面是前面描述到的 Myserver 项目的 VBR: VB5SERVERINFO VERSION=1.0.0 HKEY_CLASSES_ROOT\myserver.myclass = myclass HKEY_CLASSES_ROOT\myserver.myclass\NotInsertable HKEY_CLASSES_ROOT\myserver.myclass\CLSID = {149C74C0-D2F3-11D1-988E-00805FC792B1} HKEY_CLASSES_ROOT\CLSID\{149C74C0-D2F3-11D1-988E-00805FC792B1} = myclass HKEY_CLASSES_ROOT\CLSID\{149C74C0-D2F3-11D1-988E-00805FC792B1}\ProgId = myserver.myclass HKEY_CLASSES_ROOT\CLSID\{149C74C0-D2F3-11D1-988E-00805FC792B1}\VersionIndependentProgId = myserver.myclass HKEY_CLASSES_ROOT\CLSID\{149C74C0-D2F3-11D1-988E-00805FC792B1}\LocalServer32 = myserver.exe /automation HKEY_CLASSES_ROOT\CLSID\{149C74C0-D2F3-11D1-988E-00805FC792B1}\TypeLib = {149C74C2-D2F3-11D1-988E-00805FC792B1} HKEY_CLASSES_ROOT\CLSID\{149C74C0-D2F3-11D1-988E-00805FC792B1}\Version = 1.0 HKEY_CLASSES_ROOT\INTERFACE\{149C74C1-D2F3-11D1-988E-00805FC792B1} = myclass HKEY_CLASSES_ROOT\INTERFACE\{149C74C1-D2F3-11D1-988E-00805FC792B1}\ProxyStubClsid = {00020424-0000-0000-C000-000000000046} HKEY_CLASSES_ROOT\INTERFACE\{149C74C1-D2F3-11D1-988E-00805FC792B1}\ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046} HKEY_CLASSES_ROOT\INTERFACE\{149C74C1-D2F3-11D1-988E-00805FC792B1}\TypeLib = {149C74C2-D2F3-11D1-988E-00805FC792B1} HKEY_CLASSES_ROOT\INTERFACE\{149C74C1-D2F3-11D1-988E-00805FC792B1}\TypeLib\"Version" = 1.0 ; TypeLibrary registration HKEY_CLASSES_ROOT\TypeLib\{149C74C2-D2F3-11D1-988E-00805FC792B1} HKEY_CLASSES_ROOT\TypeLib\{149C74C2-D2F3-11D1-988E-00805FC792B1}\1.0 = myserver type library HKEY_CLASSES_ROOT\TypeLib\{149C74C2-D2F3-11D1-988E-00805FC792B1}\1.0\0\win32 = myserver.tlb HKEY_CLASSES_ROOT\TypeLib\{149C74C2-D2F3-11D1-988E-00805FC792B1}\1.0\FLAGS = 0 正如你所在 VBR 文件中所见, 对于串 "myserver.myclass" 有一个特定的叫做 CLSID 的 GUID (表示为 ProgID). 另一个 GUID 用于接口, 类型库中有一个以上. "LocalServer32 = myserver.exe /automation" 这一行指出要运行的服务程序. 在安装机器的注册表中实际上有一个前面带完整路径名的 EXE 名字. 这就是为什么 COM 可以找到 EXE 服务程序. 对于一个 DLL 服务程序, VBR 将有以下内容: HKEY_CLASSES_ROOT\CLSID\{69F21492-D2B7-11D1-BF21-AC3498000000}\InProcServer32 = myserver.dll HKEY_CLASSES_ROOT\CLSID\{69F21492-D2B7-11D1-BF21-AC3498000000}\InProcServer32\"ThreadingModel" = Apartment 第二行只说明 COM VFP6 服务程序是 Apartment 模式线程. 更多细节参见本文线程一节. COM 如何实例一个对象 当一个 COM 客户创建一个 COM 服务程序实例时, 它从内部执行 OLE 函数 CoCreateInstance(). 该函数将在注册表中搜索要求的服务程序信息并用该信息启动服务程序. 给定一个 ProgID 如 "Myserver.Myclass", 要做的第一件事是转换它为称为 CLSID 的 GUID. 然后寻找服务程序来创建是搜索注册表中的 \HKEY_CLASSES_ROOT\CLSID\{guid} 入口来为 DLL 服务程序找到 InProcServer32 键或为 EXE 服务程序找到 LocalServer32 键. COM 服务程序部署问题 自注册 DLL 服务程序可以通过以下方式来自注册 Regsvr32 myserver.dll 注册一个服务程序 Regsvr32 /u myserver.dll 反注册一个服务程序 或者, 你可以用 VFP 代码来注册或反注册一个服务程序. declare integer DllRegisterServer IN myserver.dll declare integer DllUnregisterServer IN myserver.dll ?DllUnregisterServer() ?DllRegisterServer() 注册一个服务程序意味着要求服务程序的注册表入口添加到机器的注册表中. 顺便说一句, OCX 也是 DLL COM 服务程序, 而且它们可以用相同的方法注册. OCX 是特殊类型的支持一个定义良好的接口的 COM 服务程序. 所有 VFP5 和 VFP6 服务程序都是可自注册的. EXE 服务程序自注册方式是: Myserver /regserver Myserver /unregserver 不同类型的 COM 服务程序 DLL 和 EXE 服务程序 主要有两种不同类型的 COM 服务程序: DLL (有时又称为进程内) 和 EXE (有时称为本地) 服务程序. DLL 服务程序必须存在于与它们的缩主相同的进程中. EXE 服务程序存在于单独的进程中. EXE 服务程序要求客户和服务程序间 interprocess 通信, 这相对比直接通信的 DLL 服务程序要慢些. 当一个 COM 客户调用一个 DLL 服务程序方法时, 该调用就象 COM 客户调用它自己的例程一样快. 对于一个 EXE 服务程序, 方法调用必须转换 (marshalled) 自该服务程序的客户的进程空间. 因为 DLL 服务程序必须居于主机的相同的进程空间中, 一个崩溃的 DLL 服务程序会很容易崩溃客户程序. EXE 服务程序更健壮: 一个崩溃的 EXE 服务程序可能不会崩溃客户. 事实上, 一个 DLL 服务程序居于客户的进程空间中: 如果你使用 Microsoft Transaction 服务程序 (代码名 Viper), 则 DLL 将居于 MTS 创建的进程中, 有时又称为 Viperspace. 一个 EXE 服务程序 (以及居于 MTS 中的 DLL 服务程序) 可以 "远程"… 它可以放在一个不同的机器上. 因此你可以创建一个服务程序 来及时创建月底报表并让它执行在你的老板的超级机器上运行机器, 释放你的机器来玩游戏. 单独使用, 多重使用和不可创建 此外, VFP 中的 OLE Public 类可以标记为单独使用, 多重使用和不可创建. 这可以在项目信息对话框的条三个选项卡中修改. 单独使用意味只有第一客户只能创建单个类实例. 不要与单用户搞混了. 如果另一个实例被单实例服务程序请求: ox = CreateObject("myserver.myclass") oy = CreateObject("myserver.myclass") 则对于 EXE 服务程序, 一个完整的背后的 EXE 将启动. 对于一个 VFP6 DLL 服务程序, 只有一个实例 将创建, 而且第二个请求将造成一个错误信息. 即使在一个 VFP6 单独使用的 DLL 服务程序中执行相同的命令两次: ox = CreateObject("myserver.myclass") ox = CreateObject("myserver.myclass") 将造成一个错误. 这一点的理由是 VFP 指定语句求值右边的值时, 首先释放左边变量原来的内容. 这意味着第二个实例将试图在第一个被释放前创建第一个. 多重使用服务程序意味着多个 OLE Public 类的实例可以实例自同一服务程序. 如果你重建示例服务程序为多重使用 EXE 或 DLL 服务程序: ox=CreateObject("myserver.myclass") && 创建第一个实例 oy=CreateObject("myserver.myclass") && 创建第二个实例 ?ox.mydocmd("public zz") && 在第一实例中创建一个公共变量 ?ox.mydocmd("zz='set from ox'") && 给它一个值 ?oy.myeval("zz") && 从第二个实例中查询它 第二个实例可以看见由第一个实例创建的公共变量. 更有趣的情况是可以由产生两个单独的客户实例, 而不是两个来自相同客户的实例. 对于 DLL 服务程序, 各个不同的客户实例 (居于它的自己的不同地址空间) 将得到它自己的 DLL 服务程序的私有副本. 对于 EXE 服务程序, 你可以启动两个 VFP 实例到两个不同的客户. 在一个实例中, 创建第一个实例, ox=CreateObject("myserver.myclass") && 创建第一个实例 ?ox.mydocmd("public zz") && 创建一个 第一个实例中的公共变量 ?ox.mydocmd("zz='set from ox'") && 给它一个值 * 而且在第二个实例中: oy=CreateObject("myserver.myclass") && 创建第二个实例 ?oy.myeval("zz") && Query the value 来自第一个实例 在此情况下, 有两个客户, 但只有一个 服务程序 EXE 实例, 它提出两个 OLE Public 类的实例. 你可以用 Windows NT 任务管理器或一些其它工具来查看真的只有一个 MYSERVER.EXE 实例在运行. 我们已经在一个实例中创建公共变量 ZZ 并可以另一个中可以访问来证明了类的两个实例是在相同的 EXE 中. 要证明类的两个实例是真正的不同实例, 我们可以用 VFP6 的新的 AddProperty 方法来添加一个属性到第一个实例并看该属性是否在第二个实例中可见: ox.AddProperty("foo","default value") ?ox.MyEval("this.foo") *在第二个实例中, ?oy.Myeval("this.foo") && 造成一个错误 不可创建是有用的, 如果你有一个有多个 OLE Public 类的可视类库, 而且该 VCX 被多个项目共享. 你可能不想让 VCX 中的所有 OLE Public 类对于各个项目都是公共的, 因此你可以选择一个你希望是不可创建的. COM 服务程序编程问题 无用户界面服务程序 VFP6 DLL 服务程序不能进行任何类型的用户界面操作. VFP5 允许显示一个窗口, 但它不能真正地与用户交互. 这样做的理由是服务程序的主要用途是执行计算和进行数据维护. 执行用户界面不是一个服务程序的用途. 例如, 一个生成网页的的 COM 服务程序将从一个 Web 服务程序调用. 这些应该是不需要用户从它们的机器上通过用户接口操作来完成的. 事实上, Windows NT 上的 Internet Information Server (IIS) 运行在一个服务的安全上下文上, 这意味着它没有桌面. 如果一个从 IIS 调用的 COM 服务程序调用 MessageBox 函数, 服务程序会挂起在一个无穷的循环中, 因为代码认为它正在显示一个 MessageBox, 但没有实际的 MessageBox 显示. 服务没有桌面, 因此所有用户界面的函数显示在用户的桌面上. Visual FoxPro 6 在 COM 服务程序中有少量函数总是出现在 DLL 服务程序中, 而且可以在 EXE 服务程序中打开. SYS(2335) 将关闭用户接口操作. 如果一个用户接口操作 (如 READ EVENTS, MessageBox, Wait Window, 等.) 被执行, 它将造成一个 Error 事件被执行. EXE 服务程序, 从另一方面来说, 可以使用用户接口操作. 一个 EXE 服务程序没有它自己的进程空间并因此分配处理器执行时间, 并因此可以处理事件. 一个 DLL 服务程序是寄缩在客户的进程空间并只在客户调用进入服务程序时给予执行时间. 当 服务程序未执行一个方法时 方法没有处理器时间给服务程序, 因此 DLL 服务程序不能处理事件. 错误处理 服务程序试着显示用户界面是为什么在 COM 服务程序中错误处理是如此重要的一个极大的理由, 并且显然在 DLL 服务程序也是如此. 如果客户调用一个服务程序上的方法时造成一些类型的错误 (如 "文件未找到" 或 "拒绝访问"), 让服务程序只用一个 messagebox 来指明错误并不太好. 开发者可以用 OLE Public 类的 Error 方法来温文地处理这样的错误. 在 VFP6 中的一个新的叫做 COMReturnError 的函数将实际上造成一个 COM Error 对象被创建并返回到 COM 客户. 它使用两个参数: Source 和 Description. 你可以放入任何你需要的串到这些参数中. 以下示例方法可以粘贴到上面的 Myserver 示例中. FUNCTION Error(nError, cMethod, nLine) COMreturnerror(cMethod+' err#='+str(nError,5)+' line='+str(nline,6)+' '+message(),_VFP.ServerName) && 该行决不会执行 你可以用以下合法的命令调用 MyDocmd 方法来调用这个 error 方法: ox = CreateObject("myserver.myclass") && 创建服务程序对象 ?ox.mydocmd("illegal command") && 造致一个错误发生 服务程序中发生的错误被 MyClass::Error 方法捕捉, 然后造致服务程序终止处理并返回带填充了的 Source 和 Description 的 COM Error 对象. ?aerror(myarray) list memo like myarray MYARRAY Pub A ( 1, 1) N 1429 ( 1429.00000000) ( 1, 2) C "OLE IDispatch exception code 0 来自 mydocmd err#= 16 line= 2 Unrecognized command v erb.: c:\fox\test\myserver.exe.." ( 1, 3) C "c:\fox\test\myserver.exe" ( 1, 4) C "mydocmd err#= 16 line= 2 Unrecognized command verb." ( 1, 5) C "" ( 1, 6) N 0 ( 0.00000000) ( 1, 7) N 0 ( 0.00000000)