About neohope

一直在努力,还没想过要放弃...

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+反注册
找到对应的应用或组件,直接删除就好了

DCOM简单示例(05)

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

1、首先要导入COM组件编译时生成的TBL文件,ATLService.tlb

2、然后用下面的代码就可以调用了,好简单哦

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using ATLServiceLib;

namespace CSTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Type justATestType = System.Type.GetTypeFromProgID("ATLService.JustATestSvc","172.16.172.3");
            IJustATestSvc svc = (IJustATestSvc)System.Activator.CreateInstance(justATestType);
            int c = svc.Add(1, 2);
            System.Console.WriteLine(c);
            String retValue = svc.SayHiTo("dcom");
            System.Console.WriteLine(retValue);
            System.Console.Read();
        }
    }
}

DCOM简单示例(04)

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

首先是本地调用:
代码中展示了三种获取CLSID的方式,任选一种就好了。

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

#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
        const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}

MIDL_DEFINE_GUID(CLSID, CLSID_JustATestExe,0xD49F1BFF,0x60FB,0x4A43,0xA9,0xAA,0x49,0x93,0xD5,0x55,0x95,0x74);

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

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

    // 由 CLSID 启动组件,并得到 IDispatch 指针
    IDispatch * pDisp = NULL;   
    hr = ::CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp);

    /*
    // 从ATLExe_i.c获取GUID
    // 由 CLSID 启动组件,并得到 IDispatch 指针
    IDispatch * pDisp = NULL;   
    hr = ::CoCreateInstance(CLSID_JustATestExe, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp);
    */
    
    /*
    // 通过CLSIDFromString获取CLSID
    LPWSTR guidstr = L("{D49F1BFF-60FB-4A43-A9AA-4993D5559574}");
    GUID guid;
    hr = CLSIDFromString(guidstr, (LPCLSID)&guid);
    if (!SUCCEEDED(hr))
    {
        std::cout << "CLSIDFromString failed";
        DWORD d = GetLastError();
        getchar();
        return -1;
    }

    // 由 CLSID 启动组件,并得到 IDispatch 指针
    IDispatch * pDisp = NULL;
    hr = ::CoCreateInstance(guid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp);
    */

    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 = L"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;
}

然后是远程调用:

#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"ATLService.JustATestSvc", &clsid);
    if(!SUCCEEDED(hr))
    {
        std::cout << "clsid not found" << std::endl;
        getchar();
        return -1;
    }

    // 由 CLSID 启动组件,并得到 IDispatch 指针
    //IDispatch * pDisp = NULL; 
    //hr = ::CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp);

    COSERVERINFO coServerInfo;
    ZeroMemory(&coServerInfo, sizeof(COSERVERINFO));
    coServerInfo.pwszName = TEXT("127.0.0.1");
    coServerInfo.pAuthInfo = NULL;
    coServerInfo.dwReserved1 = 0;
    coServerInfo.dwReserved2 = 0;
    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;
}

DCOM简单示例(03)

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

1、testDCOM.vbs

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

'清除错误状态
Err.Clear

'本地调用
'Set Obj=CreateObject("ATLExe.JustATestExe")
'Set Obj=CreateObject("ATLExe.JustATestExe.1")
'远程调用
Set Obj=CreateObject("ATLExe.JustATestExe","127.0.0.1")
'Set Obj=CreateObject("ATLExe.JustATestExe.1","127.0.0.1")

'输出错误信息
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 testDCOM.vbs

3、如果是本机测试,一般不会遇到权限问题,但如果是远程测试的话,要先进行配置的,我之前写过这样的文章,打开可以参考一下。

DCOM简单示例(02)

本篇文章介绍了如何写一个简单的DCOM服务端(NT服务模式)。

1、VC新建工程,ATL->ATL Project(名称为ATLService)->类型选择Service(EXE)->Finish

2、工程视图,ATLService工程,右键->Add->Class->ATL->ATL Simple Object->类名为JustATestSvr,ProgID为ATLService.JustATestSvr

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

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

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

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

	return S_OK;
}


STDMETHODIMP CJustATestSvr::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、编辑JustATestSvc.rgs,在TypeLib一行前,增加这样一行

val AppID = s '%APPID%'

这是MS的一个大bug,浪费了我好几个小时。

7、编译

8、注册

ATLService.exe /service

9、运行

net start ATLService

10、停止

net stop ATLService

11、反注册

ATLService.exe /unregserver

DCOM简单示例(01)

