如何在 JNA 中正确映射 CERT_SELECT_STRUCT

How do I properly map CERT_SELECT_STRUCT in JNA

我在我的 Java 项目中使用 jna-4.5.1。 这是我要复制的 cryptdlg 结构 CERT_SELECT_STRUCT

typedef struct tagCSSA {
  DWORD           dwSize;
  HWND            hwndParent;
  HINSTANCE       hInstance;
  LPCSTR          pTemplateName;
  DWORD           dwFlags;
  LPCSTR          szTitle;
  DWORD           cCertStore;
  HCERTSTORE      *arrayCertStore;
  LPCSTR          szPurposeOid;
  DWORD           cCertContext;
  PCCERT_CONTEXT  *arrayCertContext;
  LPARAM          lCustData;
  PFNCMHOOKPROC   pfnHook;
  PFNCMFILTERPROC pfnFilter;
  LPCSTR          szHelpFileName;
  DWORD           dwHelpId;
  HCRYPTPROV      hprov;
} CERT_SELECT_STRUCT_A, *PCERT_SELECT_STRUCT_A;

我的项目的示例 Java 代码。

public class Crypto {
    public interface Cryptdlg extends Library {
        Cryptdlg INSTANCE = (Cryptdlg) Native.loadLibrary("Cryptdlg", Cryptdlg.class, W32APIOptions.DEFAULT_OPTIONS);

        public boolean CertSelectCertificate(CERT_SELECT_STRUCT pCertSelectInfo);

        public static class CERT_SELECT_STRUCT extends Structure {

            private static final List<String> fieldOrder = createFieldsOrder("dwSize", "hwndParent", "hInstance",
                    "pTemplateName", "dwFlags", "szTitle", "cCertStore", "arrayCertStore", "szPurposeOid",
                    "cCertContext", "arrayCertContext", "lCustData", "pfnHook", "pfnFilter", "szHelpFileName",
                    "dwHelpId", "hprov");

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

            public int dwSize = size();
            public HWND hwndParent;
            public HINSTANCE hInstance;
            public String pTemplateName;
            public int dwFlags;
            public String szTitle;
            public int cCertStore;
            public Pointer arrayCertStore;
            public String szPurposeOid;
            public int cCertContext;
            public Pointer arrayCertContext;
            public WinDef.LPARAM lCustData;
            public Pointer pfnHook = null;
            public Pointer pfnFilter = null;
            public String szHelpFileName;
            public int dwHelpId;
            public HCRYPTPROV hprov;

            public CERT_SELECT_STRUCT() {
                super();
            }

            public WinCrypt.CERT_CONTEXT[] getArrayCertContext() {
                WinCrypt.CERT_CONTEXT[] elements = new WinCrypt.CERT_CONTEXT[cCertContext];
                for (int i = 0; i < elements.length; i++) {
                    elements[i] = (WinCrypt.CERT_CONTEXT) Structure.newInstance(WinCrypt.CERT_CONTEXT.class,
                            arrayCertContext.getPointer(i * Native.POINTER_SIZE));
                    elements[i].read();
                }
                return elements;
            }

            public void setArrayCertContext(WinCrypt.CERT_CONTEXT[] arrayCertContexts) {
                if (arrayCertContexts == null || arrayCertContexts.length == 0) {
                    arrayCertContext = null;
                    cCertContext = 0;
                } else {
                    cCertContext = arrayCertContexts.length;
                    Memory mem = new Memory(Native.POINTER_SIZE * arrayCertContexts.length);
                    for (int i = 0; i < arrayCertContexts.length; i++) {
                        mem.setPointer(i * Native.POINTER_SIZE, arrayCertContexts[i].getPointer());
                    }
                    arrayCertContext = mem;
                }
            }

            public void setArrayCertStore(WinCrypt.HCERTSTORE[] stores) {
                if (stores == null || stores.length == 0) {
                    arrayCertStore = null;
                    cCertStore = 0;
                } else {
                    Memory mem = new Memory(Native.POINTER_SIZE * stores.length);
                    for (int i = 0; i < stores.length; i++) {
                        mem.setPointer(i * Native.POINTER_SIZE, stores[i].getPointer());
                    }
                    cCertStore = stores.length;
                    arrayCertStore = mem;
                }
            }

            public WinCrypt.HCERTSTORE[] getArrayCertStore() {
                if (arrayCertStore == null || cCertStore == 0) {
                    return new WinCrypt.HCERTSTORE[0];
                } else {
                    WinCrypt.HCERTSTORE[] result = new WinCrypt.HCERTSTORE[cCertStore];
                    for (int i = 0; i < result.length; i++) {
                        result[i] = new WinCrypt.HCERTSTORE(arrayCertStore.getPointer(i * Native.POINTER_SIZE));
                    }
                    return result;
                }
            }

            @Override
            protected List<String> getFieldOrder() {
                return fieldOrder;
            }
        }
    }

