【问题标题】:Passing back an array of custom objects from C#.NET DLL to MFC将一组自定义对象从 C#.NET DLL 传递回 MFC
【发布时间】:2017-08-19 23:29:31
【问题描述】:

这是一个示例 XML 数据文件:

<?xml version="1.0" encoding="utf-8"?>
<AssignmentHistory Version="171804">
    <W20160104>
        <StudentItems>
            <Item>
                <Name Counsel="13" NextCounsel="0" Completed="1">Name 1</Name>
                <Type>Bible Reading (Main)</Type>
            </Item>
        </StudentItems>
    </W20160104>
    <W20160111>
        <StudentItems>
            <Item>
                <Name Counsel="9" NextCounsel="9" Completed="0">Name 2</Name>
                <Type>Bible Reading (Main)</Type>
            </Item>
            <Item Description="Initial Call">
                <Name Counsel="37" NextCounsel="38" Completed="1">Name 1</Name>
                <Type>#1 Student (Main)</Type>
            </Item>
            <Item>
                <Name>Name 3</Name>
                <Type>Assistant</Type>
            </Item>
            <Item>
                <Name Counsel="48" NextCounsel="49" Completed="1">Name 4</Name>
                <Type>#2 Student (Main)</Type>
            </Item>
            <Item>
                <Name>Name 5</Name>
                <Type>Assistant</Type>
            </Item>
            <Item>
                <Name Counsel="27" NextCounsel="30" Completed="1">Name 6</Name>
                <Type>#3 Student (Main)</Type>
            </Item>
            <Item>
                <Name>Name 7</Name>
                <Type>Assistant</Type>
            </Item>
        </StudentItems>
    </W20160111>
</AssignmentHistory>

我编写了一些代码来读取 XML 数据文件并定位给定名称的未来分配历史记录。例如,如果这一周是 2016 年 1 月 4 日,并且我们正在获取名称 1 的历史记录,那么我的代码将返回一个条目列表(在本例中,只有 1 个,表示 2016 年 1 月 11 日这一周)。

我的代码:

我的方法运行良好:

public void ExtractFutureStudentHistory(String strStudent, DateTime datWeekOfMeeting, String strHistoryDatabase, out DateTime[] aryFutureDates, out string[] aryFutureAssignTypes, out int[] aryFutureStudyNo)
{
    XmlDocument docAssignHistory = new XmlDocument();

    aryFutureDates = null;
    aryFutureAssignTypes = null;
    aryFutureStudyNo = null;

    List<DateTime> listFutureDates = new List<DateTime>();
    List<string> listFutureAssignTypes = new List<string>();
    List<int> listFutureStudyNo = new List<int>();

    try
    {
        docAssignHistory.Load(strHistoryDatabase);

        // The data in the XML should already be in ascending date order

        // The data we want:

        // AssignmentHistory/<WYYYYMMDD>/StudentItems/Item/Name
        // AssignmentHistory/<WYYYYMMDD>/StudentItems/Item/Type
        XmlNodeList listHistory = docAssignHistory.SelectNodes("AssignmentHistory/*/StudentItems/Item[Name='" + strStudent + "']");
        foreach(XmlNode nodeHistoryItem in listHistory)
        {
            XmlNode weekNode = nodeHistoryItem.ParentNode.ParentNode;
            String strWeekDate = weekNode.Name.Substring(1); // This skips the preceding "W"

            DateTime dateHistoryItemWeekOfMeeting = new DateTime(Convert.ToInt32(strWeekDate.Substring(0, 4)),
                                    Convert.ToInt32(strWeekDate.Substring(4, 2)),
                                    Convert.ToInt32(strWeekDate.Substring(6, 2)));

            if (dateHistoryItemWeekOfMeeting.Date > datWeekOfMeeting.Date)
            {
                // We need to include it
                listFutureDates.Add(dateHistoryItemWeekOfMeeting);
                listFutureStudyNo.Add(Convert.ToInt32(nodeHistoryItem.SelectSingleNode("Name").Attributes["Counsel"].Value));
                listFutureAssignTypes.Add(nodeHistoryItem.SelectSingleNode("Type").InnerText);
            }
        }

        aryFutureDates = listFutureDates.ToArray();
        aryFutureStudyNo = listFutureStudyNo.ToArray();
        aryFutureAssignTypes = listFutureAssignTypes.ToArray();
    }
    catch (Exception ex)
    {
        SimpleLog.Log(ex);
    }
}

