1

I am trying to use a third-party DLL written in ANSI C (compiled as C++). One of the functions has the following C++ prototype:

tResultCode GetAttrib
(
    tHandle     handle,
    tAttrib     attrib,
    void *      value,
    tAttribType valueType,
    int         valueMaxSize
);

Where the arguments of GetAttrib are as follows:

  • handle is a void pointer to an opaque structure within the DLL
  • attrib indicates which attribute to retrieve from the structure
  • value a pointer to a variable of the type indicated by valueType
  • valueType indicates the C++ type of the buffer allocated to hold the attribute value
  • valueMaxSize is the number of bytes allocated to variable pointed to by value

The eAttribType enumeration is defined as follows:

typedef enum eAttribType
{
    eHandle,
    eBool,
    eEnum,
    eInt,
    eLong,
    eFloat,
    eDouble,
    eDate,
    eTime,
    eTimestamp,
    eString,     // char *
    eChar,       // char
    eVoid,
    eHandle,
    eFunctionPointer
} tAttribType;

The eAttrib enumeration is defined as follows:

typedef enum eAttrib
{
    eInvoiceNumber, // eString
    eInvoiceDate,   // eTimestamp
    eUnits,         // eLong
    ePrice,         // eFloat
    eDiscount,      // eDouble
    ePreferredFlag, // eBool
    ...
} tAttrib;

I declare the unmanaged function pointer in C# as follows:

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    internal delegate eResultCode getAttrib(void** handle, eAttrib attrib, void* value, eAttribType attribType, int valueMaxSize);
    internal unsafe getAttrib GetAttrib;

Where:

  • handle is a void ** for the pointer to the C++ library structure
  • attrib mimics the C++ enumeration that indicates the attribute to retrieve
  • value is a void * to the C++ variable
  • attribType mimics the C++ enumeration that indicated the C++ type of value
  • valueMaxSize is an integer meant to represent the size of the variable pointed to by value

My problem is that I don't know how call GetAttrib() from C# with different data types for the void * (the value variable). Sometimes this variable will be a C++ char *, and other times it will be a C++ int, and still other times it will be a C++ enum, etc

If someone can show me how to properly allocate/populate a char * (eString) and a long (eLong) from C# I think that the rest will fall into place (hopefilly).

2 Answers 2

2

First, eString is a special case, since it's the only variable-length type. The other types are of a fixed size and should be easier to retrieve.

So let's start with eLong. This maps to a C++ long, which in turn should probably map to a C# int - but this is architecture dependent so your mileage my vary.

  • In C#, an int is always 32-bit, and a long is always 64-bit.
  • In C++, there's no fixed size. For all you know, an int is defined as at least 16-bit, and a long as at least 32-bit. In Visual C++, they're both 32-bit though.

Make sure you know what you're doing and you get the sizes right.

Now, you need a pointer to unmovable memory the unmanaged function can safely write to. As it happens, value type stack variables won't be moved by the GC, so you can simply write the following:

internal static unsafe int GetUnits(void** handle)
{
    int value;
    GetAttrib(handle, eAttrib.eUnits, &value, eAttribType.eLong, sizeof(int));
    return value;
}

Another method would involve stackalloc:

internal static unsafe int GetUnits(void** handle)
{
    var buffer = stackalloc int[1];
    GetAttrib(handle, eAttrib.eUnits, buffer, eAttribType.eLong, sizeof(int));
    return *buffer;
}

You could use this with a stackalloc byte[whatever] if there's no C# built-in type that could fit.

That's obviously untested as I don't have the lib, but it should do it.

As for strings, you'd need a managed array, so you can feed them into Encoding.GetString. Yeah, you need to know the encoding too. I'll assume ASCII here.

internal static unsafe string GetInvoiceNumber(void** handle)
{
    var buffer = new byte[512];
    fixed (byte* bufAddr = &buffer[0])
    {
        GetAttrib(handle, eAttrib.eInvoiceNumber, bufAddr, eAttribType.eString, buffer.Length);
        return Encoding.ASCII.GetString(buffer);
    }
}

As a managed array lives on the managed heap, you need to pin it so it won't be moved by the GC during operation. That's what the fixed statement does.

Sign up to request clarification or add additional context in comments.

1 Comment

This approach works well - thank you. Of note, I needed to trim trailing '\0' values in the returned string. e.g. Encoding.ASCII.GetString(buffer).Trim('\0')
0

For completeness: to set a string attribute via the C++ API the following method does the trick

internal static unsafe string SetInvoiceNumber(void** handle, string value)
{
    var buffer = new byte[512];
    fixed (byte* bufAddr = &buffer[0])
    {
        Encoding.ASCII.GetBytes(value, 0, Math.Min(value.length, buffer.Length - 1), buffer, 0);
        SetAttrib(handle, eAttrib.eInvoiceNumber, bufAddr, eAttribType.eString);
    }
}

Where the corresponding set function has the following C++ prototype:

tResultCode SetAttrib
(
    tHandle     handle,
    tAttrib     attrib,
    void *      value,
    tAttribType valueType
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.