none
What is the correct way of using pin_ptr for manages value types? RRS feed

  • Question

  • Greetings!

    We have an issue regarding the passing of a managed struct to native function. A part of the struct is coming to the native code in a corrupted state.

    Previously we were pinning structs like this {1} (according to pin_ptr MSDN documentation):

    void TestFailing(ManagedVector v)
    { 
        pin_ptr<double> pLockV = &(v).x;    // <== {1}
        const NativeVector nv = ((const NativeVector&)*(NativeVector*)&(v).x);
        NativeFunc(nv);
    }


    Everything was fine until recently. Now, existing executables unexpectedly started to corrupt the data on some machines. Debugging shows, that NativeVector's constructor is receiving wrong arguments (y and z are wrong, although x is Ok). According to our research the problem occurs only with Release x64 builds. Furthermore, we have confirmed, that problem is occurring when the clr.dll v4.7.2600.0 is used, and is not occurring when clr.dll v4.6.1590 is used for the same executable.

    We have fixed it using pinning like {2} in the following sample. But we are failing to understand the corruption reasons, and we are not sure about validity of such a fix. The pin_ptr documentation is a little bit more obscure then I would prefer. And it lacks custom-value-type examples.

    Our code can be stripped down to the following sample, that fully reproduces the issue:

    static double g_x = 3;
    static double g_y = 5;
    static double g_z = 7; 
    
    [System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential)/*, Wrapper("mcsPoint3d")*/]
    value struct ManagedVector
    {
    	double x;
    	double y;
    	double z;
    	ManagedVector(double x_, double y_, double z_) : x(x_), y(y_), z(z_) { }
    };
    
    #pragma unmanaged
    struct NativeVector
    {
    	double x;
    	double y;
    	double z;
    	NativeVector(double x_, double y_, double z_) : x(x_), y(y_), z(z_) { }
    };
    
    void NativeFunc(const NativeVector& v)
    {
    	printf("%p %8f %8f %8f \n", &v, v.x, v.y, v.z);
    	if (v.x != g_x) { printf("v.x failed\n"); }
    	if (v.y != g_y) { printf("v.y failed\n"); }
    	if (v.z != g_z) { printf("v.z failed\n"); }
    }
    
    #pragma managed  
    
    void TestFailing(ManagedVector v)
    {
    	pin_ptr<double> pLockV = &(v).x; // <== {1} according to the pin_ptr documentation, as I understand it;
    	const NativeVector nv = ((const NativeVector&)*(NativeVector*)&(v).x);
    	NativeFunc(nv);
    }
    
    void TestFixed(ManagedVector v)
    {
    	pin_ptr<ManagedVector> pLockVF = &(v); // <== {2} my improvisation
    	const NativeVector nv = ((const NativeVector&)*(NativeVector*)&(v).x);
    	NativeFunc(nv);
    }
    
    int main()
    {
    	ManagedVector v(g_x, g_y, g_z);
    	printf("Souce Value:	%p %8f %8f %8f \n", &v, v.x, v.y, v.z);
    
    	printf("Original:	");
    	TestFailing(v);
    
    	printf("Fixed:		");
    	TestFixed(v);
    
    	scanf_s("%s", NULL);
    	return 0;
    }

    Output is like the following for some machines:

    Souce Value:    000000DA2518ED40 3.000000 5.000000 7.000000
    Original:       000000DA2518ED20 3.000000 0.000000 0.000000
    v.y failed
    v.z failed
    Fixed:          000000DA2518ECE8 3.000000 5.000000 7.000000


    The questions are:

    1) Is it invalid to use pin_ptr for value types like in {1} (implicitly pinning the whole struct via its member))?

    2) Is it valid to use pin_ptr for value types like {2} (explicitly pinning the whole struct))?

    3) Can the version of the clr.dll (or of some other .Net dll) really be the reason of this corruption unexpectedly showing up in existing application builds, or are we misinterpreting something?

    4) Is it really necessary to use pin_ptr in these Test functions at all? Isn't ManagedVector v copied into them, so it is not bound to any managed ref objects, and should be totally unaffected by the GC?

    PS: I can supply a solution demonstrating the issue, but cannot find a way to attach a file to my post.


    • Edited by Artoymyp Thursday, November 30, 2017 6:43 PM spellchecking
    Thursday, November 30, 2017 10:42 AM

