1

I'm trying to marshall Adobe XMPToolKitSDK XMPFiles.dll and XMPCore.dll. This is the first time I do such a thing and things seems to be working fine for methods that do not contain a parameter of type "void *". Please help me with the code below. Thanks!

The C++ function is:

void
WXMPMeta_GetProperty_1 ( XMPMetaRef       xmpObjRef,
                         XMP_StringPtr    schemaNS,
                         XMP_StringPtr    propName,
                         void *           propValue,
                         XMP_OptionBits * options,
                         SetClientStringProc SetClientString,
                         WXMP_Result *    wResult ) /* const */
{
    XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetProperty_1" )
    
        if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema );
        if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath );
        
        XMP_StringPtr valuePtr = 0;
        XMP_StringLen valueSize = 0;

        XMP_OptionBits voidOptionBits = 0;
        if ( options == 0 ) options = &voidOptionBits;

        bool found = thiz.GetProperty ( schemaNS, propName, &valuePtr, &valueSize, options );
        wResult->int32Result = found;
        
        if ( found && (propValue != 0) ) (*SetClientString) ( propValue, valuePtr, valueSize );

    XMP_EXIT
}

I have created a Wrapper method and a Delegate as below:

        //typedef void (* SetClientStringProc) (void* clientPtr, XMP_StringPtr valuePtr, UInt32 XMP_StringLen);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void SetClientStringProcDelegate(ref IntPtr clientPtr, string valuePtr, ulong valueLen);

        // 20   1F 00002E30 WXMPMeta_GetProperty_1 = WXMPMeta_GetProperty_1
        [DllImport(".\\XMPToolKitSDK\\XMPCore.dll")]
        public static extern void WXMPMeta_GetProperty_1(
            IntPtr xmpObjRef,
            string schemaNS,
            string propName,
            ref IntPtr propValue,
            ref ulong options,
            SetClientStringProcDelegate SetClientString,
            ref WXMP_Result wResult
            ); /* const */

And, to make my life easyer, I've also created a static class containing the following method:

        public static WXMP_Result getProperty(IntPtr xmpObjRef, string schemaNS, string propName, ref IntPtr propValue, ref ulong options, SetClientStringProcDelegate SetClientString)
        {
            WXMP_Result wResult = new WXMP_Result();
            XMPMetaWrapper.WXMPMeta_GetProperty_1(xmpObjRef, schemaNS, propName, ref propValue, ref options, SetClientString, ref wResult);
            return wResult;
        }

In my test code, I'm doing this:

string schemaNS = kXMP_NS_SKYVIEW;
string propName = "CustomField40";
IntPtr propValue = new IntPtr();
options = 0;
SetClientStringProcDelegate setClientStringDelegate = new SetClientStringProcDelegate(mySetClientString);
WXMP_Result getPropertyResult = XMPMeta.getProperty(xmpMetaObjRef, schemaNS, propName, ref propValue, ref options, setClientStringDelegate);

Console.WriteLine("getPropertyResult: " + propValue); 

// Callback Methods
public static void mySetClientString(ref IntPtr clientPtr, string valuePtr, ulong valueLen)
{
   Console.WriteLine("mySetClientString|clientPtr: " + clientPtr + "|valuePtr: " + valuePtr + "|valueLen: " + valueLen);
}

When I run my testCode, the Callback function is called and the result is:

XMP Obtido com sucesso: 472446411633

doesPropertyExistResult: 1

mySetClientString|clientPtr: 0|valuePtr: Objects detected | 4 Faces detected and 2 Faces identified|valueLen: 58

getPropertyResult: 0

As you can see, the property value was retrieved and printed by the Callback function "mySetClientString", as the valuePtr. But, for some reason I cannot figure out, the variable passed by reference "propValue" does not return anything. Am I correctly Marshaling the "void*" as IntPtr?

Please advise! Thanks in advance!

  • You marshal `void*` as `ref IntPtr` (which is fine since `void*` is a pointer/reference to anything), but you don't seem to be assigning it in `mySetClientString`. How do you expect the value be returned? – IS4 Aug 07 '22 at 23:19
  • Without the documentation it's hard to know how the marshalling is supposed to work. How do you know which side initializes and frees which pointers? And how do you know how to encode the strings? How do you know it's `StdCall`? – Charlieface Aug 08 '22 at 01:23
  • You can also use C++/CLI to write a wrapper in managed C++. Be sure to pin your memory you pass into the C++ code or the .Net garbage collector is allowed to move the memory C++ might be working on (which can lead to crashes/unexpected behavior). [Quick-C-CLI-Learn-C-CLI-in-less-than-10-minutes](https://www.codeproject.com/Articles/19354/Quick-C-CLI-Learn-C-CLI-in-less-than-10-minutes). Gives you and idea of what C++/CLI is. – Pepijn Kramer Aug 08 '22 at 03:00
  • @IS4 good point. But since clientPtr is of type IntPtr and valuePtr is of type string, how would I cast or pass the value back? Sorry for the newbie question. – Ilian Felinto Aug 08 '22 at 17:25
  • Please put the code for the solution in the answer, not the question – Charlieface Aug 08 '22 at 19:06

1 Answers1

0

Thanks all for your comments, specially to @IS4 for pointing what I was missing. The original C++ function receives a ref void (void*) which is then passed by ref to the callback function, which is supposed to set the final value that will be returned in this parameter.

There is a tricky matter that is to get the string returned by the original function and to return it as an IntPtr. I can't understand why Adobe did it that way, but the solution is to Marshal the string into an IntPtr inside the callback function and then, Mashal it back once the function is run.

Below is the changed code with the solution implemented:

string schemaNS = kXMP_NS_SKYVIEW;
string propName = "CustomField40";
IntPtr propValue = new IntPtr();
options = 0;
SetClientStringProcDelegate setClientStringDelegate = new SetClientStringProcDelegate(mySetClientString);
WXMP_Result getPropertyResult = XMPMeta.getProperty(xmpMetaObjRef, schemaNS, propName, ref propValue, ref options, setClientStringDelegate);

// SOLUTION
// Marshal the string back
Console.WriteLine("getPropertyResult: " + Marshal.PtrToStringAuto(propValue)); 
// Free the unmanaged memory
Marshal.FreeHGlobal(propValue);
//
// END SOLUTION

// Callback Methods
public static void mySetClientString(ref IntPtr clientPtr, string valuePtr, ulong valueLen)
{
   Console.WriteLine("mySetClientString|clientPtr: " + clientPtr + "|valuePtr: " + valuePtr + "|valueLen: " + valueLen);

   // SOLUTION
   // Marshall the string into an IntPtr
   clientPtr = Marshal.StringToHGlobalUni(valuePtr); 
   //
   // END SOLUTION
}