【问题标题】:Exception Passing SAFEARRAY in VARIANT to C# From C++ COM Server从 C++ COM 服务器将 VARIANT 中的 SAFEARRAY 传递给 C# 的异常
【发布时间】:2012-03-12 07:28:53
【问题描述】:

我花了最后一天搜索文档,查看论坛帖子,并在谷歌上搜索尝试做一些我认为可以通过正确的信息轻松完成的事情。

我有一个非常大的现有 C++ 应用程序,它有一个已经定义的 COM 服务器,并公开了许多方法。我正在尝试在 C# 应用程序中使用这些 COM 方法(我在 C++ 方面经验丰富,但是是 C# 新手)。

所以在我的 VS2010 C# 应用程序中,我添加了 COM 服务器作为参考。 COM 方法在对象浏览器中可见,并且传递单值字符串、浮点数和整数似乎工作正常。

但我很难将 SAFEARRAY 值从 C++ COM 服务器中读取到 C# 应用程序中。最终,我需要将字符串数组从 C++ 服务器传递到 C# 应用程序,但在仅测试传递浮点数数组时,当我尝试强制转换包含浮点数数组的 System.Object 时,我有构建但失败并出现以下异常的代码到 (float[]) ,

“异常{System.InvalidCastException:无法将'System.Object[]'类型的对象转换为'System.Single[]'类型。”

通过智能我可以看到该对象包含正确的 8760 长浮点数组,但我无法在 C# 中访问该数据。

这里是C#端的代码(d2RuleSet是DOE2Com COM服务器中定义的一个接口)。上面的异常在下面的最后一行抛出。

DOE2ComLib.DOE2Com d2RuleSet;

d2RuleSet = new DOE2ComLib.DOE2Com();

System.Int32 i_Series =0;

System.Object pv_WeatherData;
float[] faWeatherData;

iOut = d2RuleSet.GetWeatherData(i_Series, out pv_WeatherData);
Type typeTest;
typeTest = pv_WeatherData.GetType();
int iArrayRank = typeTest.GetArrayRank();
Type typeElement = typeTest.GetElementType();                    
faWeatherData = (float[])pv_WeatherData;

下面是 idl 文件中定义 C++ COM 方法的部分

HRESULT GetWeatherData( [in] int iSeries, [out] VARIANT* pvWeatherData,    [out,retval]      int * piErrorCode);

下面是加载 VARIANT 数据的 C++ 代码。

void CDOE2BaseClass::GetWeatherData( int iSeries, VARIANT* pvWeatherData, int*   piErrorCode)
{
    *piErrorCode = 0;

    if (iSeries < 0 || iSeries >= D2CWS_NumSeries)
       *piErrorCode = 1;
    else if (m_faWeatherData[iSeries] == NULL)
       *piErrorCode = 3;
    else
   {
       SAFEARRAYBOUND rgsaBound;
       rgsaBound.lLbound = 0;
       rgsaBound.cElements = 8760;

      // First lets create the SafeArrays (populated with VARIANTS to ensure    compatibility with VB and Java)
       SAFEARRAY* pSAData = SafeArrayCreate( VT_VARIANT, 1, &rgsaBound );
       if( pSAData == NULL ) {
 #ifndef _DOE2LIB
          _com_issue_error( E_OUTOFMEMORY);
#else
//RW_TO_DO - Throw custom Lib-version exception
          OurThrowDOE2LibException(-1,__FILE__,__LINE__,0,"OUT OF MEMORY");
 #endif //_DOE2LIB
       }

      for (long hr=0; hr<8760; hr++)
      {
          COleVariant vHrResult( m_faWeatherData[iSeries][hr] );
          SafeArrayPutElement( pSAData, &hr, vHrResult );
       }

      // Now that we have populated the SAFEARRAY, assign it to the VARIANT pointer that we are returning to the client.
       V_VT( pvWeatherData ) = VT_ARRAY | VT_VARIANT;
       V_ARRAY( pvWeatherData ) = pSAData;
    }
}

在此先感谢您对这个问题的帮助,我觉得我在应该是一个简单的问题上花费了太多时间。另外,请发布任何涵盖本机 C++ 和 C# 之间互操作的链接或书籍(我想我已经对大部分 Visual Studio/MSDN 文档进行了反复研究,但也许我也错过了一些内容)。

------------------原始问题结束-------------------------- ---------------------------- 我正在编辑发布下面 phoog 成功解决方案的代码,以便其他人可以阅读和使用它。

int iOut = 0;
System.Int32 i_Series =0;
System.Object pv_WeatherData = null;

iOut = d2RuleSet.GetWeatherData(i_Series, out pv_WeatherData);
Type typeTest;
typeTest = pv_WeatherData.GetType();
int iArrayRank = typeTest.GetArrayRank();
Type typeElement = typeTest.GetElementType();

//float[] faWeatherData = (float[])pv_WeatherData;
float[] faWeatherData = ConvertTheArray((object[])pv_WeatherData);

....

float[] ConvertTheArray(object[] inputArray)
{
   float[] result = new float[inputArray.Length];
   for (var index = 0; index < result.Length; index++)
      result[index] = (float)inputArray[index];
   return result;
}

【问题讨论】:

  • 您确定pv_WeatherData 中的数据是IEEE 单浮点数而不是双精度数吗?如果将其转换为Object[] 或只是Array 并在调试器中检查它会发生什么?哦,我刚刚注意到它确实是一个 InvalidCastException。你的类型有误,需要强制转换。
  • 另一种情况会发生什么?例如如果我有一个 C++ 函数要在我的 C# 应用程序中使用,并且该函数期望接收一个矩阵,例如:MyUnmanagedFunction(&Array);如何传入数组?

标签: c# c++ com variant


【解决方案1】:

编组的 SAFEARRAY 是 System.Object[];您不能将其引用转换为System.Single[]。您必须投射各个元素。假设参数数组的所有元素实际上都是装箱的浮点数,这将起作用:

float[] ConvertTheArray(object[] inputArray)
{
    float[] result = new float[inputArray.Length];
    for (var index = 0; index < result.Length; index++)
        result[index] = (float)inputArray[index];
    return result;
}

您可以使用 Linq 减少输入次数,但由于您是 C# 新手,我认为更基本的解决方案可能会更好。

编辑

由于您在评论中指出您的 object[] 被引用为 object,因此使用示例如下:

object obj = GetMarshalledArray();
float[] floats = ConvertTheArray((object[])obj);

编辑 2

使用 Linq 的更短的解决方案:

float[] ConvertTheArray(object[] inputArray)
{
    return inputArray.Cast<float>().ToArray();
}

【讨论】:

  • phoog,非常感谢您的快速回复。问题是 pv_WeatherData 是单个对象而不是对象数组,因此当我尝试将其传递给 ConvertTheArray 时出现编译错误。 pv_WeatherData 对象具有 System.Array 类型,当我将光标悬停在它上面时,intellisense 会显示浮点数。我没有做任何自定义编组,只是在我的 C# 应用程序中引用了 COM 服务器的类型库。
  • ,我对 pv_WeatherData 代表的类型感到困惑。我在上面粘贴的异常说“System.InvalidCastException:无法将'System.Object []'类型的对象转换为'System.Single []'类型”。但是pv_WeatherData的声明是System.Object。但是当我在调试器中将鼠标悬停在 pv_WeatherData 上时,异常之前 pv_WeatherData 的类型是 object[]。
  • @TommyVee 如果对象引用指向System.Object[] 类型的对象,正如异常消息所暗示的,那么您可以将其转换为(object[])obj。
  • 感谢@phoog,演员阵容和你的功能奏效了。非常感谢,你能推荐一本关于 C++ C# COM 互操作的好书或网站吗?我现在在偏远的哥斯达黎加,但至少我可以订购。我将使用工作代码编辑我的问题,以便其他用户可以看到并使用它。
  • @TommyVee 不幸的是我不知道一本好书;我自己也不是特别精通这个话题。我完全从 C# 端得出了解决方案,使用异常消息中的信息,即返回的对象是一个 object[] 数组;其余的遵循 C# 类型系统的上下文。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
  • 1970-01-01
  • 2010-11-02
  • 2021-07-10
  • 2023-01-04
相关资源
最近更新 更多