【问题标题】:How to Pass an ATL com object from c++ to c#如何将 ATL com 对象从 c++ 传递到 c#
【发布时间】:2022-01-26 08:09:12
【问题描述】:

我使用 ATl 创建了一个名为“Comptes”的 com 对象,它具有名称、用户名等属性。这是atl项目的idl文件

import "oaidl.idl";
import "ocidl.idl";

[
    object,
    uuid(15297371-6D05-4466-B80D-26207555CD28),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface Icompte : IDispatch{
    [propget, id(1)] HRESULT name([out, retval] BSTR* pVal);
    [propput, id(1)] HRESULT name([in] BSTR newVal);
    [propget, id(2)] HRESULT sername([out, retval] BSTR* pVal);
    [propput, id(2)] HRESULT sername([in] BSTR newVal);
    [propget, id(3)] HRESULT email([out, retval] BSTR* pVal);
    [propput, id(3)] HRESULT email([in] BSTR newVal);
    [propget, id(4)] HRESULT phone([out, retval] BSTR* pVal);
    [propput, id(4)] HRESULT phone([in] BSTR newVal);
};
[
    uuid(73A9DD4C-CE2D-4419-9F95-4A84C4E3B271),
    version(1.0),
]
library ATLOBJECTSLib
{
    importlib("stdole2.tlb");
    [
        uuid(1199A9AD-8AF7-4A25-A10B-DD06FB294B2E)      
    ]
    coclass compte
    {
        [default] interface Icompte;
    };
};

我已经在 c++ mfc 项目中包含头文件和 cpp 文件,并且能够建立 com 对象,现在在 mfc 项目中我正在向 c# 项目发送数据(只是简单的字符串) 这是 mfc 部分:

void CmfcappDlg::OnBnClickedCreateacountbutt()
{

    CoInitializeEx(nullptr, COINIT::COINIT_MULTITHREADED);
    Icompte* pcompte;

    HRESULT hr = CoCreateInstance(CLSID_compte,nullptr,CLSCTX_INPROC_SERVER,IID_Icompte,(LPVOID*)&pcompte);
    if (SUCCEEDED(hr)) 
    {
        namectrl.GetWindowText(name);
        sernamectrl.GetWindowText(sername);
        emailctrl.GetWindowText(email);
        phonectrl.GetWindowText(phone);
        
        pcompte->put_name(name.AllocSysString());
        pcompte->put_sername(sername.AllocSysString());
        pcompte->put_phone(phone.AllocSysString());
        pcompte->put_phone(email.AllocSysString());
        //Managed::showcswindow(name, sername);

    }

    CoUninitialize();
}

现在我可以使用此类将 dat 发送到 c# 部分:

#include "stdafx.h"
#include "Managed.h"
#using <System.dll>
using namespace System;
using namespace ::gettingdatafromc;

void Managed::showcswindow(CString name,CString sername)
{
    services::showwindow(gcnew String(name), gcnew String(sername));
}

并使用此类在 c# 项目中接收它:

namespace gettingdatafromc
{
    public static class services
    {
        public static void showwindow(string name, string sername)
        {

        }
    }
}

除了我想通过将 com 对象作为参数传递给 showwindow 方法而不是简单的字符串给 c# 项目来发送 com 对象之外,一切都很好,这甚至是可操作的,

【问题讨论】:

  • 您可以将 IWhatever(IUnknown 派生)COM 接口传递给方法(就像您使用 BSTR 一样:[propget, id(1)] HRESULT blah([out, retval] IWhatever** pVal); [propput, id(1)] HRESULT blah([in] IWhatever* newVal); 或者您可以像这样传递 VARIANT:[propget, id(1)] HRESULT blah([out, retval] VARIANT* pVal); [propput, id(1)] HRESULT blah([in] VARIANT newVal); 并包装对象( IUnknown* 或 IDispatch*) 进入 VARIANT
  • COM interop 和通过 C++/CLI 进行互操作是不同的技术。听起来您希望 COM 互操作位于 C++/CLI 互操作之上,这很奇怪。在不理解问题的情况下很难产生答案。你想要什么?
  • 确保 ATL 项目已构建并注册。在 C# 项目中,使用 Project > Add Reference > COM 选项卡,选择您的 ATL 库。您现在可以使用 ATLOBJECTSLib.Icompte 作为方法参数类型。

标签: c# c++ mfc com atl


【解决方案1】:

尝试将原始IUnkown* 指针传递给C#,然后使用Marshal.GetObjectForIUnknownIntPtr 转换为object,然后将此对象转换为Icompte。您需要在 C# 项目中添加对相应 TypeLib 的引用才能执行此类转换。

【讨论】:

  • 您的方法运行良好,我可以在 c# 中接收对象并读取其数据,现在我在修改对象属性时遇到问题,我可以读取这些属性但无法修改它们,我得到一个错误 | ||(引发了'System.AccessViolationException 类型的异常) 试图读取或写入受保护的内存。这通常表明其他内存已损坏|||。
  • 从异常中我只能说你很可能在 C++ 实现中的内存管理方面存在一些问题。只有使用完整的代码(或者更好的是,使用一些最小的可重现样本)才能进行进一步的诊断。
【解决方案2】:

感谢您的友好回复,所以我在应用程序中遵循了您的指南,我使用了新的 showwindow2 方法:

CoInitializeEx(nullptr, COINIT::COINIT_MULTITHREADED);
    Icompte* pcompte;
    
    HRESULT hr = CoCreateInstance(CLSID_compte,nullptr,CLSCTX_INPROC_SERVER,IID_Icompte,(LPVOID*)&pcompte);
    if (SUCCEEDED(hr)) 
    {
        namectrl.GetWindowText(name);
        sernamectrl.GetWindowText(sername);
        emailctrl.GetWindowText(email);
        phonectrl.GetWindowText(phone);
        
        pcompte->put_name(name.AllocSysString());
        pcompte->put_sername(sername.AllocSysString());
        pcompte->put_phone(phone.AllocSysString());
        pcompte->put_phone(email.AllocSysString());
        
        //Managed::showcswindow(name, sername);
        Managed::showcswindow2(pcompte);
    }

    CoUninitialize();

在 managed.cpp 中找到了,我将 Iunknow 传递给 csahrp 方法):

 void Managed::showcswindow2(IUnknown* p)
{
    
    services::showwindow2((IntPtr)p);

}

在 csharp 代码中:

public static void showwindow2(IntPtr p)
        {

            Icompte c = (Icompte)Marshal.GetObjectForIUnknown(p);
            string b =  c.name;
        }

该 dll 已被注册并包含在 refrence 中;

现在的问题是csharp代码中对象的属性为空,字符串b为空。

【讨论】:

  • 不能确定为什么属性是空的。从您成功将对象转换为接口后,看起来对象已顺利传递。出于测试目的,您还可以尝试将测试无参数方法(将显示 MessageBox 或在磁盘上创建文件)添加到接口中,并尝试从 C# 调用此类方法以检查 COM 对象是否已传递和可访问。另请查看 Hans Passant 的评论 - 他说这是解决问题的更简单方法。
  • 它工作得很好,实际上我忘了填充 atl 项目中属性的 put 方法填充了这些东西,一切都像魔术一样工作我可以从 c++ 填充对象并从 c# 检索值
猜你喜欢
  • 1970-01-01
  • 2014-10-08
  • 2016-06-26
  • 1970-01-01
  • 1970-01-01
  • 2016-08-12
  • 1970-01-01
  • 2018-07-29
相关资源
最近更新 更多