    public void CertSelect() {
       Cryptdlg cryptdlg = Cryptdlg.INSTANCE;

     ...// parentHwnd and hCertStore are initalized and passed to this method
     Cryptdlg.CERT_SELECT_STRUCT certSel = new Cryptdlg.CERT_SELECT_STRUCT();
     WinCrypt.CERT_CONTEXT[] pContexts = new WinCrypt.CERT_CONTEXT[1];
     certSel.hwndParent = parentHwnd;
     certSel.szTitle = "title";
     certSel.cCertStore = 1;
     certSel.setArrayCertStore(new WinCrypt.HCERTSTORE[] {hCertStore});
     pCertSelectInfo.cCertContext = 1;
     pContexts[0] = new WinCrypt.CERT_CONTEXT.ByReference();
     certSel.setArrayCertContext(pContexts);
     cryptdlg.CertSelectCertificate(certSel); //line 60

    ...
    }
}

当我调用此方法时,我在上面第 60 行的 dll 调用 cryptdlg.CertSelectCertificate(certSel) 处得到“java.lang.Error:无效内存访问”。

java.lang.Error: Invalid memory access
at com.sun.jna.Native.invokeInt(Native Method)
    at com.sun.jna.Function.invoke(Function.java:419)
    at com.sun.jna.Function.invoke(Function.java:354)
    at com.sun.jna.Library$Handler.invoke(Library.java:244)
    at com.sun.proxy.$Proxy13.CertSelect(Unknown Source)
    at com.project.Crypto.CertSelect(Crypto.java:60)
    

我不确定为什么会出现异常。我关注了example mentioned here.

[更新]

为了它的价值, 当我将“setArrayCertStore”的类型从 Pointer 修改为 HCERTSTORE[] 时,我没有收到任何异常,但没有证书被提取。

这让我思考 arrayCertStore 是否正确初始化。

WinCrypt.HCERTSTORE[] cStoreArray = new WinCrypt.HCERTSTORE[1];
pCertSelectInfo.cCertStore = 1;
cStoreArray[0] = hCertStore;
pCertSelectInfo.arrayCertStore = cStoreArray;

并且结构体定义修改如下

public WinCrypt.HCERTSTORE[] arrayCertStore;

HCRYPTPROV定义为

    public static class HCRYPTPROV extends BaseTSD.ULONG_PTR {

      public HCRYPTPROV() {}
      public HCRYPTPROV(long value) {
      super(value);
      }
    }

==================================

[编辑] 经过与丹尼尔等人的讨论。这是有效的更新代码

public class Crypto {
    public interface Cryptdlg extends Library {
        Cryptdlg INSTANCE = (Cryptdlg) Native.loadLibrary("Cryptdlg", Cryptdlg.class, W32APIOptions.DEFAULT_OPTIONS);

        public boolean CertSelectCertificate(CERT_SELECT_STRUCT pCertSelectInfo);

        public static class CERT_SELECT_STRUCT extends Structure {

            private static final List<String> fieldOrder = createFieldsOrder("dwSize", "hwndParent", "hInstance",
                    "pTemplateName", "dwFlags", "szTitle", "cCertStore", "arrayCertStore", "szPurposeOid",
                    "cCertContext", "arrayCertContext", "lCustData", "pfnHook", "pfnFilter", "szHelpFileName",
                    "dwHelpId", "hprov");

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

            public int dwSize;
            public HWND hwndParent;
            public HINSTANCE hInstance;
            public String pTemplateName;
            public int dwFlags;
            public String szTitle;
            public int cCertStore;
            public Pointer arrayCertStore;
            public String szPurposeOid;
            public int cCertContext;
            public Pointer arrayCertContext;
            public WinDef.LPARAM lCustData;
            public Pointer pfnHook = null;
            public Pointer pfnFilter = null;
            public String szHelpFileName;
            public int dwHelpId;
            public HCRYPTPROV hprov;

            public CERT_SELECT_STRUCT() {
                super();
            }

            public WinCrypt.CERT_CONTEXT[] getArrayCertContext() {
                WinCrypt.CERT_CONTEXT[] elements = new WinCrypt.CERT_CONTEXT[cCertContext];
                for (int i = 0; i < elements.length; i++) {
                    elements[i] = (WinCrypt.CERT_CONTEXT) Structure.newInstance(WinCrypt.CERT_CONTEXT.class,
                            arrayCertContext.getPointer(i * Native.POINTER_SIZE));
                    elements[i].read();
                }
                return elements;
            }

            public void setArrayCertContext(WinCrypt.CERT_CONTEXT[] arrayCertContexts) {
                if (arrayCertContexts == null || arrayCertContexts.length == 0) {
                    arrayCertContext = null;
                    cCertContext = 0;
                } else {
                    cCertContext = arrayCertContexts.length;
                    Memory mem = new Memory(Native.POINTER_SIZE * arrayCertContexts.length);
                    for (int i = 0; i < arrayCertContexts.length; i++) {
                        mem.setPointer(i * Native.POINTER_SIZE, arrayCertContexts[i].getPointer());
                    }
                    arrayCertContext = mem;
                }
            }

