【问题标题】:How do I properly map CERT_SELECT_STRUCT in JNA如何在 JNA 中正确映射 CERT_SELECT_STRUCT
【发布时间】:2020-08-18 18:21:49
【问题描述】:

我在我的 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: Invalid memory access”。

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);

    ...
    }
}

【问题讨论】:

  • 在您的更新中,您将setArrayCertStore 的类型(在结构中)从Pointer 修改为HCERTSTORE[],但这是不正确的。 java数组指针与本机指针不同。您需要在结构中保留Pointer 定义。只需更新setArrayCertStore() 方法。对数组进行赋值,然后从数组的0 元素返回getPointer(),例如pCertSelectInfo.arrayCertStore = cStoreArray[0].getPointer();
  • 讨论 JNA list 和 stackoverflow 很烦人。尤其是邮件的回复非常很短。

标签: java winapi structure jna


【解决方案1】:

有一些潜在的问题领域。

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

CertSelectCertificate() 函数有两种变体,一种以A 结尾,一种以W 结尾。 -A 函数是 ANSI(8 位字符),而 -W 变体用于 Unicode/宽字符串(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 字段的内容时,第三种可能性(也是最可能的原因,如果我猜的话)在行中。它被记录为:

指向CERT_CONTEXT 结构数组的指针。

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

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

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

另外,比分配内存更容易,结构数组的分配可以这样完成:

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

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

【讨论】:

  • 谢谢,Daniel 我尝试删除具有 ASCII 的构造函数,但仍然收到相同的错误“无效的内存访问”,我不确定我是否在 CertSelect() 方法中正确映射了结构值。
  • 刚刚更新了我的答案,增加了一些可能性。您在结构中的映射很好,但您设置某些值的方法需要进行一些更改。
  • 我遵循了建议,但这些对我不起作用。我已经更新了这个问题,我也找到了。
猜你喜欢
  • 1970-01-01
  • 2015-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-14
  • 1970-01-01
  • 1970-01-01
  • 2020-11-09
相关资源
最近更新 更多