传回自定义对象的数组从C#.NET的DLL到MFC

问题描述:

下面是一个示例XML数据文件:传回自定义对象的数组从C#.NET的DLL到MFC

<?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的历史记录,那么我的代码将返回一个条目列表(在这种情况下,2016年1月11日的一周中只有1个)。

我的代码:

我的方法是优良表现:

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<StudentItem>,但没有奏效。然后我将其更改为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)的\微软的Visual Studio的 \ 2017年\社区\的MSBuild \ 15.0 \斌\ Microsoft.Common.CurrentVersion。目标(4556,5): 警告:类型库导出警告处理 'MSAToolsLibrary.StudentItem.k__BackingField,MSAToolsLibrary'。 警告:公共结构包含一个或多个 将被导出的非公用字段。

你这里有两种选择:

删除自动实现的属性。这将以一种众所周知的方式公开您的结构,这可以由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); 

这使得它更容易使用的方法在你的客户,因为数组现在被声明为一个标准的返回参数。

+0

谢谢。你提供的两种选择中会有一种偏好? –

+1

我推荐使用界面方法,因为它更易于使用。使用结构体时,您需要使用IRecordInfo接口来访问其布局。 – Aurora

您可以像下面的代码一样返回一个结构赋值。我使用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; } 
    } 

} 
+0

谢谢。但是这没有考虑到C#应用程序是我从C++ MFC调用的DLL ... –

+0

那么,什么?只要结构在C++中定义相同,就可以像使用c语言应用程序一样在非托管内存中传递结构起始地址(指针)。通过传递一个指针,你所做的就是将对象的起始地址压入调用堆栈。 – jdweng

+0

查看我更新的问题。 –