在 C# 中将 CodeElements 编组到 IntPtr

Posted

技术标签:

【中文标题】在 C# 中将 CodeElements 编组到 IntPtr【英文标题】:Marshalling CodeElements to an IntPtr in C# 【发布时间】:2009-08-19 19:46:21 【问题描述】:

我正在编写一个 Visual Studio 插件,需要将一个托管的 CodeElements 对象编组为它的非托管形式。我只需要内存中的指针,因为我可以将它转换为非托管端的 CodeElement。

    [DllImport("CodeMethodsToString.dll")]
    private static extern BSTR* CodeMethodsToString(void* functionObject);

    public static void CodeMethodsToXML(XmlElement parent, CodeElements elements)
    
        //Call CodeMethodsToString: how do I marshall CodeElements to an IntPtr?
        //set XmlElement in here
    

我知道如何处理 XML,并且我在 C# 中有一个工作版本。我创建了非托管 DLL,因为在最低级别的递归调用所有各种成员变量会降低程序的速度。我只需要知道如何使用 System.Runtime.Interop.Marshal 将 CodeElements 对象转换为指向内存中 COM 对象的指针。

谢谢。

【问题讨论】:

DTB 似乎接近答案,但它似乎没有正确编组指针。获取对象引用未设置为对象实例或受保护的内存错误,仍需要通过编组进行更改。 【参考方案1】:

看起来乔纳森很接近。我就是这样做的:

[DllImport("CodeMethodsToString.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string CodeMethodsToString(IntPtr functionObject);

public static void CodeMethodsToXML(XmlElement parent, CodeElements elements)

   GCHandle pin;
   try
   
      pin = GcHandle.Alloc(elements, GCHandleType.Pinned);
      string methods = CodeMethodsToString(pin.AddrOfPinnedObject());
    
    finally
    
       pin.Free();
    

【讨论】:

就是这样。需要固定对象以防止 GC 移动指针,以避免和 AcessViolationException。 我很困惑——我无法让它在我的测试工具中工作。对于不是 CodeElements 类型的 RCW,我在 GCHandle.Alloc 上收到 ArgumentException。 Visual Studio 的 CodeElements 不是 TLBImped COM 接口吗? @Kim 你还在测试你的答案中的 DoStuff 实现吗? 是的,或者各种。为了让本机端获得 COM 接口指针,我不确定 GCHandle.Alloc 是否足够。但它甚至没有走那么远——对我的 tlbimp:ed 接口的 Alloc 调用失败。 AFAIK,指针就是指针。 GCHandleType.Pinned 只是确保垃圾收集器不会释放内存(因为本机应用程序正在使用它)。【参考方案2】:

CodeElements 是一个ComVisible 接口并且有一个GuidAttribute 吗?

然后 C# 将为您进行 COM 对象的编组,您可以简单地使用 CodeElements 作为参数类型:

[DllImport("example.dll")]
private static extern void DoStuff(CodeElements codeElements);

【讨论】:

恐怕没那么简单:CodeElements 是抽象的。我需要一个指向该对象的指针。我怎么会得到它? 澄清一下:当它进行这种默认编组时,它是按值传递的吗?有没有办法让它发挥它的编组魔法但只传递一个指针? 引用传递。我假设“CodeElements”指的是这个:msdn.microsoft.com/en-us/library/envdte.codeelements.aspx 所以我的例子应该可以工作。如果没有,请发布错误详细信息。 肯定有错误,但我真的很感激这个跟进:有一个 System.AccessViolation,试图读取或写入受保护的内存。我正在做你在 C# 代码中编写的内容,在 C++ 方面(这让我感到困惑)我有 DECL BSTR* CodeMethodsToString(EnvDTE::CodeElements &classObject);其中 DECL 是 dllexport 的宏。在任何情况下,它都错误地处理了这些点(因为类是抽象的,我不能简单地按值传递)。 为什么是&classObject 而不是*classObject【参考方案3】:

一旦看到星号或 & 号,您应该首先将其转换为 ref(指针的安全版本)。当我过去使用 ref 关键字时,我已经让引用类型神奇地开始工作(这是非常矛盾的 - 但我认为这是其中一个互操作的东西):

[DllImport("example.dll")]
private static extern void DoStuff(ref CodeElements codeElements);

你也可以试试:

[DllImport("example.dll")]
private static extern void DoStuff([In, Out] ref CodeElements codeElements);

或这些属性的排列之一。

您可能想尝试的一件事是使用 MFC(我想,自 C++ 以来已经有很长时间了)来制作 COM 库。不要使用本机调用 export the thing as a type library 并将其添加为 Visual Studio 中的引用(是的,就这么简单)。因此你会得到类似的东西:

myCoolClass.DoStuff(codeElements);

您可能还想固定它(如果您需要固定它,错误将是间歇性的)。我不记得RCW 是否会为您执行此操作(我几乎可以肯定它会执行此操作),所以这里是执行此操作的代码:

GCHandle handle = new GCHandle();
try

  handle = GCHandle.Alloc(fooz, GCHandleType.Pinned);
  // Use fooz.

finally

  if (handle.IsAllocated)
    handle.Free();

【讨论】:

ref 用于通过引用而不是值传递值类型(例如 struct)。 CodeElements 是一个 COM 接口,即引用类型,因此总是按引用传递。 我试过了,但它没有用,它可能在我正在做的其他编组的某种组合中起作用。感谢您提醒我这些关键字之一可能是相关的。 @dtb - 阅读!它说它在过去有所作为;即使看起来不应该 - 生成的 MSIL 并不相同,互操作层可能会将关键字视为相关。在纯 .Net 到 .Net 的调用中,同意,它没有任何效果。【参考方案4】:

您可以选择查看文章末尾的示例: GCHandle MSDN

【讨论】:

以上是关于在 C# 中将 CodeElements 编组到 IntPtr的主要内容,如果未能解决你的问题,请参考以下文章

在本机 C++ 中将变量从 C# 编组为 void*,并在本机程序内更改 Managed/C# 中的变量值

编组二维数组

将复杂结构编组到 C#

将浮点数组编组到 C#

将具有枚举成员的非托管结构编组到 c#

将引用类型从 C++ 编组到 C#