问题描述
考虑下面的代码,它打算由 C++ 使用 com 访问
Consider the code below that is meant to be accessed by C++ using com
namespace MarshalLib
{
//define an interface for account services
[ComVisible(true)]
[Guid("39B8A693-79BB-4638-92DE-245A88720953")]
public interface IAccountStructLookup
{
AccountStruct RetrieveAccount(int acctId);
void UpdateBalance(ref AccountStruct account);
Alias[] GetRef();
}
//Implement an account struct
[ComVisible(true)]
[Guid("DB48C5B6-9646-491A-B030-C0CADCFC03E0")]
public struct AccountStruct
{
public int AccountId;
[MarshalAs(UnmanagedType.BStr)]
public string AccountName;
[MarshalAs(UnmanagedType.Currency)]
public decimal Balance;
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
//[MarshalAs(UnmanagedType.SafeArray)]
//public Alias[] Aliases;
}
[ComVisible(true)]
[Guid("9829CAB3-4020-47EA-BE72-86EC7CFFAE1D")]
public struct Alias
{
public string Name;
}
//implement a class to provide account services
//using an AccountStruct
[ComVisible(true)]
[Guid("CEFE5CAA-5C7E-464F-8020-E0FC78180D9B")]
[ClassInterface(ClassInterfaceType.None)]
public class DniNetStructsObj : IAccountStructLookup
{
public AccountStruct RetrieveAccount(int acctId)
{
AccountStruct result = new AccountStruct();
if (acctId == 123)
{
result.AccountId = acctId;
result.AccountName = "myAccount";
result.Balance = 1009.95M;
//result.Aliases = new Alias[5];
//result.Aliases[0].Name = "1";
//result.Aliases[1].Name = "2";
//result.Aliases[2].Name = "3";
//result.Aliases[3].Name = "4";
//result.Aliases[4].Name = "5";
}
return result;
}
public void UpdateBalance(ref AccountStruct account)
{
//update the balance
account.Balance += 500.00M;
}
public Alias[] GetRef( )
{
Alias[] al= new Alias[2];
al[0].Name = "1";
al[1].Name = "2";
return al;
}
}
还有 C++ 方面
#include "stdafx.h"
#include "ConsoleApplication1.h"
#import "D:Source CodeMarshalLibMarshalLibinDebugMarshalLib.tlb" raw_interface_only
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// The one and only application object
CWinApp theApp;
using namespace std;
using namespace MarshalLib;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(NULL);
if (hModule != NULL)
{
// initialize MFC and print and error on failure
if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed
"));
nRetCode = 1;
}
else
{
try
{
CoInitialize(NULL);
IAccountStructLookupPtr api(__uuidof(DniNetStructsObj));
api->GetRef();
CoUninitialize();
}
catch (...)
{
}
}
}
else
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: GetModuleHandle failed
"));
nRetCode = 1;
}
return nRetCode;
}
当我调用 api-GetRef() 来获取结构数组时出现错误.请帮我从 c# 返回一个结构数组并在 c++ 中使用它.
I get an error when I call api-GetRef() to get an array of structs. Please help me return an array of structs from c# and use it in c++.
提前致谢.
推荐答案
返回数组的问题是,在 C++ 中你会看到一个指向 struct 的指针并且没有关于数组大小的信息.您可以尝试将其编组为 SAFEARRAY,但 IMO,SAFEARRAY 令人头疼.
The problem with returning the array is that in the C++ you will see a pointer to struct and have no information about array size. You can try to marshal it as a SAFEARRAY, but IMO, SAFEARRAYs are pain in the neck.
我更喜欢这样建模:
[ComVisible(true)]
[Guid("C3E38106-F303-46d9-9EFB-AD8A8CA8644E")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct MyStruct
{
public int Value;
// I marshal strings as arrays! see note at the bottom
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string Unit
}
[ComVisible(true),
Guid("BD4E6810-8E8C-460c-B771-E266B6F9122F"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
]
public interface IMyService
{
int GetData([MarshalAs(UnmanagedType.LPArray)] out MyStruct[] data);
}
客户端代码是这样的:
Lib::MyStruct* data;
long size = svc->GetData(&data);
for(size_t i = 0; i < size; ++i)
{
Lib::MyStruct& current = data[i];
long val = current.Value;
bstr_t unit = current.Unit;
// ...
}
// now you need to release the memory. However, if you marshal
// strings in struct as BSTRs, you need to first release them by
// calling SysFreeString. This is why I prefer to marshal strings
// as arrays whenever I can: you can still easily construct a bstr_t
// in your client code, but you don't need to release them explicitly
CoTaskMemFree(data);
关于 SAFEARRAY
s 的评论:仅当接口必须是自动化兼容即后期绑定即 IDispatch
接口即标记为 时才需要它们ComInterfaceType.InterfaceIsIDispatch
.如果不是这种情况(并且我将接口声明为自定义,即 ComInterfaceType.InterfaceIsIUnknown
),则使用标准数组非常好,并且它们与 SAFEARRAY
一样受到很好的支持.此外,使用自定义结构的 SAFEARRAY
会带来 一些额外的复杂度宁愿避免.如果你不需要后期绑定,那么没有理由和SAFEARRAY
s争吵.
With regard to comment about SAFEARRAY
s: they are required only if the interface must be automation compliant i.e. late-bound i.e. an IDispatch
interface i.e. marked as ComInterfaceType.InterfaceIsIDispatch
. If this is not the case (and I declared the interface as custom i.e. ComInterfaceType.InterfaceIsIUnknown
) using the standard arrays is perfectly fine and they are equally well supported as SAFEARRAY
s. Furthermore, working with SAFEARRAY
s of custom structs brings some additional complexity which I prefer to avoid. If you don't need late binding, there is no reason to fight with SAFEARRAY
s.
关于 CComSafeArray
,如文档所述,它不支持支持结构数组所需的 VT_RECORD
(另一种选择是将其编组为 VT_VARIANT
与 IRecordInfo
但我什至不会深入探讨).
With regard to CComSafeArray
, as documented, it doesn't support VT_RECORD
which is required to support arrays of structs (another option is to marshal it as VT_VARIANT
with IRecordInfo
but I won't even go into that).
这篇关于使用 com 可调用包装器将结构数组从 c# 传递到 C++的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!