本篇文章介绍了如何写一个简单的DCOM服务端(EXE模式)。

1、VC新建工程,ATL->ATL Project(名称为ATLExe)->类型选择Executable(EXE)->Finish

2、工程视图,ATLExe工程,右键->Add->Class->ATL->ATL Simple Object->类名为JustATestExe,ProgID为ATLExe.JustATestExe

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

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

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

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

	return S_OK;
}


STDMETHODIMP CJustATestExe::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、注册

ATLExe.exe /regserver

8、反注册

ATLExe.exe /unregserver

ATL三种项目的使用方式

ATL向导允许我们建立三种项目:
1、DLL
2、EXE
3、NT服务

三种项目的使用方式如下:
1、DLL
A、注册

regsvr32 xxx.dll

B、反注册

regsvr32 /u xxx.dll

2、EXE
A、注册

xxx.exe /regserver

B、反注册

xxx.exe /unregserver

3、NT服务
A、注册

xxx.exe /service

B、反注册

xxx.exe /unregserver

DCOM服务授权配置

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

Error: 70
Description: Permission denied

这时就要进行授权。

首先是服务端配置:
1、首先在DCOM服务端新建一个用户,比如DCOMTEST

2、在DCOM服务端运行

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

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

4、在“服务组件-》计算机-》我的电脑-》DCOM配置”,对应的DCOM组件上,右键,属性
常规-》身份验证级别-》无
安全-》启动与激活权限,添加新增用户DCOMTEST,并授权
安全-》访问权限,添加新增用户DCOMTEST,并授权

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

搞定!

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

ATL NT Service 调用超时

最近发现了VS(VS2008,VS2010,VS2012,VS2013)的一个大Bug,就是在新建ATL项目时,如果直接选择Service,则服务无将法正确调用。其表现为:

在客户端调用CoCreateInstance或CreateObject会返回:

Error: 429
Description: ActiveX 部件不能创建对象

在服务端会返回(CLSID会根据实际情况发生变化):

The server {7A387102-53AE-4A3A-8F28-5EE76C2BC1E4} did not register with DCOM within the required timeout.
服务器 {7A387102-53AE-4A3A-8F28-5EE76C2BC1E4} 没有在限定的时间内用 DCOM 注册。

经过多方排查,最后发现,是在rgs文件中少了一行

val AppID = s '%APPID%'

调整后的rgs文件如下:

HKCR
{
	ATLService.JustATestSvc.1 = s 'JustATestSvc Class'
	{
		CLSID = s '{7A387102-53AE-4A3A-8F28-5EE76C2BC1E4}'
	}
	ATLService.JustATestSvc = s 'JustATestSvc Class'
	{		
		CurVer = s 'ATLService.JustATestSvc.1'
	}
	NoRemove CLSID
	{
		ForceRemove {7A387102-53AE-4A3A-8F28-5EE76C2BC1E4} = s 'JustATestSvc Class'
		{
			ProgID = s 'ATLService.JustATestSvc.1'
			VersionIndependentProgID = s 'ATLService.JustATestSvc'
			ForceRemove Programmable
			LocalServer32 = s '%MODULE%'
			{
				val ServerExecutable = s '%MODULE_RAW%'
			}
			val AppID = s '%APPID%'
			TypeLib = s '{9D5B6B0C-85D6-4DB6-B88A-915180B89038}'
			Version = s '1.0'
		}
	}
}

重新编译后,就可以调用成功了。

好坑啊!

Oracle与SQL Server同步技术对比

方式 Oracle SQL Server 说明
备份还原 备份还原 备份还原 简单粗暴,无法实时,无法实现增量
日志备份 备库(Dataguard) 数据库镜像(Database Mirroring)
日志传输(Log Shipping)
读写操作受限
集群 RAC(Real Application Clusters) 集群(Database Cluster) RAC配置复杂,SQL Server集群只有单节点工作。实际存储只有一份。
视图 物化视图(Materialized View) 索引视图(Indexed Views) 不可改表结构,如增加字段等。
数据变更捕获 CDC(Change Data Capture) CDC(Change Data Capture) 不够灵活,无法配置只想获取部分事件,数据量很大。
订阅发布 ogg(Oracle Golden Gate)
流复制(Stream Replication)
高级复制(Oracle advanced Replication)
订阅发布(Publish and Subscribe)
数据库复制(Database Replication)
订阅发布(Publish and Subscribe)
最灵活的方式了,但也有限制。如果ogg在源加一列,或订阅发布的快照过期了,就惨了