【问题标题】:Marshal a C# struct to C++ VARIANT将 C# 结构编组为 C++ VARIANT
【发布时间】:2013-10-31 15:06:35
【问题描述】:

首先,我对 COM 技术不是很熟悉,这是我第一次使用它,所以请多多包涵。 我正在尝试从 C# 调用 COM 对象函数。

这是idl-file中的接口:

[id(6), helpstring("vConnectInfo=ConnectInfoType")] 
HRESULT ConnectTarget([in,out] VARIANT* vConnectInfo);

这是我运行 tlbimp 后得到的互操作接口:

    void ConnectTarget(ref object vConnectInfo);

COM对象中目标函数的c++代码:

STDMETHODIMP PCommunication::ConnectTarget(VARIANT* vConnectInfo)
{
    if (!((vConnectInfo->vt & VT_ARRAY) && (vConnectInfo->vt & VT_BYREF)))
    {
        return E_INVALIDARG;
    }

    ConnectInfoType *pConnectInfo = (ConnectInfoType *)((*vConnectInfo->pparray)->pvData);
...
}

这个 COM 对象正在另一个进程中运行,它不在 dll 中。

我可以补充一点,COM 对象也被另一个用 C++ 编写的程序使用。在这种情况下没有问题,因为在 C++ 中创建了一个 VARIANT,并将 pparray->pvData 设置为 connInfo 数据结构,然后使用 VARIANT 作为参数调用 COM 对象。

据我了解,在 C# 中,我的结构应自动编组为 VARIANT。

这是我一直在使用的两种方法(或者实际上我已经尝试了更多......)从 C# 调用此方法:

    private void method1_Click(object sender, EventArgs e)
    {
        pcom.PCom PCom = new pcom.PCom();
        pcom.IGeneralManagementServices mgmt = (pcom.IGeneralManagementServices)PCom;
        m_ci = new ConnectInfoType();
        fillConnInfo(ref m_ci);
        mgmt.ConnectTarget(m_ci);
    }

在上述情况下,结构被编组为 VT_UNKNOWN。这是一个简单的情况,如果参数不是结构(例如,适用于 int),则可以使用。

    private void method4_Click(object sender, EventArgs e)
    {
        ConnectInfoType ci = new ConnectInfoType();
        fillConnInfo(ref ci);

        pcom PCom = new pcom.PCom();
        pcom.IGeneralManagementServices mgmt = (pcom.IGeneralManagementServices)PCom;

        ParameterModifier[] pms = new ParameterModifier[1];
        ParameterModifier pm = new ParameterModifier(1);
        pm[0] = true;
        pms[0] = pm;
        object[] param = new object[1];
        param[0] = ci;
        object[] args = new object[1];
        args[0] = param;
        mgmt.GetType().InvokeMember("ConnectTarget", BindingFlags.InvokeMethod, null, mgmt, args, pms, null, null);
    }

在这种情况下,它被编组为 VT_ARRAY | VT_BYREF | VT_VARIANT。问题是,在调试“目标函数”ConnectTarget 时,我无法在 SAFEARRAY 结构(或内存中的任何其他位置)中找到我发送的数据

我该如何处理 VT_VARIANT?

关于如何获取我的结构数据的任何想法?

更新:

ConnectInfoType 结构:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class ConnectInfoType
    {
        public short network;
        public short nodeNumber;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 51)]
        public string connTargPassWord;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
        public string sConnectId;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string sConnectPassword;
        public EnuConnectType eConnectType;
        public int hConnectHandle;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
        public string sAccessPassword;
    };

以及c++中对应的struct:

typedef struct ConnectInfoType
{
    short network;
    short nodeNumber;
    char connTargPassWord[51];
    char sConnectId[8];
    char sConnectPassword[16];
    EnuConnectType eConnectType;
    int hConnectHandle;
    char sAccessPassword[8];
} ConnectInfoType;

【问题讨论】:

  • search了吗?看起来有些事情可能会有所帮助。
  • 我已经搜索了一整天,帖子中的几乎所有内容都来自搜索。搜索仍在继续,谢谢。
  • @jortan,目前尚不清楚,您的目标是将ConnectInfoType 的数组从C# 传递到C++,还是只传递一个ConnectInfoType
  • @Noseratio,它是一个单一的 ConnectionInfoType。
  • @jortan,你为什么不能只传递一个结构,例如像这样:stackoverflow.com/a/18435743/1768303

标签: c# c++ com marshalling


【解决方案1】:

使用这个类http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.variantwrapper%28v=vs.110%29.aspx包装它,然后调用所需的方法。

你能贴出类型“ConnectInfoType”的c++源码吗?

【讨论】:

  • 如果这样做并在我的帖子中使用“method1”调用 COM 函数,我会收到异常“VariantWrappers 不能存储在 Variants 中。”。如果我使用“method4”执行此操作,它将被编组为 VT_UNKNOWN。
  • 尝试将 [MarshalAs(UnmanagedType.U4/SysUInt)] 添加到 c# struct enum 类型中的 enum 变量
【解决方案2】:

我找到了一个可接受的解决方案/解决方法。我将结构转换为字节数组,然后将接收到的数据转换为 ConnectInfoType 结构。

    // C#
    private void method3_Click(object sender, EventArgs e)
    {
        pcom.PCom PCom = new pcom.PCom();
        pcom.IGeneralManagementServices mgmt = (pcom.IGeneralManagementServices)PCom;
        ConnectInfoType ci = new ConnectInfoType();
        fillConnInfo(ref ci);

        IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(ci));
        Marshal.StructureToPtr(ci, ptr, false);
        byte[] arr = new byte[Marshal.SizeOf(ci)];
        Marshal.Copy(ptr, arr, 0, Marshal.SizeOf(ci));

        mgmt.ConnectTarget(arr);
    }

这将被编组为VT_ARRAY |VT_UI1,因此我将 c++ 代码更改为:

// C++
STDMETHODIMP PCommunication::ConnectTarget(VARIANT* vConnectInfo)
{
    if (!((vConnectInfo->vt & VT_ARRAY) && (vConnectInfo->vt & VT_UI1)))
    {
        return E_INVALIDARG;
    }

    ConnectInfoType *pConnectInfo = (ConnectInfoType *)(vConnectInfo->parray->pvData);
...
}

如果有人知道更好的解决方案,请随时添加。

【讨论】:

  • 您所做的是针对进程外服务器的推荐解决方案,如我提到的 MSKB 文章中所述:support.microsoft.com/kb/122289。在不显着改变 C++ 部分的情况下,似乎没有更好的方法来做到这一点。但是,您应该在调用 Marshal.Copy 之后释放分配的内存 (Marshal.FreeHGlobal),并在 C++ 端复制ConnectInfoType,而不是存储指向它的指针(不清楚是否这样做已经)。
猜你喜欢
  • 2014-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
  • 2022-11-22
  • 1970-01-01
相关资源
最近更新 更多