调用C++ dll时获取正确参数的C# P/Invoke调用问题

3
尝试从Oracle的Outside In API中进行功能交互。拥有以下函数:
SCCERR EXOpenExport {VTHDOC hDoc, VTDWORD dwOutputId, VTDWORD dwSpecType, 
VTLPVOID pSpec, VTDWORD dwFlags, VTSYSPARAM dwReserved, VTLPVOID pCallbackFunc, 
VTSYSPARAM dwCallbackData, VTLPHEXPORT phExport);

从头文件中,我将参数简化为:
typedef VTSYSPARAM VTHDOC,VTLPHDOC *
typedef DWORD_PTR VTSYSPARAM
typedef unsigned long DWORD_PTR

typedef unsigned long VTDWORD   

typedef VTVOID* VTLPVOID  
#define VTVOID void  

typedef VTHDOC VTHEXPORT, *VTLPEXPORT  
These are for 32 bit windows 

浏览了头文件、示例程序和文档,我发现:
1. pSpec 可以是缓冲区的指针或 NULL,所以我将其设置为 IntPtr.Zero(文档)。
2. 根据文档,dwFlags 和 dwReserved 必须由开发者设置为 0。
3. 如果不想处理回调,pCallbackFunc 可以设置为 NULL。
4. 最后两个参数基于我使用 [StructLayout(LayoutKind.Sequential)] 编写 C# 包装器的结构。然后,我实例化了一个对象,并通过首先使用 Marshal.AllocHGlobal(Marshal.SizeOf(instance)) 创建 IntPtr,然后获取地址值,将其作为 uint 传递给 dwCallbackData,将其作为 IntPtr 传递给 phExport,生成了参数。

最终的参数列表如下: 1. phDoc 作为 IntPtr,由之前调用的 DAOpenDocument 函数加载了一个地址。
2. dwOutputId 作为 uint 设置为 1535,表示 FI_JPEGFIF。
3. dwSpecType 作为 int 设置为 2,表示 IOTYPE_ANSIPATH。
4. pSpec 作为 IntPtr.Zero,输出将被写入其中。
5. dwFlags 作为 uint 设置为 0,按照指示。
6. dwReserved 作为 uint 设置为 0,按照指示。
7. pCallbackFunc 作为 IntPtr 设置为 NULL,因为我会处理结果。
8. dwCallBackDate 作为 uint,是一个结构体缓冲区的地址。
9. phExport 作为 IntPtr,指向另一个结构体缓冲区。

仍然从 API 中得到未定义错误。这意味着调用返回了一个在任何头文件中都没有定义的 961。过去,当我的参数类型选择不正确时,我也遇到过这种情况。
我开始使用 Interop Assistant,它有助于学习许多参数类型的转换方式。但是,它受限于我能否从头文件中正确获取本机类型的程度。例如,前面函数中使用的 hDoc 参数被定义为非文件系统句柄,因此尝试使用 Marshal 创建句柄,然后使用 IntPtr,最后它被证明是 int(实际上是 &phDoc 在此处使用)。
那么除了试错之外,还有更科学的方法吗?
Jim


你尝试过http://pinvoke.net吗? - Jeff Yates
961在十六进制中是3C1。在sccerr.h中被定义为“访问冲突”。 - Dan Blair
2个回答

1
你的定义:
typedef unsigned long DWORD_PTR;

是不正确的 — DWORD_PTR 被定义为:

typedef ULONG_PTR DWORD_PTR;

转而被定义为:
typedef unsigned __int3264 ULONG_PTR;

属性__int3264这里有文档记录,我将重点复述如下:

关键字__int3264指定了一个具有以下特性的整数类型:

  • 在32位平台上为32位
  • 在64位平台上为64位
  • 在网络上为了向后兼容性而被截断为32位。在发送端被截断,在接收端适当地扩展(有符号或无符号)。

当你在进行C语言的“类型”封送(如DWORD_PTR),其大小取决于平台指针大小,这通常是操作系统参数(如LPARAMWPARAMLRESULTSIZE_T等)所需要的情况,而名称VTSYSPARAM似乎也在暗示这一点,你应该使用IntPtr而不是uint

基本上:

应该是这样的:
  1. dwReserved 作为 IntPtr,根据指示设置为 IntPtr.Zero
  1. dwCallBackData 作为 IntPtr,用于存放结构体的缓冲区地址

然后它应该可以工作了,如果还不行,你需要确保在编组必要的结构体时没有犯类似的错误。


为什么你要回答一个13年前的问题?这个问题很可能已经过时了。 - undefined
因为被标记为正确答案的回答并不正确,而且实际上并没有回答问题?更不用说平台指针长度的概念对于理解封送是很重要的了。 - undefined
1
OP早就过去了(甚至可能换了1-3个雇主)。现在人们可能会使用Outside-in .NET API。至于具体指定类型:如果您使用源代码生成的C#/Win32,甚至可以为您生成特定于平台的类型LPARAM、WPARAM、LRESULT等。 - undefined
1
@JHBonarius我没有回答这个问题,是因为提问者可能在此期间也已经去世了 - 我回答是因为被选中的答案根本不是答案,不应该被接受。你建议使用自动化并在项目中注入另一个依赖(还处于测试阶段),而不是学习如何正确地进行数据转换,这甚至更糟糕。 - undefined

-1

是的,我已经使用过工具包,但这仍然是一个猜测游戏。 - Jim Jones
@Jim:我完全同意。要正确地获取这些签名确实很困难。我经常采用在C++/CLI中编写一些包装器,然后将其暴露给C#的方法。 - Jeff Yates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接