可能逻辑可以简化,但它有效。我的问题在于我的方法是 C# .NET DLL 的一部分。目前我有这个公共接口方法:

[Guid("xx")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
public interface IMSAToolsLibraryInterface
{
    void ExtractFutureStudentHistory(String strStudent, DateTime datWeekOfMeeting, String strHistoryDatabase, out DateTime[] aryFutureDates, out string[] aryFutureAssignTypes, out Int32[] aryFutureStudyNo);

 }

它工作正常。在 C++ MFC 中,我有三个 SAFEARRAY** 对象,一个 BSTR 类型,一个 DATE 类型和一个 int 类型。它本身没问题。

我的问题是,我的函数可以更改为输出单个对象列表吗?例如,如果我创建了一个类:

StudentItem 具有日期、作业类型和研究编号三个成员变量。

我尝试将我的函数参数更改为out List&lt;StudentItem&gt;,但这没有用。然后我把它改成out StudentItem[] 还是不行。

我将StudentItem 声明为具有三个成员的基本struct。声明这个对象的正确方法是什么,以便我可以将它作为数组传回以便在 MFC 中使用?

谢谢。

更新

第 1 步:

我在 DLL 项目中添加了一个新对象:

[Guid("xx")]
[ComVisible(true)]
public struct StudentItem
{
    public string Type { get; set; }
    public DateTime Week { get; set; }
    public int Study { get; set; }
}

第 2 步:

我在界面中添加了一个引用:

void ExtractFutureStudentHistory2(String strStudent, DateTime datWeekOfMeeting, String strHistoryDatabase, out StudentItem[] aryStudentItems);

第 3 步:

我添加调整后的方法:

public void ExtractFutureStudentHistory2(String strStudent, DateTime datWeekOfMeeting, String strHistoryDatabase, out StudentItem[] aryStudentItems)
{
    XmlDocument docAssignHistory = new XmlDocument();

    aryStudentItems = null;

    List<StudentItem> listStudentItems = new List<StudentItem>();

    try
    {
        docAssignHistory.Load(strHistoryDatabase);

        // The data in the XML should already be in ascending date order

        // The data we want:

        // AssignmentHistory/<WYYYYMMDD>/StudentItems/Item/Name
        // AssignmentHistory/<WYYYYMMDD>/StudentItems/Item/Type
        XmlNodeList listHistory = docAssignHistory.SelectNodes("AssignmentHistory/*/StudentItems/Item[Name='" + strStudent + "']");
        foreach (XmlNode nodeHistoryItem in listHistory)
        {
            XmlNode weekNode = nodeHistoryItem.ParentNode.ParentNode;
            String strWeekDate = weekNode.Name.Substring(1); // This skips the preceding "W"

            DateTime dateHistoryItemWeekOfMeeting = new DateTime(Convert.ToInt32(strWeekDate.Substring(0, 4)),
                                    Convert.ToInt32(strWeekDate.Substring(4, 2)),
                                    Convert.ToInt32(strWeekDate.Substring(6, 2)));

            if (dateHistoryItemWeekOfMeeting.Date > datWeekOfMeeting.Date)
            {
                StudentItem oItem = new StudentItem();
                oItem.Week = dateHistoryItemWeekOfMeeting;
                oItem.Type = nodeHistoryItem.SelectSingleNode("Type").InnerText;
                oItem.Study = Convert.ToInt32(nodeHistoryItem.SelectSingleNode("Name").Attributes["Counsel"].Value);

                listStudentItems.Add(oItem);
            }
        }

        aryStudentItems = listStudentItems.ToArray();
    }
    catch (Exception ex)
    {
        SimpleLog.Log(ex);
    }
}

第 4 步:

我编译 DLL。我遇到了一个问题:

1>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(4556,5): warning : 类型库导出器警告处理 'MSAToolsLibrary.StudentItem.k__BackingField,MSAToolsLibrary'。 警告:公共结构包含一个或多个非公共字段 将被导出。

【问题讨论】:

    标签: c# com


    【解决方案1】:

    这里有两个选择:

    删除自动实现的属性。这会以一种众所周知的方式公开您的结构,供 COM 客户端使用:

    [Guid("xx")]
    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct StudentItem
    {
        [MarshalAs(UnmanagedType.BStr)]
        public string Type;
        public DateTime Week;
        public int Study;
    }
    

    ... 或使用接口。 COM 接口完全支持属性:

    [Guid("xx")]
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface StudentItem
    {
        string Type { get; set; }
        DateTime Week { get; set; }
        int Study { get; set; }
    }
    

    作为旁注,您可以考虑按如下方式更改您的方法:

    StudentItem[] ExtractFutureStudentHistory2(String strStudent, DateTime datWeekOfMeeting, String strHistoryDatabase);
    

    这使得在您的客户端中使用该方法更容易,因为该数组现在被声明为标准返回参数。

    【讨论】:

    • 谢谢。在您提供的两个选择中,会有一个偏好吗?
    • 我推荐界面方法,因为它更容易使用。对于结构,您需要使用IRecordInfo 接口来访问其布局。
    【解决方案2】:

    您可以像下面的代码一样返回结构分配。我使用 xml linq 将整个 xml 解析为一个列表。可以查询列表对象

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;
    
    
    namespace ConsoleApplication73
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.xml";
            static void Main(string[] args)
            {
    
                XDocument doc = XDocument.Load(FILENAME);
    
                List<Assignment> assignments = doc.Root.Elements().Select(x => new
                {
                    items = x.Descendants("Item").Select(y => new Assignment()
                    {
                        date = DateTime.ParseExact(x.Name.LocalName.Substring(1),"yyyyMMdd",System.Globalization.CultureInfo.InvariantCulture),
                        _type = (string)y.Descendants("Type").FirstOrDefault(),
                        name = (string)y.Descendants("Name").FirstOrDefault(),
                        counsel = (string)y.Descendants("Name").FirstOrDefault().Attribute("Counsel"),
                        nextCounsil = (string)y.Descendants("Name").FirstOrDefault().Attribute("NextCounsel"),
                        completed = y.Descendants("Name").FirstOrDefault().Attribute("Completed") == null ? false :
                           ((int)y.Descendants("Name").FirstOrDefault().Attribute("Completed")) == 0 ? false : true
                    }).ToList()
                }).SelectMany(x => x.items).ToList();
            }
        }
        public struct Assignment
        {
            public string name { get; set; }
            public DateTime date { get; set; }
            public string counsel { get; set; }
            public string nextCounsil { get; set; }
            public Boolean completed { get; set; }
            public string _type { get; set; }
        }
    
    }
    

    【讨论】:

    • 谢谢。但这并没有考虑到 C# 应用程序是我从 C++ MFC 调用的 DLL ...
    • 那又怎样?只要结构在 c++ 中定义相同,您就可以在非托管内存中传递结构起始地址(指针),就像使用 c 语言应用程序一样。通过传递指针所做的一切就是将对象的起始地址压入调用堆栈。
    • 错误表明某些内容不公开。一切看起来都是公开的,除了: public static void ExtractFutureStudentHistory2(String strStudent, DateTime datWeekOfMeeting, String strHistoryDatabase, out StudentItem[] aryStudentItems);
    • David 再次制作不准确的 cmets。忽略是 cmets。
    • @jdweng 这个问题涉及 C++ MFC 程序和 C# 类库之间的 COM 互操作,通过 COM 使用 ComVisible 公开。您展示了一个控制台程序,该程序提供了一种解析 XML 的不同方法(对提问者来说不是问题),但忽略了互操作,这是问题的全部要点。你真的应该听我和提问者的意见,他们告诉你你不明白这个问题是关于什么的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-04
    • 2012-11-21
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 2021-10-30
    相关资源
    最近更新 更多