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'
		}
	}
}

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

好坑啊!