Spring Cloud Dataflow入门

1、下载下面的文件

#Kafka 0.10.1.1
http://kafka.apache.org/downloads
#服务端
http://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-server-local/1.1.3.RELEASE/spring-cloud-dataflow-server-local-1.1.3.RELEASE.jar
#客户端
http://repo.spring.io/release/org/springframework/cloud/spring-cloud-dataflow-shell/1.1.3.RELEASE/spring-cloud-dataflow-shell-1.1.3.RELEASE.jar
#Avogadro.properties
http://bit.ly/Avogadro-SR1-stream-applications-kafka-10-maven

2、启动服务

#启动ZK
bin/zookeeper-server-start.sh config/zookeeper.properties  &
#启动kafka
bin/kafka-server-start.sh config/server.properties &
#启动服务端
java -jar spring-cloud-dataflow-server-local-1.1.3.RELEASE.jar &
#启动客户端
java -jar spring-cloud-dataflow-shell-1.1.3.RELEASE.jar
#导入Avogadro.properties
app import --uri file://PATH_TO_FILE/Avogadro.properties

3、打开界面
http://IP:9393/dashboard

4、新建一个简单的流,每15秒,写入一行数据
就不截图了,直接粘进去就好了

per_15s_flow01=per_15s: time --cron="0/15 * * * * ?" --date-format="yyyy-MM-dd HH:mm:ss" | file01: file --directory=PATH_TO_FILE --name=file01.txt

创建,并开启工作流。
查看file01.txt
销毁工作流。

5、新建一个较为复杂的流

per_15s_flow01=per_15s: time --cron="0/15 * * * * ?" --date-format="yyyy-MM-dd HH:mm:ss" | file01: file --directory=PATH_TO_FILE --name=file01.txt
per_15s_subflow01=:per_15s_flow01.per_15s > groovy-transform --script="nscript.groovy" --variables=msg='Time is ' | file02: file --directory=PATH_TO_FILE/ --name=file02.txt

用到了一个脚本nscript.groovy

msg+payload

创建,并开启工作流。
查看file01.txt及file02.txt
销毁工作流。

如果报错找不到文件nscript.groovy的话,可以考虑把nscript.groovy文件加到groovy-transform-processor-kafka-10-1.1.1.RELEASE.jar中。

Spring Cloud Config入门

1、从github上下载最新版本
spring-cloud-config

2、编译并运行spring-cloud-config-server

cd spring-cloud-config-server
../mvnw spring-boot:run

如果运行mvnw遇到下面的错误,用vi处理一下就好了

/bin/sh^M: bad interpreter: No such file or directory

用vi处理一下

:set fileformat=unix

3、默认端口为8888,默认配置仓库为github
config-repo

支持以下访问语法:

#获取配置
/{application}/{profile}[/{label}]
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml

