Reading 3DPDF Streams in ExchangeSharp

Hi, this is a question regarding exchange sharp.

I’m currently working on reading multiple PRC streams from a 3D PDF. I had no trouble following and implementing the example in the C++ programming guide (2.1 Simple Load and Export). When trying to replicate in C# however, I’ve run into an issue.

Up until the API call to A3DAsmModelFileLoadFromPrcStream, everything works as expected. I’m able to marshal the data from IntPtr to A3DStream3DPDFData and the m_acstream (“PRC \u001b”) and m_uiStreamSize both appear to be correct based on the working solution’s data in C++. The call to A3DAsmModelFileLoadFromPrcStream however, does not set the model file pointer to any model, and instead points to IntPtr.Zero.
Strangely, ppPrcReadHelper IS modified to point to some assigned address by the call, but the ppModelFile is not.

Finally, the API call to A3DAsmModelFileLoadFromPrcStream returns the status A3D_LOADPRC_INITIALIZATION_FAILURE. Any help/tips on likely suspects would be appreciated. Below is a snippet of code covering my attempt at a simple use case. Thanks,

A3DStream3DPDFData d;
API.Initialize(out d);

//Create IntPtr to stream
IntPtr pdfPtr = Marshal.AllocHGlobal(d.m_usStructSize);
Marshal.StructureToPtr(d, pdfPtr, false); //Unnecessary line I think
int iNumStreams;

API.A3DGet3DPDFStreams(pcFileName, pdfPtr, out iNumStreams);
if(iNumStreams == 0) { return; }

IntPtr ptr = Marshal.ReadIntPtr(pdfPtr);

//Only testing first prc stream for now
var file = (A3DStream3DPDFData)Marshal.PtrToStructure(ptr, typeof(A3DStream3DPDFData));

    //If type of embedded model is prc
    if (file.m_bIsPrc)
    {
        IntPtr ppModelFile;
        IntPtr ppReadHelper;
        //Load the model file from prcstream
        // m_acStream = "PRC \u001b"
        API.A3DAsmModelFileLoadFromPrcStream(file.m_acStream, file.m_uiStreamSize, out ppReadHelper, out ppModelFile);
        //At this point ppModelFile unexpectedly points to nothing
    }

Hey @Josh_KS ,
Hope you’re doing well.

I got the workflow you’re after working using the following code:

            IntPtr stream_data_array;
            Int32 n_streams = 0;
            if(A3DStatus.A3D_SUCCESS != API.A3DGet3DPDFStreams( input_file, out stream_data_array, out n_streams) ) {
                Console.WriteLine("Failure getting streams.");
                return;
            }
            for(Int32 stream_idx = 0; stream_idx < n_streams; ++stream_idx ) {
                var ptr = IntPtr.Add( stream_data_array, stream_idx * Marshal.SizeOf<A3DStream3DPDFData>() );
                var stream_data = Marshal.PtrToStructure<A3DStream3DPDFData>(ptr);
                IntPtr model_file;
                IntPtr helper = IntPtr.Zero;
                if( A3DStatus.A3D_SUCCESS != API.A3DAsmModelFileLoadFromPrcStream(stream_data.m_acStream, stream_data.m_uiStreamSize, ref helper, out model_file ) ) {
                    Console.WriteLine("Failure to read PRC stream.");
                }


                var model_file_data = new A3DAsmModelFileWrapper( model_file );
                int indent = 0;
                Write( GetName( model_file ), indent++ );
                for( int idx = 0; idx < model_file_data.m_uiPOccurrencesSize; ++idx ) {
                    var po = Marshal.ReadIntPtr(model_file_data.m_ppPOccurrences, idx * Marshal.SizeOf( typeof(IntPtr) ) );
                    RecursivelyPrintName( po, indent );
                }
            }

This code is a modification of examples/PrintProductStructure, thus the GetName and RecursivelyPrintName

** I didn’t test this with a file that has more than one stream, so the Marshal.ReadIntPtr is not really tested, but I’m pretty sure it’s correct.

For this code to work correctly, I had to update a couple a few things in the binding. The tool used to generate the C# binding wants to marshal C-style byte arrays to strings. In this case, that’s not what’s wanted, since the byte array is used as a buffer instead. So these changes remove the unwanted marshaling.

API.cs:
-        public delegate A3DStatus PFA3DAsmModelFileLoadFromPrcStream([MarshalAs(UnmanagedType.LPStr)] string pcBufferStream, uint uBufferLength, out IntPtr ppPrcReadHelper, out IntPtr ppModelFile);
+        public delegate A3DStatus PFA3DAsmModelFileLoadFromPrcStream(IntPtr pcBufferStream, uint uBufferLength, ref IntPtr ppPrcReadHelper, out IntPtr ppModelFile);

-        public delegate A3DStatus PFA3DGet3DPDFStreams([MarshalAs(UnmanagedType.LPStr)] string pcFileName, IntPtr ppStreamData, out int piNumStreams);
+        public delegate A3DStatus PFA3DGet3DPDFStreams([MarshalAs(UnmanagedType.LPStr)] string pcFileName, out IntPtr ppStreamData, out int piNumStreams);

Classes.cs:
-        public string m_acStream => _d.m_acStream;
+        public IntPtr m_acStream => _d.m_acStream;

Structs.cs:
-        [MarshalAs(UnmanagedType.LPStr)] public string m_acStream;
+        public IntPtr m_acStream;

Hope this works for you! Please let us know.

Thanks,
Brad (:bread:)

3 Likes