How to implement IOPMPropertyExpander2 in .NET

How to implement IOPMPropertyExpander2 in .NET

Anonymous
Not applicable
2,582 Views
18 Replies
Message 1 of 19

How to implement IOPMPropertyExpander2 in .NET

Anonymous
Not applicable

Hello,

I'm trying to implement a dynamic property for an entitytype in my .NET ExtensionApplication.

So far using several online examples I've managed to implement a 'default' dynamic property with a string, double or integer value using the IDynamicProperty2 interface.

Then I got a step further using the IDynamicEnumProperty for a dropdownlist.

I got these interfaces working implementing them in a C# interface refering to the COM imports with the specific GUID's

 

Also a specific group for the properties in the OPM was possible implementing the IAcPiCategorizeProperties interface which removed some difficulty specifying the interface myself in my C# interfaces.

 

Now the last hurdle is using the IOPMPropertyExpander2 so that the in OPM a specific dynamic property (Point3d) can be expanded into the X,Y and Z values like the Start X, Start Y and Start Z of a line and the button to pick a point becomes available.

At least, that's what I understand from the documentation.

Problem is that I can't find any examples or help for the implementation of the IOPMPropertyExpander2 in C#.

 

From some experimenting I've come to this interface definition:

 

    [ComImport(), Guid("d0f45feb-71d5-44ea-B1A0-6E2F27B2085D"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    public unsafe interface IOPMPropertyExpander2
    {

        void GetElementValue(
            [In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In] uint dwCookie,
            [In, Out, MarshalAs(UnmanagedType.Struct)]ref object varData);

        void SetElementValue(
            [In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In] uint dwCookie,
            [In, Out, MarshalAs(UnmanagedType.Struct)]ref object varData);

        void GetElementStrings([In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In] uint dwCookie,
            [Out]out IntPtr pCaStringsOut,
            [In, Out]ref IntPtr pCaCookiesOut);


        void GetElementGrouping([In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In, Out, MarshalAs(UnmanagedType.I2)]ref short groupingNumber);

        void GetGroupCount([In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In, Out, MarshalAs(UnmanagedType.I4)]ref long nGroupCnt);
    }

(The full contents of the interface definitions are included)

 

This seems to work, because when I implement it on my CustomPointPropertyClass at least the GetElementStrings method is triggered, but I've got no C++ knowledge, so I can't figure out what to return here to make the OPM expand the property in multiple values.

My interpretation of the help files is that this method specifies the different sub-elements.

I've been struggling with it for a while now but have come to the conclusion that I need some help.

 

 

Now for the questions:

- Is my definition of the GetElementStrings in my C# interface correct?

- How do i implement it so that i can specify a X, Y and Z coordinate?

- Will AutoCAD automagically show the point-picker and if not, how can I accomplish that?.

 

A couple of assumptions for the next steps:

1. when the different elements are defined the GetElementValue is called for each of the defined elements giving me the chance to  specify the values. 2. when the value of an element is changed the SetElementValue is called

 

 

Kind regards & thanks in advance,

Sietse Wijnker

 

Oh in addition, the dynamic properties are based on the example from Kean Walmsley. A big thanks to Kean for all the examples and information on how to extend AutoCAD with .NET!

0 Likes
2,583 Views
18 Replies
Replies (18)
Message 2 of 19

ActivistInvestor
Mentor
Mentor

Below is some (completely untested) code that was quickly adapted from an implementation of IPerPropertyBrowsing that I use with COM properties.

 

It should do most of what you need.

 

 

   class OPMUtils
   {
      [StructLayout(LayoutKind.Sequential)]
      public struct OPMLPOLESTR
      {
         public ulong cElems;
         public IntPtr pElems;
      }

      [StructLayout(LayoutKind.Sequential)]
      public struct OPMDWORD
      {
         public uint cElems;
         public IntPtr pElems;
      }

      // Fills an OPMLPOLESTR with strings from an IList<string>

      public static OPMLPOLESTR CreateOPMLPOLESTR(IList<string> strings)
      {
         OPMLPOLESTR opmLPOLESTR = new OPMLPOLESTR();
         if(strings != null)
         {
            opmLPOLESTR.cElems = (ulong) strings.Count;
            int size = Marshal.SizeOf(typeof(IntPtr));
            opmLPOLESTR.pElems = Marshal.AllocCoTaskMem(strings.Count * size);
            IntPtr ptr = opmLPOLESTR.pElems;
            foreach(string str in strings)
            {
               IntPtr tempPtr = Marshal.StringToCoTaskMemUni(str);
               Marshal.WriteIntPtr(ptr, tempPtr);
               ptr = new IntPtr(ptr.ToInt64() + size);
            }
         }
         return opmLPOLESTR;
      }

      // Fills an OPMDWORD with DWORDs from an IList<uint>

      public static OPMDWORD CreateOPMDWORD(IList<uint> values)
      {
         OPMDWORD opmDWORD = new OPMDWORD();
         if(values != null)
         {
            opmDWORD.cElems = (uint) values.Count;
            int size = Marshal.SizeOf(typeof(UInt32));
            opmDWORD.pElems = Marshal.AllocCoTaskMem(values.Count * size);
            IntPtr ptr = opmDWORD.pElems;
            foreach(uint value in values)
            {
               Marshal.WriteInt32(ptr, (int) value);
               ptr = new IntPtr(ptr.ToInt64() + size);
            }
         }
         return opmDWORD;
      }

   }
0 Likes
Message 3 of 19

Anonymous
Not applicable

Hi,

Thanks for providing the code for creating the reply in the GetElementStrings, but the OPM doesn't show any response.

The propertyname is shown but with an empty value. I've tried changing the GetCurrentValueType response but no changes there.
My hope is that anyone here knows any way why I can't get any respone. My suspicion is that my  interface definition is incorrect and that ACAD can't handle it (posted in the opening post).

 

My implementation of the GetElementStrings:

        public unsafe void GetElementStrings(
            [In] int dispID, 
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnknown,
            [In] uint dwCookie, 
            [ Out] out OPMLPOLESTR* pCaStringsOut, 
            [In, Out] ref OPMDWORD* pCaCookiesOut)
        {
                var dwlist = new List<uint> {1001, 1002, 1003};
                var stringlist = new List<string> {"X", "Y", "Z"};
                var strings = CreateOPMLPOLESTR(stringlist);
                var values = CreateOPMDWORD(dwlist);

                pCaStringsOut = &strings;
                pCaCookiesOut = &values;
        }

Can anyone please help me out?

 

Regards,

Sietse

0 Likes
Message 4 of 19

ActivistInvestor
Mentor
Mentor

@Anonymous wrote:

Hi,

Thanks for providing the code for creating the reply in the GetElementStrings, but the OPM doesn't show any response.

The propertyname is shown but with an empty value. I've tried changing the GetCurrentValueType response but no changes there.
My hope is that anyone here knows any way why I can't get any respone. My suspicion is that my  interface definition is incorrect and that ACAD can't handle it (posted in the opening post).

 

Can anyone please help me out?

 

Regards,

Sietse


You've only posted your implementation of GetElementStrings() which is most-likely not where the problem is.

0 Likes
Message 5 of 19

Anonymous
Not applicable

Hi Activist, you're completely right.

I've attached the custom property implementation.

The question remains the same.

 

Regards,

Sietse

0 Likes
Message 6 of 19

ActivistInvestor
Mentor
Mentor

Is the following actually working?

 

        public void GetCurrentValueData (object pUnk, ref object pVarData)
        {
            // TODO: Get the value and return it to AutoCAD
            //pVarData = (int) 10;
            // Because we said the value type was a 32b int (VT_I4)

            pVarData = new Point3d();
// as opposed to:
// pVarData = new Point3d().ToArray()) }

 

0 Likes
Message 7 of 19

Anonymous
Not applicable

No it isn't working, mainly because the method isn't even called.

I've set the GetCurrentValueType to 

        public void GetCurrentValueType (out ushort varType) {
            varType = (ushort)VarEnum.VT_USERDEFINED;
        }

And GetCurrentValueData to

        public void GetCurrentValueData (object pUnk, ref object pVarData)
        {
            pVarData = new Point3d().ToArray();
        }

The GetElementStrings is never called. When I set the CurrentValueType to VT_ARRAY the GetElementStrings IS called.

 

After that the GetElementGrouping and GetGroupCount are the last methods called. The GetElementValue is never called.

0 Likes
Message 8 of 19

ActivistInvestor
Mentor
Mentor

@Anonymous wrote:

No it isn't working, mainly because the method isn't even called.

I've set the GetCurrentValueType to 

        public void GetCurrentValueType (out ushort varType) {
            varType = (ushort)VarEnum.VT_USERDEFINED;
        }

And GetCurrentValueData to

        public void GetCurrentValueData (object pUnk, ref object pVarData)
        {
            pVarData = new Point3d().ToArray();
        }

The GetElementStrings is never called. When I set the CurrentValueType to VT_ARRAY the GetElementStrings IS called.

 

After that the GetElementGrouping and GetGroupCount are the last methods called. The GetElementValue is never called.


 

How about (VT_R8 | VT_ARRAY) ?

 

You also might try using a VariantWrapper.

0 Likes
Message 9 of 19

Anonymous
Not applicable

When VT_ARRAY is in the GetCurrentValueType the GetElementStrings is called.
I'm breaking my head on why the process is stopping after the GetElementGrouping and GetGroupCount are called.
I'm currently in the situation that Autocad comes chrashing down, so I'm definately doing something wrong.

 

I'm suspecting it has to do with me defining the interface differently than the way Autocad expects the propertyclass to be.

Is there a way to verify my interface-definition?

 

this is my interface:

    [ComImport(), Guid("d0f45feb-71d5-44ea-B1A0-6E2F27B2085D"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IOPMPropertyExpander2
    {

        void GetElementValue(
            [In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In] uint dwCookie,
            [Out]out object varData);

        void SetElementValue(
            [In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In] uint dwCookie,
            [In] object varData);

        void GetElementStrings([In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [In] uint dwCookie,
            [Out]out tagOPMLPOLESTR pCaStringsOut,
            [Out]out tagOPMDWORD pCaCookiesOut);


        void GetElementGrouping([In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [Out]out long groupingNumber);

        void GetGroupCount([In]int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)]object pUnknown,
            [Out]out short nGroupCnt);
    }

and this is my implementation:

        #region IOPMPropertyExpander2 implementation



        public void GetElementValue([In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnknown,
            [In] uint dwCookie,
            [Out] out object varData)
        {
            varData = 0.1; // testing
        }

        public unsafe void SetElementValue(
            int dispID, 
            object pUnknown, 
            uint dwCookie, 
            object varData)
        {
            throw new NotImplementedException();
        }

        // Caching of the strings
        private bool loadedProps = false;
        private unsafe tagOPMLPOLESTR _strings;
        private unsafe tagOPMDWORD _cookies;

        public unsafe void GetElementStrings(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnknown,
            [In] uint dwCookie,
            [Out] out tagOPMLPOLESTR pCaStringsOut,
            [Out] out tagOPMDWORD pCaCookiesOut)
        {
            if (!loadedProps)
            {
                loadedProps = true;
                var dwlist = new List<uint> { 1001, 1002, 1003 };
                var stringlist = new List<string> { "X", "Y", "Z" };
                var strings = tagOPMLPOLESTR.FromList(stringlist);
                var values = tagOPMDWORD.FromList(dwlist);

                //pCaStringsOut = strings;
                //pCaCookiesOut = values;

                _strings = strings;
                _cookies = values;
            }
            pCaStringsOut = _strings;
            pCaCookiesOut = _cookies;
        }



        IntPtr groupingnrPtr = IntPtr.Zero;
        public void GetElementGrouping([In] int dispID, 
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnknown, 
            [Out] out long groupingNumber)
        {
           groupingNumber = 3;
        }


        IntPtr groupCntPtr = IntPtr.Zero;
        public void GetGroupCount([In] int dispID, 
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnknown, 
            [Out] out short nGroupCnt)
        {
           nGroupCnt = 0;
        }

 
        #endregion
        public override void GetCurrentValueType (out ushort varType) {
            varType = (ushort) (VarEnum.VT_ARRAY | VarEnum.VT_R8);
        }

        public void GetCurrentValueData(object pUnk, ref object pVarData)
        {
                pVarData = new Point3d(10.1,20.2,30.3).ToArray(); // Testing
        }

On a side note, is there a way to get the OPM to display the pointpicker command button like f.i. the X, Y or Z coordinates of a line starting point have?

I'm suspecting that the implementation of the IOPMPropertyExpander2 won't do that automatically.

 

Regards,

Sietse

0 Likes
Message 10 of 19

ActivistInvestor
Mentor
Mentor

 

According to the docs:

 

virtual HRESULT STDMETHODCALLTYPE GetGroupCount(
    DISPID dispID, 
    IUnknown * pUnk, 
    long * nGroupCnt
) = 0;

 

And, for a simple 3D coordinate, this method should return E_NOTIMPL, because grouping is only used with properties that represent an array of coordinates, like polyline vertices or spline control points, where there's a spinner-type control that you can set to the vertex/control point index, and then that vertex or control point's coordinates appear.

 

I'm not sure why you would want the picker button to appear next to X, Y and Z components. It has never done what I would expect it to do (allow discrete editing of the X, Y, and Z components). They also behave unpredictably when the current UCS is not the WCS. My guess is that it has to do with the metadata you give to the OPM about the type of your property via GetCurrentValueName().

0 Likes
Message 11 of 19

Anonymous
Not applicable

2017-09-21 23_24_18-AutoCAD Application.png

I understood the docs in that the GetGroupCount can return 0 or throw a NotImplementedException because it's a single point.

The GetElementGrouping should return 3 because it's a 3D point and the GetElementsString returns three elements.

But now the both methods GetGroupCount and NotImplementedException are called (verified by breakpoints) bot after that ACAD becomes unresponsive and the message 'AutoCAD application has stopped working' pop up.

So somewhere in the three methods I'm returning a value that causes AutoCAD to crash.

 

 

 

0 Likes
Message 12 of 19

ActivistInvestor
Mentor
Mentor

@Anonymous wrote:

2017-09-21 23_24_18-AutoCAD Application.png

I understood the docs in that the GetGroupCount can return 0 or throw a NotImplementedException because it's a single point.

The GetElementGrouping should return 3 because it's a 3D point and the GetElementsString returns three elements.

But now the both methods GetGroupCount and NotImplementedException are called (verified by breakpoints) bot after that ACAD becomes unresponsive and the message 'AutoCAD application has stopped working' pop up.

So somewhere in the three methods I'm returning a value that causes AutoCAD to crash.

 

 

 


Your declaration of GetGroupCount() doesn't match the declaration I posted from the docs. You have a short, but interface declaration declares a long*

0 Likes
Message 13 of 19

344675003
Participant
Participant
        public static OPMLPOLESTR CreatetagOPMLPOLESTR(IList<string> strings)
        {
            OPMLPOLESTR opmLPOLESTR = new OPMLPOLESTR();
            if (strings != null)
            {
                opmLPOLESTR.cElems = (UInt32)strings.Count;
                int size = Marshal.SizeOf(typeof(IntPtr));
                opmLPOLESTR.pElems = Marshal.AllocCoTaskMem(strings.Count * size);
                IntPtr intptr = opmLPOLESTR.pElems;
                foreach (string str in strings)
                {
                    //IntPtr ptr = Marshal.StringToCoTaskMemUni(str); // FAIL
                    IntPtr tempptr = Marshal.StringToBSTR(str); // SUCCESS
                    Marshal.WriteIntPtr(intptr, tempptr);
                    intptr = new IntPtr(intptr.ToInt64() + size);
                }
            }
            return opmLPOLESTR;
        }
0 Likes
Message 14 of 19

344675003
Participant
Participant

My IOPMPropertyExpander2

    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    public struct OPMLPOLESTR
    {
        public UInt32 cElems;
        public IntPtr pElems;
    }
    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    public struct OPMDWORD
    {
        public UInt32 cElems;
        public IntPtr pElems;
    }

    [
     ComImport(),
     Guid("d0f45feb-71d5-44ea-B1A0-6E2F27B2085D"),
     InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    public unsafe interface IOPMPropertyExpander2
    {

        void GetElementValue(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In] uint dwCookie,
            [In, Out, MarshalAs(UnmanagedType.Struct)] ref object pVarOut);
        void SetElementValue(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In] uint dwCookie,
            [In, Out, MarshalAs(UnmanagedType.Struct)] ref object VarIn);
        void GetElementStrings(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In, Out, MarshalAs(UnmanagedType.Struct)] ref OPMLPOLESTR pCaStringsOut,
            [In, Out, MarshalAs(UnmanagedType.Struct)] ref OPMDWORD pCaCookiesOut);
        void GetElementGrouping(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In, Out, MarshalAs(UnmanagedType.I2)] ref short groupingNumber);
        void GetGroupCount(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In, Out, MarshalAs(UnmanagedType.I8)] ref long nGroupCnt);
    }

how to creat the struct OPMLPOLESTR and OPMDWORD

        public static OPMLPOLESTR CreateOPMLPOLESTR(IList<string> strings)
        {
            OPMLPOLESTR opmLPOLESTR = new OPMLPOLESTR();
            if (strings != null)
            {
                opmLPOLESTR.cElems = (UInt32)strings.Count;
                int size = Marshal.SizeOf(typeof(IntPtr));
                opmLPOLESTR.pElems = Marshal.AllocCoTaskMem(strings.Count * size);
                IntPtr intptr = opmLPOLESTR.pElems;
                foreach (string str in strings)
                {
                    //IntPtr ptr = Marshal.StringToCoTaskMemUni(str); // FAIL
                    IntPtr tempptr = Marshal.StringToBSTR(str); // SUCCESS
                    Marshal.WriteIntPtr(intptr, tempptr);
                    intptr = new IntPtr(intptr.ToInt64() + size);
                }
            }
            return opmLPOLESTR;
        }
        public static OPMDWORD CreateOPMDWORD(IList<uint> values)
        {
            OPMDWORD opmDWORD = new OPMDWORD();
            if (values != null)
            {
                opmDWORD.cElems = (UInt32)values.Count;
                int size = Marshal.SizeOf(typeof(UInt32));
                opmDWORD.pElems = Marshal.AllocCoTaskMem(values.Count * size);
                IntPtr intptr = opmDWORD.pElems;
                foreach (uint value in values)
                {
                    Marshal.WriteInt32(intptr, (int)value);
                    intptr = new IntPtr(intptr.ToInt64() + size);
                }
            }
            return opmDWORD;
        }
0 Likes
Message 15 of 19

344675003
Participant
Participant

this is my SUCCESS sample code in vs2019+AutoCAD2021

    /// <summary>
    /// 展开属性测试类
    /// </summary>
    [
        Guid("EC0AB7F1-5D92-4FF1-96AE-7AC537C1347E"),
        ProgId("OPMNETSample.ExpanderProperty.1"),
        ClassInterface(ClassInterfaceType.None),
        ComDefaultInterface(typeof(IDynamicProperty2)),
        ComVisible(true)
    ]
    public class ExpanderPropertyDemo : IDynamicProperty2, ICategorizeProperties, IOPMPropertyExpander2
    {
        private IDynamicPropertyNotify2 property_notify_ = null;
        private int m_numberOfVertices;
        private Point3d[] vertices;
        private int currrntIndex = 0; // Save the current vertex's index

        // 默认构造函数
        public ExpanderPropertyDemo()
        {
            m_numberOfVertices = 5;
            vertices = new Point3d[m_numberOfVertices];
            vertices[0] = new Point3d(1000, 1000, 0);
            vertices[1] = new Point3d(2000, 2000, 0);
            vertices[2] = new Point3d(3000, 3000, 0);
            vertices[3] = new Point3d(4000, 4000, 0);
            vertices[4] = new Point3d(5000, 5000, 0);

        }

        // IDynamicProperty2
        public void Connect(object pSink)
        {
            property_notify_ = (IDynamicPropertyNotify2)pSink;
        }
        public void Disconnect()
        {
            property_notify_ = null;            
        }
        public void GetCurrentValueData(object pUnk, ref object varData)
        {
            varData = null;
        }
        public void GetCurrentValueName(out string name)
        {
            name = null;
        }
        public void GetCurrentValueType(out ushort pVarType)
        {
            pVarType = 12; // VT_VARIANT = 12
        }
        public void GetDescription(out string description)
        {
            description = "扩展动态属性演示";
        }
        public void GetDisplayName(out string name)
        {
            name = "当前顶点";
        }
        public void GetGUID(out Guid propGUID)
        {
            propGUID = new Guid("EC0AB7F1-5D92-4FF1-96AE-7AC537C1347E");
        }
        public void IsPropertyEnabled(object pUnk, out int bEnabled)
        {
            bEnabled = 1;
        }
        public void IsPropertyReadOnly(out int bReadonly)
        {
            bReadonly = 0;
        }
        public void SetCurrentValueData(object pUnk, object varData)
        {
            /*
             * when pick a point, this method is called.
             */
            double[] xyz = (double[])varData;
            vertices[currrntIndex] = new Point3d(xyz[0], xyz[1], xyz[2]);
        }

        // ICategorizeProperties
        public void MapPropertyToCategory(int dispid, ref int propcat)
        {
            propcat = 4;
        }
        public void GetCategoryName(int propcat, uint lcid, out string name)
        {
            name = "扩展属性";
        }

        /*
         * c# IOPMPropertyExpander2
         */
        
        public void GetElementValue(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In] uint dwCookie,
            [In, MarshalAs(UnmanagedType.Struct), Out] ref object pVarOut)
        {
            int index = (int)dwCookie / 3;
            int subIndex = ((int)dwCookie - index * 3) % 3;
            pVarOut = vertices[index][subIndex];
            currrntIndex = index;
        }
        public void SetElementValue(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In] uint dwCookie,
            [In, MarshalAs(UnmanagedType.Struct), Out] ref object VarIn)
        {
            int index = (int)dwCookie / 3;
            int subIndex = ((int)dwCookie - index * 3) % 3;
            Point3d point = vertices[index];
            if (subIndex == 0)
            {
                vertices[index] = new Point3d((double)VarIn, point.Y, point.Z);
            }
            else if (subIndex == 1)
            {
                vertices[index] = new Point3d(point.X, (double)VarIn, point.Z);
            }
            else if (subIndex == 2)
            {
                vertices[index] = new Point3d(point.X, point.Y, (double)VarIn);
            }
        }
        public unsafe void GetElementStrings(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In, Out, MarshalAs(UnmanagedType.Struct)] ref OPMLPOLESTR pCaStringsOut,
            [In, Out, MarshalAs(UnmanagedType.Struct)] ref OPMDWORD pCaCookiesOut)
        {
            List<string> strings = new List<string> { "顶点 X 坐标", "顶点 Y 坐标", "顶点 Z 坐标" };
            List<uint> values = new List<uint> { 0, 1, 2 };
            pCaStringsOut = OPMUtils.CreateOPMLPOLESTR(strings);
            pCaCookiesOut = OPMUtils.CreateOPMDWORD(values);
        }
        public void GetElementGrouping(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In, MarshalAs(UnmanagedType.I2), Out] ref short groupingNumber)
        {
            groupingNumber = 3;
        }
        public void GetGroupCount(
            [In] int dispID,
            [In, MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In, MarshalAs(UnmanagedType.I8), Out] ref long nGroupCnt)
        {
            nGroupCnt = m_numberOfVertices;
        }   
        
    }

微信图片_20210819181941.png

0 Likes
Message 16 of 19

344675003
Participant
Participant

//IntPtr strptr = Marshal.StringToCoTaskMemUni(str); // FAIL
IntPtr strptr = Marshal.StringToBSTR(str); // SUCCESS

 

you must use Marshal.StringToBSTR!!!

NOT Marshal.StringToCoTaskMemUni.

0 Likes
Message 17 of 19

344675003
Participant
Participant

屏幕截图 2021-08-20 115922.png

Marshal.StringToBSTR: before the "Vertex X Cord" is 4 byte means the string length 1a = 2*13

0 Likes
Message 18 of 19

OoOAxue
Community Visitor
Community Visitor
error info:
‘Commands' does not implement interface member 'IOPMPropertyExpander2.GetElementStrings(int, object, ref tagOPMLPOLESTR, ref tagOPMDWORD),
'tagOPMLPOLESTR' is inaccessible due to its protection level

always prompt error
0 Likes
Message 19 of 19

OoOAxue
Community Visitor
Community Visitor
can you help me?
0 Likes