#更新Environment、重新绑定@ConfigurationProperties、重置日志级别
/env
#刷新@RefreshScope
/refresh
#重启Spring上下文,默认是禁用的
/restart` for restarting the Spring context (disabled by default)
#调用ApplicationContext的stop及start等生命周期相关函数
/pause
/resume

我们来测试一下服务端运行情况:(服务端要可以连接到github上哦)

#foo.properties
curl localhost:8888/foo-master.properties
#foo.properties tag=v1.0.0.RC2
curl localhost:8888/v1.0.0.RC2/foo-master.properties
#foo-development.properties
curl localhost:8888/foo-development.properties
#foo-db.properties
curl localhost:8888/foo-db.properties
#foo-development-db.properties
curl localhost:8888/foo-development-db.properties

4、看一下自带的例子
spring-cloud-config-sample/src/main/java/sample/Application.java


package sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class Application {

	@Autowired
	private Environment environment;

	@Value("${spring.application.name}")
	private String appName;
	
	@RequestMapping("/appName")
	public String appName() {
		return appName;
	}

	@RequestMapping("/")
	public String query(@RequestParam("q") String q) {
		return environment.getProperty(q);
	}

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

5、测试一下例子

cd spring-cloud-config-sample
mvn spring-boot:run

6、看一下输出信息

Mapping servlet: 'dispatcherServlet' to [/]
Mapping filter: 'metricsFilter' to: [/*]
Mapping filter: 'characterEncodingFilter' to: [/*]
Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
Mapping filter: 'httpPutFormContentFilter' to: [/*]
Mapping filter: 'requestContextFilter' to: [/*]
Mapping filter: 'webRequestLoggingFilter' to: [/*]
Mapping filter: 'applicationContextIdFilter' to: [/*]
Mapping : Mapped "{[/]}" onto public java.lang.String sample.Application.query(java.lang.String)
Mapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
Mapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
Mapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
Mapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
Mapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
Mapping     : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/auditevents || /auditevents.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public org.springframework.http.ResponseEntity<?> org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint.findByPrincipalAndAfterAndType(java.lang.String,java.util.Date,java.lang.String)
Mapping     : Mapped "{[/loggers/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.get(java.lang.String)
Mapping     : Mapped "{[/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)
Mapping     : Mapped "{[/loggers || /loggers.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/env],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.value(java.util.Map<java.lang.String, java.lang.String>)
Mapping     : Mapped "{[/env/reset],methods=[POST]}" onto public java.util.Map<java.lang.String, java.lang.Object> org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.reset()
Mapping     : Mapped "{[/pause || /pause.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
Mapping     : Mapped "{[/resume || /resume.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
Mapping     : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/features || /features.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
Mapping     : Mapped "{[/env || /env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/health || /health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest)
Mapping     : Mapped "{[/restart || /restart.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.restart.RestartMvcEndpoint.invoke()
Mapping     : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/heapdump || /heapdump.json],methods=[GET],produces=[application/octet-stream]}" onto public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException
Mapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
Mapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/refresh || /refresh.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
Mapping     : Mapped "{[/info || /info.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
Mapping     : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()

7、测试一下

curl localhost:8080/?q=foo

8、是不是很简单,到自己的项目上试试吧

Spring Cloud各模块作用

首先说明一下,由于工作实在太忙了,这个系列的后面几篇,是后面补的,并没能及时进行总结和整理。
有一些细节的地方,可能并不准确(调整过部分代码和配置文件什么的),遇到问题麻烦调整一下,有问题也可以留言。

1、配置中心(基于git、svn、ftp等方式)
Spring Cloud Config

2、负载均衡
Spring Cloud Cluster

3、消息总线
Spring Cloud Bus

4、服务注册及发现
Spring Cloud Zookeeper
Spring Cloud Consul

5、任务调度
Spring Cloud Data Flow 任务调度
Spring Cloud Stream 工作流
Spring Cloud Stream App Starters 工作流实现
Spring Cloud Task 批处理
Spring Cloud Task App Starters 批处理实现

6、服务监控
Spring Cloud Sleuth

7、安全(OAuth2)
Spring Cloud Security

8、数据库、消息队列等
Spring Cloud Connectors

9、快速开发groovy
Spring Cloud CLI

10、Moke测试
spring-cloud-contract

11、部署(现在有mvn和docker两种方式)
spring-cloud-deployer

12、第三方云厂商集成
Spring Cloud for Amazon Web Services
Spring Cloud for Cloud Foundry
Spring Cloud Cloud Foundry Service Broker

13、Spring Cloud Netflix
archaius 配置管理
ribbon 客户端负载均衡
feign 基于注解的JSON库,自带ribbon功能
eureka 服务注册、LB、故障转移
hystrix 断路器/日志采集
turbine 性能日志聚合
Zuul API网关,数据路由+负载均衡
spectator/servo 客户端性能指标采集
atlas 时间序列数据库

14、其他
sidecar 将非JVM服务,封装为Netflix服务,并提供代理,让非JVM服务调用

15、移动端支持
RxJava 响应式编程支持(observable, operator和susbscriber)

几种常见的微服务编排模式

随着需要管理服务的增多,如何编排服务,成了一个很迫切的问题。本文就介绍几种常见的微服务编排方式:

1、Orchestration
这种方式,和BPM、ESB的思想很相似,实现方案多是同步的。
首先要有一个流程控制服务,该服务接收请求,依照业务逻辑规则,依次调用各个微服务,并最终完成处理逻辑。
这种方法的好处是,流程控制服务时时刻刻都知道每一笔业务究竟进行到了什么地步,监控业务成了相对简单的事情。
这种方法的坏处是,流程控制服务很容易控制了太多的业务逻辑,耦合度过高,变得臃肿,而各个微服务退化为单纯的增删改查,容易失去自身价值。

为了便于理解,您可以把控制服务看作BPM、ESB引擎,微服务为BPM、ESB的各种组件。

2、Choreography
这种方式,可以看作一种消息驱动模式,或者说是订阅发布模式,实现方案多是异步的。
每笔业务到来后,各个监听改事件的服务,会主动获取消息,处理,并可以按需发布自己的消息。
这种方法的好处是,耦合度低,每个服务都可以各司其职。
这种方法的坏处是,业务流程是通过订阅的方式来体现的,很难直接监控每笔业务的处理,因此需要增加相应的监控系统,来保证业务顺畅进行。

为了便于理解,您可以把不同队列看作不同种类的消息,微服务看作消息处理函数。

3、API网关
API网关,可以看作一种简单的接口聚合/拆分的方式。
每笔业务到来后,先到达网关,网关调用各微服务,并最终聚合/拆分需反馈的结果。
这种方法的好处是,对外接口相对稳定,可以利用LAN的带宽,弥补因特网的不足。
这种方法的坏处是,只适合业务逻辑较为简单的场景,业务逻辑过于复杂时,网关接口耦合度及复杂度会急剧升高,变得臃肿。

其实就是一个适配网关,比如对于Web端,可以一个页面同时发起几十个请求,而对于移动端,最好是一个页面就几个请求
。而采用API网关,后面的微服务可以是相同的。

COM+服务授权配置

远程调用COM+时经常会遇到下面的错误:

Error: 70
Description: Permission denied

这时就要进行授权(这个方法只能调通Win7与Win7,无法调通Win7与XP)。

首先是服务端配置:

1、首先在COM+服务端新建一个用户,比如COMPTEST,用户一定要加入组Distributed COM Users。(XP没有这个组,我用了Administrator权限依然不可以。。。)

2、在COM+服务端运行

#32位系统32位COM,64位系统64位COM
dcomcnfg
#64位系统32位COM
comexp.msc -32

3、在“服务组件-》计算机-》我的电脑”右键,属性
默认属性-》确认DCOM服务开启

4、在“服务组件-》计算机-》我的电脑-》COM+应用程序”,对应的COM+应用程序上,右键,属性
安全性-》授权,取消勾选
安全性-》安全性级别,改为Process
安全性-》将身份验证级别修改为无
安全性-》将模拟级别改为匿名
激活-》激活类型改为应用程序

5、在在“服务组件-》计算机-》我的电脑-》COM+应用程序-》对应COM+应用程序-》组件”,对应的COM+组件上,右键,属性
安全性-》授权,取消勾选

然后是客户端配置
1、在客户端,添加服务端的COMPTEST用户凭证

搞定!

PS:如果还不行的话,再设置下面的内容
1、在“服务组件-》计算机-》我的电脑”右键,属性
默认属性-》将默认身份验证基本修改为无
默认属性-》将默认模拟级别改为匿名
COM安全-》访问权限,添加Everyone及匿名用户
COM安全-》启动与激活权限,添加Everyone及匿名用户
2、然后要修改一下本地安全策略,让匿名用户与Everyone权限相同
3、然后修改本地安全策略,让DCOM可以Everyone操作

COM+简单示例(05)

本篇文章介绍了如何写一个简单的COM+客户端(CSharp)。

1、首先要导入COM组件编译时生成的TBL文件(ATL),并引用COM组件的Assembly(CS)

2、代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ATLCOMPLib;
using CSDll;

namespace CSTestComp
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                testATL();
                //testCS();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadKey();
        }

        static void testATL()
        {
            Type justATestCOMPType = System.Type.GetTypeFromProgID("ATLCOMP.JustATestCOMP", "172.16.172.3");
            IJustATestCOMP svc = (IJustATestCOMP)System.Activator.CreateInstance(justATestCOMPType);
            int c = svc.Add(1, 2);
            System.Console.WriteLine(c);
            String retValue = svc.SayHiTo("com+");
            System.Console.WriteLine(retValue);
            System.Console.Read();
        }

        static void testCS()
        {
            Type justATestCSSvcType = System.Type.GetTypeFromProgID("CSDll.JustATestCSSvc", "172.16.172.3");
            IJustATestCSSvc svc = (IJustATestCSSvc)System.Activator.CreateInstance(justATestCSSvcType);
            int c = svc.Add(1, 2);
            System.Console.WriteLine(c);
            String retValue = svc.SayHiTo("com+");
            System.Console.WriteLine(retValue);
            System.Console.Read();
        }
    }
}

如果是本机测试(带IP),一般不会遇到权限问题

PS:
用了一晚上时间,只能调通Win7与Win7之间远程调用,无法调通Win7与XP之间远程调用(总是各种提示Access is Denied)。
如果有谁调通过,麻烦留言告诉我一下。谢谢!

COM+简单示例(04)

本篇文章介绍了如何写一个简单的COM+客户端(CPP)。

#include "stdafx.h"
#include "windows.h"
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{
    // COM 初始化
    ::CoInitialize(NULL);       

    // 通过 ProgID 得到 CLSID
    CLSID clsid;                
    //HRESULT hr = ::CLSIDFromProgID(L"ATLCOMP.JustATestCOMP", &clsid);
    HRESULT hr = ::CLSIDFromProgID(L"CSDll.JustATestCSSvc", &clsid);
    if(!SUCCEEDED(hr))
    {
        std::cout << "clsid not found" << std::endl;
        getchar();
        return -1;
    }

    // 由 CLSID 启动组件,并得到 IDispatch 指针
    COAUTHIDENTITY authIdentity;
    authIdentity.User = (USHORT *)L"DCOMTEST";
    authIdentity.UserLength = wcslen(L"DCOMTEST");
    //authIdentity.Domain = (USHORT *)L"Domain";
    //authIdentity.DomainLength = wcslen(L"Domain");
    authIdentity.Password = (USHORT *)L"DCOMTEST";
    authIdentity.PasswordLength = wcslen(L"DCOMTEST");
    authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;

    COAUTHINFO  authInfo;
    ZeroMemory(&authInfo, sizeof(COAUTHINFO));
    authInfo.dwAuthnSvc = RPC_C_AUTHN_NONE;
    authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE;
    //authInfo.dwAuthnSvc = RPC_C_AUTHZ_DEFAULT;
    //authInfo.dwAuthzSvc = RPC_C_AUTHZ_DEFAULT;
    //authInfo.pwszServerPrincName = L"Domain\\MachineName";
    authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_NONE;
    authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_ANONYMOUS;
    //authInfo.pAuthIdentityData = &authIdentity;
    //authInfo.dwCapabilities = EOAC_NONE;

    COSERVERINFO coServerInfo;
    ZeroMemory(&coServerInfo, sizeof(COSERVERINFO));
    coServerInfo.pwszName = TEXT("172.16.172.3");
    coServerInfo.pAuthInfo = NULL;
    coServerInfo.dwReserved1 = 0;
    coServerInfo.dwReserved2 = 0;
    coServerInfo.pAuthInfo = &authInfo;

    MULTI_QI mqi;
    ZeroMemory(&mqi, sizeof(MULTI_QI));
    mqi.pIID=&IID_IDispatch;
    mqi.pItf=NULL;
    mqi.hr=0;
    hr = CoCreateInstanceEx(clsid,
                            NULL,
                            CLSCTX_ALL,
                            &coServerInfo,
                            1,
                            &mqi);

    IDispatch * pDisp = (IDispatch*)mqi.pItf;

    if (!SUCCEEDED(hr))
    {
        std::cout << "IDispatch not found" << std::endl;
        DWORD d = GetLastError();
        getchar();
        return -1;
    }

    LPOLESTR pwFunNameAdd = L"Add";     // 准备取得 Add 函数的序号 DispID
    DISPID dispIDAdd;                   // 取得的序号,准备保存到这里
    hr = pDisp->GetIDsOfNames(          // 根据函数名,取得序号的函数
        IID_NULL,
        &pwFunNameAdd,                  // 函数名称的数组
        1,                              // 函数名称数组中的元素个数
        LOCALE_SYSTEM_DEFAULT,          // 使用系统默认的语言环境
        &dispIDAdd);                    // 返回值
    if (!SUCCEEDED(hr))
    {
        std::cout << "Add not found" << std::endl;
        getchar();
        return -1;
    }

    VARIANTARG vAdd[2];                             // 调用 Add(1,2) 函数所需要的参数
    vAdd[0].vt = VT_I4; vAdd[0].lVal = 2;           // 第二个参数,整数2
    vAdd[1].vt = VT_I4; vAdd[1].lVal = 1;           // 第一个参数,整数1

    DISPPARAMS dispParamsAdd = {vAdd, NULL, 2, 0 }; // 把参数包装在这个结构中
    VARIANT vResultAdd;                             // 函数返回的计算结果

    hr = pDisp->Invoke(         // 调用函数
        dispIDAdd,              // 函数由 dispID 指定
        IID_NULL,
        LOCALE_SYSTEM_DEFAULT,  // 使用系统默认的语言环境
        DISPATCH_METHOD,        // 调用的是方法,不是属性
        &dispParamsAdd,         // 参数
        &vResultAdd,            // 返回值
        NULL,                   // 不考虑异常处理
        NULL);                  // 不考虑错误处理
    if (!SUCCEEDED(hr))
    {
        std::cout << "Invoke failed" << std::endl;
        getchar();
        return -1;
    }
    std::cout << vResultAdd.lVal << std::endl;

    LPOLESTR pwFunNameSayHiTo = L"SayHiTo"; // 准备取得 SayHiTo 函数的序号 DispID
    DISPID dispIDSayHiTo;                   // 取得的序号,准备保存到这里
    hr = pDisp->GetIDsOfNames(              // 根据函数名,取得序号的函数
        IID_NULL,
        &pwFunNameSayHiTo,                  // 函数名称的数组
        1,                                  // 函数名称数组中的元素个数
        LOCALE_SYSTEM_DEFAULT,              // 使用系统默认的语言环境
        &dispIDSayHiTo);                    // 返回值
    if (!SUCCEEDED(hr))
    {
        std::cout << "SayHiTo not found" << std::endl;
        getchar();
        return -1;
    }

    VARIANTARG vSayHiTo[1];                                         // 调用 vSayHiTo("dcom") 函数所需要的参数
    vSayHiTo[0].vt = VT_BSTR;   vSayHiTo[0].bstrVal = TEXT("dcom"); // 第一个参数,字符串dcom

    DISPPARAMS dispParamsSayHiTo = { vSayHiTo, NULL, 1, 0 };        // 把参数包装在这个结构中
    VARIANT vResultSayHiTo;                                         // 函数返回的计算结果

    hr = pDisp->Invoke(         // 调用函数
        dispIDSayHiTo,          // 函数由 dispID 指定
        IID_NULL,
        LOCALE_SYSTEM_DEFAULT,  // 使用系统默认的语言环境
        DISPATCH_METHOD,        // 调用的是方法,不是属性
        &dispParamsSayHiTo,     // 参数
        &vResultSayHiTo,        // 返回值
        NULL,                   // 不考虑异常处理
        NULL);                  // 不考虑错误处理
    if (!SUCCEEDED(hr))
    {
        std::cout << "Invoke failed" << std::endl;
        getchar();
        return -1;
    }

    std::wcout << vResultSayHiTo.bstrVal << std::endl;

    pDisp->Release();       // 释放接口指针
    ::CoUninitialize();     // 释放 COM

    getchar();
    return 0;
}

如果是本机测试(带IP),一般不会遇到权限问题

PS:
用了一晚上时间,只能调通Win7与Win7之间远程调用,无法调通Win7与XP之间远程调用(总是各种提示Access is Denied)。
如果有谁调通过,麻烦留言告诉我一下。谢谢!

COM+简单示例(03)

本篇文章介绍了如何写一个简单的COM+客户端(VBS)。

1、testCOMP.vbs

'发生错误时,继续运行
On Error Resume Next

'清除错误状态
Err.Clear

Set Obj=CreateObject("CSCOMTest.JustATestCOM")

'输出错误信息
If Err.Number <> 0 Then
    WScript.Echo "Error: " & Err.Number
    WScript.Echo "Error (Hex): " & Hex(Err.Number)
    WScript.Echo "Source: " &  Err.Source
    WScript.Echo "Description: " &  Err.Description
    'Err.Clear
    '退出程序
    WScript.Quit(Err.Number)
End If

'On Error Goto 0

WScript.Echo obj.Add(1,2)
WScript.Echo obj.SayHiTo("dcom")


set obj=Nothing

2、运行

cscript testCOMP.vbs

3、如果是本机测试(带IP),一般不会遇到权限问题

PS:
用了一晚上时间,只能调通Win7与Win7之间远程调用,无法调通Win7与XP之间远程调用(总是各种提示Access is Denied)。
如果有谁调通过,麻烦留言告诉我一下。谢谢!

COM+简单示例(02)

本篇文章介绍了如何用C#写一个简单的COM+组件(Dll)。

1、新建一个C# Class Library项目“CSDll”,项目增加引用“System.EnterpriseServices”

2、项目中,新增一个接口文件IJustATestCSSvc.cs,Guid要自己生成

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace CSDll
{
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [Guid("F243B9A5-A171-49AD-AA68-47CCF6A68A2A")]
    public interface IJustATestCSSvc
    {
        int Add(int a, int b);
        String SayHiTo(String someOne);
    }
}

3、项目中,新增一个类文件JustATestCSSvc.cs,Guid要自己生成

using System;
using System.Collections.Generic;
using System.EnterpriseServices;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace CSDll
{
    
    [ComVisible(true)]
    [Guid("0C7660A9-37EC-4A39-AB0E-E936B2B24A98")]
    [ProgId("CSDll.JustATestCSSvc")]
    public class JustATestCSSvc : ServicedComponent,IJustATestCSSvc
    {

        public int Add(int a, int b)
        {
            return a + b;
        }

        public String SayHiTo(String someOne)
        {
            return "Hi " + someOne + "!";
        }
    }
}

4、项目的AssemblyInfo.cs中,增加下面几行

[assembly: ApplicationName("CSDll")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false,
AccessChecksLevel = AccessChecksLevelOption.ApplicationComponent)]

5、项目属性->Application->Assembly Infomation->Make Assembly COM-Visible->打勾

6、项目属性->Signing->Sign the assembly->打勾
项目属性->Signing->Choose a strong name key file->New

7、编译

8、注册

#这里一定要用正确版本的gacutil及RegAsm
C:\VBS\DNFW4\gacutil.exe /i CSCOMTest.dll
C:\VBS\DNFW4\RegAsm.exe CSCOMTest.dll
C:\VBS\DNFW4\RegSvcs.exe /c CSCOMTest.dll

9、反注册

#这里一定要用正确版本的gacutil及RegAsm
C:\VBS\DNFW4\gacutil.exe /u CSCOMTest
C:\VBS\DNFW4\RegAsm.exe /u CSCOMTest.dll
C:\VBS\DNFW4\RegSvcs.exe /u CSCOMTest.dll

10、查看注册结果(COM+)

#32位系统32位COM,64位系统64位COM
dcomcnfg
#64位系统32位COM
comexp.msc -32

COM+简单示例(01)

本篇文章介绍了如何用ATL写一个简单的COM+组件(Dll)。

1、VC新建工程,ATL->ATL Project(名称为ATLCOMP)->类型选择Dynamic Library(DLL)->勾选Support COM+ 1.0->Finish

2、工程视图,ATLCOMP工程,右键->Add->Class->ATL->ATL COM+ 1.0 Component->类名为JustATestCOMP,ProgID为ATLCOMP.JustATestCOMP

3、切换到类视图,ATLCOMP项目下的IJustATestCOMP接口上右键Add Method
名称:Add
参数1:[in]LONG a
参数2:[in]LONG b
参数3:[out,retval]LONG* c

4、类视图,ATLCOMP项目下的IJustATestCOMP接口上右键Add Method
名称:SayHiTo
参数1:[in]BSTR someOne
参数2:[out,retval]BSTR* retValue

5、打开JustATestCOMP.cpp完成两个函数

STDMETHODIMP CJustATestCOMP::Add(LONG a, LONG b, LONG* c)
{
	// TODO: Add your implementation code here
	*c = a+b;

	return S_OK;
}


STDMETHODIMP CJustATestCOMP::SayHiTo(BSTR someOne, BSTR* retValue)
{
	// TODO: Add your implementation code here
	CComBSTR sResult("Hi ");
	CComBSTR sName(someOne);
	CComBSTR sMark("!");
	sResult.AppendBSTR(sName);
	sResult.AppendBSTR(sMark);
	*retValue = sResult.Copy();

	return S_OK;
}

6、编译

7、注册

regsvr32 ATLCOMP.dll

8、反注册

regsvr32 /u ATLCOMP.dll

9、注册到COM+
9.1打开组件管理器

#32位系统32位COM,64位系统64位COM
dcomcnfg
#64位系统32位COM
comexp.msc -32

9.2选择到“Components Services-》Computers-》My Computer->COM+ Applications”
9.3右键“New-》Application(ATLCOMP)”
9.4选择到“Components Services-》Computers-》My Computer->COM+ Applications->ATLCOMP->Components”
9.5右键“New-》Component-》导入已注册的组件-》选择ATLCOMP.JustATestCOMP-》确定”

10、COM+反注册
找到对应的应用或组件,直接删除就好了