Search code examples
c#c++structdllpinvoke

Pass a struct* parameter from c# to a native c++ dll


I want to pass a struct as parameters from my C# application to a function in a C++ dll. The method is called correctly and works fine but ignores all set params and always runs with the default params.

In C++ the struct is defined as follows:

    [DllImport("cgm2vector_api.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern CGM_STATUS cgm2vector(
    [MarshalAs(UnmanagedType.LPWStr)] string inputFile,
    [MarshalAs(UnmanagedType.LPWStr)] string outputFile,
    [MarshalAs(UnmanagedType.LPWStr)] string logDest,
    [MarshalAs(UnmanagedType.LPWStr)] string configFile,
    ref CGM2VECTOR_PARAMS @params);

public static CGM_STATUS Convert(string inputFile, string outputFile, int units, string logDest = null, string configFile = null)
{
    var u = (CGM2VECTOR_UNITS)units;

    var @params = new CGM2VECTOR_PARAMS
    {
        width = 0,
        height = 0,
        units = (int)u,
        demoflag = 0,
        lineScale = 1.0,
        logMode = 0,
        uncompressed = 0,
        colorMode = 0,
        reverseVideo = 0,
        mediaColor = 0,
        boundsCheckRatio = 0,
        outputType = 2,
        marginSize = [0, 0],
        marginUnits = (int)CGM2VECTOR_UNITS.CGM2VECTOR_PTS,
        lineWidthMin = -1,
        lineWidthMax = -1,
        extentCheckMargin = 0,
        CSSformat = 0,
        genRegions = 0,
        seqIdentifiers = 0,
        usePaths = 0,
        rotation = 0
    };

    return Cgm2VectorApi.cgm2vector(inputFile, outputFile, logDest, configFile, ref @params);
}

Here is my param struct. I also tried LayoutKind.Explicit but it did not change anything

[StructLayout(LayoutKind.Sequential)]
public struct CGM2VECTOR_PARAMS
{
    public double width;
    public double height;
    public int units;
    public long demoflag;
    public double lineScale;
    public long logMode;
    public int uncompressed;
    public long colorMode;
    public long reverseVideo;
    public long mediaColor;
    public float boundsCheckRatio;
    public int outputType;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public float[] marginSize;
    public long marginUnits;
    public double lineWidthMin;
    public double lineWidthMax;
    public float extentCheckMargin;
    public int CSSformat;
    public int genRegions;
    public int seqIdentifiers;
    public int usePaths;
    public double rotation;
}

The h file of the c++ methods looks likes this.

#pragma once

#ifdef WIN32
#    define CALL_TYPE __stdcall
#else
#    define CALL_TYPE
#endif /* WIN32 */

#ifdef __cplusplus
#define EXTERN
extern "C" {
#else
#define EXTERN extern
#endif /*__cplusplus*/

/* Status return code used by all of the CGM entry points. */

#ifndef CGM_STATUS_DEFINED
#define CGM_STATUS_DEFINED
typedef enum {
   CGM_STATUS_OK,
   CGM_STATUS_ERROR,
   CGM_STATUS_FILE_DOES_NOT_EXIST,
   CGM_STATUS_ERROR_OPENING_FILE,
   CGM_STATUS_NOT_CGM,
   CGM_STATUS_ARG_ERROR,
   CGM_STATUS_TAKES_TOO_MUCH_MEMORY,
   CGM_STATUS_IS_CHARACTER,     // cgm is character encoded which is not supported
   CGM_STATUS_EVAL_EXPIRED,     // evaluation period has expired
   CGM_STATUS_EXTENT_ERROR,     // check if graphical extent extends beyond picture extent.
   CGM_STATE_ERROR,             // caused by out of sequence control element
   CGM_TOO_MANY_UNRECOGNIZED,   // > 10 undefined element ids usually indicating corrupt cgm file
   CGM_STATUS_EXCEPTION_ERROR   // try-catch exception
} CGM_STATUS;
#endif

typedef enum {
    CGM2VECTOR_INCHES,
    CGM2VECTOR_CMS,
    CGM2VECTOR_PTS,
    CGM2VECTOR_PERCENT,
    CGM2VECTOR_MM
} CGM2VECTOR_UNITS;

typedef struct CGM2VECTOR_PARAMS {
    double width;       // page width in inches, centimeters, millimeters, points, or points
    double height;      // page height in inches, centimeters, millimeters, points, or points
                        /* page size logic...
                        [0] > 0. && [1] > 0. - page width and page height as specified
                        [0] = 0. && [1] = 0. - page width and height will be CGM dimensions

                        [0] < 0. && [1] < 0. - shrink to fit within [0]-[1] maintaining CGM aspect ratio
                        [0] < 0. && [1] = 0. - shrink to fit [0] maintaining CGM aspect aspect ratio
                        [0] < 0. && [1] > 0. - force page height to [1], if width < abs([0]) OK... else ?
                        [0] = 0. && [1] < 0. - shrink to fit [1] maintaining aspect
                        [0] = 0. && [1] > 0. - force page height to [1] maintaining aspect
                        [0] > 0. && [1] < 0. - force page width to [0], if height < abs(height) OK... else ?
                        [0] > 0. && [1] = 0. - force page width to [0] maintaining aspect */
    int units;          /* page width & height units:
                        CGM2VECTOR_INCHES  - specified in inches
                        CGM2VECTOR_CMS     - specified in centimeters
                        CGM2VECTOR_PTS     - specified in points
                        CGM2VECTOR_PERCENT - specified in percent
                        CGM2VECTOR_MM      - specified in millimeters */
    long demoflag;      // insert demo banner in output: 0= no demo, 1= add 
    double lineScale;   // line width scale factor
    long logMode;       // 0= no log, 1= log to console/file
    int  uncompressed;  // image data compression- 0= PNG compressed, 1= uncompressed
    long colorMode;     // 0= do nothing, 1= greyscale, 2= greyscale enhanced (turn nonwhite/non fills to black)
    long reverseVideo;  // 0= do nothing, 1= reverse black and white
    long mediaColor;    // 0= do nothing, 1= black, 2= white, 3= transparent
    float boundsCheckRatio; /* use to remove excessive whitespace:
                        if area of visible bounds covers less then this fraction of
                        the area of CGM (VDC) extent then the visible bounds (plus 5% or 
                        margins if specified) will be used as the CGM (VDC) extent */
    int outputType;     // 0= PDF, 1= EPS, 2= SVG (3= WIN32-Cairo 4=WIN32-GDI 5=WIN32-GL)
    float marginSize[2];/* margin size: [0] = width, [1] = height in INCHES, CM, POINTS, PERCENT, or MM */
    long marginUnits;   /* margin size units: 
                        CGM2VECTOR_INCHES - margin size specified in inches
                        CGM2VECTOR_CMS    - margin size specified in centimeters
                        CGM2VECTOR_PTS    - margin size specified in points 
                        CGM2VECTOR_MM     - margin size specified in millimeters */
    double lineWidthMin;    // line width minimum
    double lineWidthMax;    // line width maximum
    float extentCheckMargin; // 0= do not check extent, >0= generate error if graphical extent extends beyond CGM picture extent
                             // + margin. Example: .05 if graphical content extends beyond picture extent + 5 percent margin
    int CSSformat;      // for SVG outputType; 0=use inline styling (larger file size, but easier to join),
                        //                    !0=use internal CSS (produces smaller fill size, harder to join)
    int genRegions;     // generate hotspots regions when grobject is missing region attribute but has name attribute
    int seqIdentifiers; // for SVG; 0=generate random identifier for clip path Ids for easier merging
                        //          1=use sequential identifier for clip path Ids (to reproduce exact output images)
    int usePaths;       // 0= convert polylines and polygons to svg polyline (line for 2 points) and polygon elements
                        // 1= use <path> elements instead to reduces svg file size
    double rotation;    // rotate image output, acceptable values are 0., 90., 180., 270.
} CGM2VECTOR_PARAMS;

CGM_STATUS CALL_TYPE cgm2vector(
    const wchar_t *input_file,  // [in] CGM input file path
    const wchar_t *output_file, // [in] vector output file path
    wchar_t *log_dest,          // [in] log destination (file path or memory pointer)
    const wchar_t *config_file,
    CGM2VECTOR_PARAMS *params);

#ifdef __cplusplus
}
#endif /*__cplusplus*/

Why does it always run with default params? I would have expected if something was wrong with my wrapper to not run at all.


Solution

  • In C#, long is a keyword for .NET's System.Int64, which represents a 64-bit signed integer.

    In C++, long's size can vary but it's often a 32-bit signed integer (check your compiler).

    So if your C++ long's size is indeed 32-bit, you must use C#'s int (or .NET System.Int32) otherwise you'll get an offset mismatch in the structure and anything can and will happen.

    PS: .NET Marshal.SizeOf, Marshal.OffsetOff and C++ sizeof, offsetof can be used also to compare overall structures offsets and sizes.