Answers

  • Hi Artoymyp,

    I think it's a good practice to follow Viorel_'s suggestion. You declare x member first(so same address as your object) in your class so when you pass address of x, it may work, but I still don't think it's a good way to convert double* type to NativeVector* type.

    As for your question, you can refer to this link.

    1) "Pinning a sub-object defined in a managed object has the effect of pinning the whole object". So it's not invalid.

    2) It's valid.

    3) It may be, but I'm not sure.

    4) "A pinning pointer is an interior pointer that prevents the object pointed to from moving on the garbage-collected heap". In your scenario, you use copy assignment to initiate your actual parameter, so I think it's not necessary to use pin_ptr in this condition.

    Best Regards,

    Charles He


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.



    Wednesday, December 6, 2017 2:10 AM

All replies

  • Probably the optimiser believes that you only intend to access the x, therefore the other members are not passed.

    By the way, I think that you should obtain the object using the pin_ptr variable:

       pin_ptr<ManagedVector> pLock = &v;

       const NativeVector & nv = *(NativeVector*)pLock;

     

    According to documentation, pin_ptr can be used with value classes too, when there is nothing to pin.

    The next code seems to work too:

    #pragma managed 

    void Test( const ManagedVector & v )

    {

       const ManagedVector * p = &v;

       const NativeVector & nv = *(NativeVector*)p;

       NativeFunc( nv );

    }

    Friday, December 1, 2017 8:56 AM
  • Your pin_ptr<ManagedVector> example is working fine. Your Test example was working perfectly fine in our environment, too, just until 2 years ago, when the same data corruption had started and we had solved it using pin_ptr as in {1}. It seems that we had solved the problem incorrectly, and the problem is rising again. So I am trying to understand the true reasons and to find the bulletproof solution.

    I have tried out the following:

    void TestFailing(ManagedVector v)
    {
    	pin_ptr<double> pLockV = &v.x; // <== {1} according to the pin_ptr documentation, as I understand it;
    	const NativeVector & nv = *(NativeVector*)pLockV;
    	NativeFunc(nv);
    }

    The results are failing the same way. Also, passing v into function by reference ManagedVector & v yields correct results.

    Have you the answers for some of my numbered questions?

    Monday, December 4, 2017 7:58 AM
  • Hi Artoymyp,

    I think it's a good practice to follow Viorel_'s suggestion. You declare x member first(so same address as your object) in your class so when you pass address of x, it may work, but I still don't think it's a good way to convert double* type to NativeVector* type.

    As for your question, you can refer to this link.

    1) "Pinning a sub-object defined in a managed object has the effect of pinning the whole object". So it's not invalid.

    2) It's valid.

    3) It may be, but I'm not sure.

    4) "A pinning pointer is an interior pointer that prevents the object pointed to from moving on the garbage-collected heap". In your scenario, you use copy assignment to initiate your actual parameter, so I think it's not necessary to use pin_ptr in this condition.

    Best Regards,

    Charles He


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.



    Wednesday, December 6, 2017 2:10 AM
  • Thank you, Charles. You have really cleared things up for me. Also can anyone give me an advice? Is there a place where I can submit question (3) from this issue as a CLR-bug to Microsoft?
    Wednesday, December 6, 2017 7:23 AM
  • Hi Artoymyp,

    Thanks for your updating.

    As for advice, I still recommend Viorel_'s suggestion.

    What's more, you can follow this link to report your issue.

    Have a nice day!

    Best Regards,

    Charles He


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.



    Wednesday, December 6, 2017 7:59 AM