            public void setArrayCertStore(WinCrypt.HCERTSTORE[] stores) {
                if (stores == null || stores.length == 0) {
                    arrayCertStore = null;
                    cCertStore = 0;
                } else {
                    Memory mem = new Memory(Native.POINTER_SIZE * stores.length);
                    for (int i = 0; i < stores.length; i++) {
                        mem.setPointer(i * Native.POINTER_SIZE, stores[i].getPointer());
                    }
                    cCertStore = stores.length;
                    arrayCertStore = mem;
                }
            }

            public WinCrypt.HCERTSTORE[] getArrayCertStore() {
                if (arrayCertStore == null || cCertStore == 0) {
                    return new WinCrypt.HCERTSTORE[0];
                } else {
                    WinCrypt.HCERTSTORE[] result = new WinCrypt.HCERTSTORE[cCertStore];
                    for (int i = 0; i < result.length; i++) {
                        result[i] = new WinCrypt.HCERTSTORE(arrayCertStore.getPointer(i * Native.POINTER_SIZE));
                    }
                    return result;
                }
            }

            @Override
            public void write() {
                this.dwSize = size();
                super.write();
            }
            
            @Override
            protected List<String> getFieldOrder() {
                return fieldOrder;
            }
        }
    }

    public void CertSelect() {
     Cryptdlg cryptdlg = Cryptdlg.INSTANCE;
     Cryptdlg.CERT_SELECT_STRUCT certSel = new Cryptdlg.CERT_SELECT_STRUCT();
     certSel.hwndParent = parentHwnd;
     certSel.szTitle = "title";
     certSel.cCertStore = 1;
     certSel.setArrayCertStore(new WinCrypt.HCERTSTORE[] {hCertStore});
     pCertSelectInfo.cCertContext = 1;
     pCertSelectInfo.arrayCertContext = new Memory(Native.POINTER_SIZE);
     pCertSelectInfo.arrayCertContext.setPointer(0, Pointer.NULL);
     cryptdlg.CertSelectCertificate(certSel);

    ...
    }
}

有几个潜在的问题区域。

首先,您混合了 ANSI 和 Unicode 版本。

CertSelectCertificate() 函数有两种变体,一种以 A 结尾,另一种以 W 结尾。 -A 函数是 ANSI(8 位字符),而 -W 变体用于 Unicode/Wide-string(16 位字符)。这些方法的主要区别在于 CERT_SELECT_STRUCT 结构中字符串中的字符数。它同样有两个变体,CERT_SELECT_STRUCT_ACERT_SELECT_STRUCT_W

JNA 使用其默认类型映射器自动将您映射到正确的版本(在现代操作系统中几乎所有情况下都是 -W 版本)。您可能应该使用 Win32 库的默认选项在您的库加载中显式添加该类型映射器:

Cryptdlg INSTANCE = (Cryptdlg) Native.loadLibrary("Cryptdlg", Cryptdlg.class,
    W32APIOptions.DEFAULT_OPTIONS);

但是,您在 CERT_SELECT_STRUCT() 构造函数中有一个使用 -A 类型映射器的显式调用:

public CERT_SELECT_STRUCT() {
  super(W32APITypeMapper.ASCII);
}

这会强制它使用结构的 -A 版本,它的字符串中每个字符只有 8 位。你应该打电话给 super().


第二种可能性,虽然从文档中并不明显,但第一个元素 dwSize 应该是结构的大小。你应该把它放在你的构造函数中:

this.dwSize = this.size();

第三种可能性(也是最可能的原因,如果我猜的话)是在您设置 arrayCertContext 字段内容的行中。它记录为:

A pointer to an array of CERT_CONTEXT structures.

您将 Java 端的数组定义为一个结构(它有自己的指针)并手动将其设置到内存中,但是您没有填充 CERT_CONTEXT 结构,而是放置了一个 Pointer 那里:

pContexts[0] = new WinCrypt.CERT_CONTEXT.ByReference();

最后用您刚刚创建的指针地址填充“结构”的前 8 个字节,其中前 4 个字节分配给 dwCertEncodingType,接下来的 4 个字节(加上 4 个 null字节)转到 ByteByReference() 字段的指针值。

此外,结构数组的分配比内存分配更容易:

WinCrypt.CERT_CONTEXT[] pContextArray = 
    (WinCrypt.CERT_CONTEXT[]) new WinCrypt.CERT_CONTEXT().toArray(1);

顺便说一句,您可能会发现 JNA 5.x(当前为 5.6.0)中的结构定义更加精简,其中包括 @FieldOrder 注释作为创建字段顺序列表的首选方法。