Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @awscloud, former CTO @artsy, +@vestris, NYC

Email Twitter LinkedIn Github Strava
Creative Commons License

The Grey Rat

There’s a giant grey rat outside of my window and a bunch of non-union workers laboring in the rain. The union guy who’s guarding the rat and giving away flyers got too cold and went inside the building. But I digress, the post is about working with Unions in Java Native Access (JNA).

Preamble

I was trying to retrieve Active Directory forest trust information via DsGetForestTrustInformationW. The function takes a pointer to a PLSA_FOREST_TRUST_INFORMATION, a pointer to a pointer to an LSA_FOREST_TRUST_INFORMATION structure. So far so good, we just need to pay attention to the several levels of indirection: whenever we want the value of a pointer to something, it’s a ByReference.

public int DsGetForestTrustInformation(String serverName, String trustedDomainName, int Flags,
        PLSA_FOREST_TRUST_INFORMATION.ByReference ForestTrustInfo);
public static class PLSA_FOREST_TRUST_INFORMATION extends Structure {
    public static class ByReference extends PLSA_FOREST_TRUST_INFORMATION
        implements Structure.ByReference {
    }
    public LSA_FOREST_TRUST_INFORMATION.ByReference fti;
}

LSA_FOREST_TRUST_INFORMATION is a structure that contains a RecordCount number of PLSA_FOREST_TRUST_RECORD items. Those are pointers, so Entries is an array of pointers. Since we want the value of a pointer, we use ByReference again.

public static class LSA_FOREST_TRUST_INFORMATION extends Structure {
    public static class ByReference extends LSA_FOREST_TRUST_INFORMATION
        implements Structure.ByReference {
    }

    public NativeLong RecordCount;
    public PLSA_FOREST_TRUST_RECORD.ByReference Entries;
    public PLSA_FOREST_TRUST_RECORD[] getEntries() {
        return (PLSA_FOREST_TRUST_RECORD[]) Entries.toArray(RecordCount.intValue());
    }
}

A pointer to a record is simply a structure that contains a pointer to the record.

public static class PLSA_FOREST_TRUST_RECORD extends Structure {
    public static class ByReference extends PLSA_FOREST_TRUST_RECORD
        implements Structure.ByReference {
    }
    public LSA_FOREST_TRUST_RECORD.ByReference tr;
}

Union inside a Structure?

Still with me? The record is declared like this:

typedef struct _LSA_FOREST_TRUST_RECORD {
    ULONG Flags;
    LSA_FOREST_TRUST_RECORD_TYPE ForestTrustType; // type of record
    LARGE_INTEGER Time;
    union { // actual data
        LSA_UNICODE_STRING TopLevelName;
        LSA_FOREST_TRUST_DOMAIN_INFO DomainInfo;
        LSA_FOREST_TRUST_BINARY_DATA Data; // used for unrecognized types
    } ForestTrustData;
} LSA_FOREST_TRUST_RECORD;

Note that MSDN has a mistake here, missing the Time field, which gave me lots of headache and wasted hours of my time. Got to use definitions in platform SDK.

This is a union. How do you declare this in JNA?

A union is just like a structure, except that every field lives at an offset zero. In JNA, you must tell the union which field to use before reading the value.

public static class LSA_FOREST_TRUST_RECORD extends Structure {
    public static class ByReference extends LSA_FOREST_TRUST_RECORD
        implements Structure.ByReference {
    }
    public static class UNION extends Union {
        public static class ByReference extends UNION
            implements Structure.ByReference {
        }
        public LSA_UNICODE_STRING TopLevelName;
        public LSA_FOREST_TRUST_DOMAIN_INFO DomainInfo;
        public LSA_FOREST_TRUST_BINARY_DATA Data;
    }
    public NativeLong Flags;
    public int ForestTrustType;
    public LARGE_INTEGER Time;
    public UNION u;
    public void read() {
        super.read();
        switch(ForestTrustType) {
        case NTSecApi.ForestTrustTopLevelName:
        case NTSecApi.ForestTrustTopLevelNameEx:
            u.setType(LSA_UNICODE_STRING.class);
            break;
        case NTSecApi.ForestTrustDomainInfo:
            u.setType(LSA_FOREST_TRUST_DOMAIN_INFO.class);
            break;
        default:
            u.setType(LSA_FOREST_TRUST_BINARY_DATA.class);
            break;
        }
        u.read();
    }
}

In our case we override read() and set the type depending on the ForestTrustType value. Then re-read the union from memory. Voila.

Notes

Committed to JNA under com.sun.jna.platform.win32.