将一组自定义对象从 C#.NET DLL 传回 MFC
Passing back an array of custom objects from C#.NET DLL to MFC
这里是一个例子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 的历史记录,那么我的代码将 return 一个条目列表(在这种情况下,只有 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 的一部分。现在我有这个 public 接口方法:
[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
类型。它本身没问题。
我的问题是,我的函数可以更改为输出单个对象列表吗?例如,如果我创建了一个 class:
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)\Microsoft Visual
Studio17\Community\MSBuild.0\Bin\Microsoft.Common.CurrentVersion.targets(4556,5):
warning : Type library exporter warning processing
'MSAToolsLibrary.StudentItem.k__BackingField, MSAToolsLibrary'.
Warning: The public struct contains one or more non-public fields that
will be exported.
您可以 return 结构赋值,如下面的代码所示。我使用 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; }
}
}
这里有两个选择:
删除自动实现的属性。这将以众所周知的方式公开您的结构,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);
这使得在您的客户端中使用该方法变得更加容易,因为数组现在被声明为标准 return 参数。
这里是一个例子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 的历史记录,那么我的代码将 return 一个条目列表(在这种情况下,只有 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 的一部分。现在我有这个 public 接口方法:
[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
类型。它本身没问题。
我的问题是,我的函数可以更改为输出单个对象列表吗?例如,如果我创建了一个 class:
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)\Microsoft Visual Studio17\Community\MSBuild.0\Bin\Microsoft.Common.CurrentVersion.targets(4556,5): warning : Type library exporter warning processing 'MSAToolsLibrary.StudentItem.k__BackingField, MSAToolsLibrary'. Warning: The public struct contains one or more non-public fields that will be exported.
您可以 return 结构赋值,如下面的代码所示。我使用 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; }
}
}
这里有两个选择:
删除自动实现的属性。这将以众所周知的方式公开您的结构,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);
这使得在您的客户端中使用该方法变得更加容易,因为数组现在被声明为标准 return 参数。