将结构从 C# 传递给非托管代码
我已经和这个问题作斗争有一段时间了,希望有人能提供帮助。我正在将 VB6 应用程序转换为 C#。我将要展示的所有内容在 VB6 中都可以完美运行,但在 C# 中却失败了。
我正在对 C dll 进行 API 调用,并传递一个结构。该结构在 C 端修改了值,我应该在返回结构中看到修改后的值。
注意,Sig 是一个 int,Status 是一个 int,CLIENTID 是一个 UINT_PTR。
以下是 C 结构:
typedef struct
{
short vers;
short flags;
short cmd;
short objtype;
DWORD id;
DWORD client;
char buf[MAX_TOOLBUF];
DWORD toolID;
NMSG msg;
DWORD caller;
CLIENTID clientID;
DWORD ticket;
ToolResult PTR result;
long spare[4];
} Request;
typedef struct
{
DWORD hwnd;
DWORD message;
DWORD wParam;
DWORD lParam;
} NMSG;
typedef struct
{
Sig sig;
short cnt;
Status error;
DWORD ticket;
DWORD toolID;
long spare[4];
} ToolResult;
在我的 C# 代码中,我定义了以下结构来映射到上面的 C 结构:
[StructLayout(LayoutKind.Sequential)]
public struct NMSG
{
public int hWnd;
public int msg;
public int wParam;
public int lParam;
}
[StructLayout(LayoutKind.Sequential)]
public struct Request
{
public Int16 vers;
public Int16 flags;
public Int16 cmd;
public int16 objType;
public int id;
public int Client;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string buf;
public int toolID;
public NMSG msg;
public int caller;
public IntPtr clientID;
public int ticket;
public ToolResult result;
[MarshalAs(UnmanagedType.ByValArray, SizeConst= 4) ]
public int64[] spare;
}
这是 C 代码中的方法修饰:
SendRequest(Request PTR req)
{
....
}
这是我在 C# 中的 PInvoke 声明,用于使用这些 C 中的 API结构:
[DllImport("TheCDLL.dll", EntryPoint = "_Request@4", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void Request(ref Request req);
这是我的 C# 代码,用于填充结构并进行 API 调用:
Request req = new Request();
req.vers = 1;
req.toolID = 4000;
req.Client = 0;
req.cmd = 11;
req.objType = 1;;
req.id = 1;
req.msg.hWnd = Hwnd; // verified this is a valid window handle
req.msg.msg = 1327;
req.msg.wParam = 101;
req.msg.lParam = 0;
req.spare = new int[4];
req.buf = new string(' ', 260);
req.flags = 11;
Request(ref req);
问题是,在 VB6 代码中,在进行 API 调用后,结构项“结果”会填充一个值。在 C# 中,“结果”始终为 0。我猜我的编组方式一定有问题?我读过几十篇文章,并尝试了很多不同的方法来使其发挥作用,但没有成功。任何帮助表示赞赏!
更新:现在我已经根据下面的建议更新了代码(修复类型并清理最初编写的代码),我遇到了一个异常:
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
我发现一旦我更改为“public”,就会发生这种情况我的请求结构中的“public int vers”中的“Int16 vers”。想法?
I've been fighting this problem for some time now and am hoping someone can help. I'm converting a VB6 application to C#. Everything I'm about to show works perfect in VB6, but fails in C#.
I'm making an API call into a C dll, passing a structure. The structure gets values modified in the C side and I should see the modified values in my return structure.
Note, Sig is an int, Status is an int, and CLIENTID is a UINT_PTR.
Here are the C structures:
typedef struct
{
short vers;
short flags;
short cmd;
short objtype;
DWORD id;
DWORD client;
char buf[MAX_TOOLBUF];
DWORD toolID;
NMSG msg;
DWORD caller;
CLIENTID clientID;
DWORD ticket;
ToolResult PTR result;
long spare[4];
} Request;
typedef struct
{
DWORD hwnd;
DWORD message;
DWORD wParam;
DWORD lParam;
} NMSG;
typedef struct
{
Sig sig;
short cnt;
Status error;
DWORD ticket;
DWORD toolID;
long spare[4];
} ToolResult;
In my C# code, I have the following structs defined to map to the C structs above:
[StructLayout(LayoutKind.Sequential)]
public struct NMSG
{
public int hWnd;
public int msg;
public int wParam;
public int lParam;
}
[StructLayout(LayoutKind.Sequential)]
public struct Request
{
public Int16 vers;
public Int16 flags;
public Int16 cmd;
public int16 objType;
public int id;
public int Client;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string buf;
public int toolID;
public NMSG msg;
public int caller;
public IntPtr clientID;
public int ticket;
public ToolResult result;
[MarshalAs(UnmanagedType.ByValArray, SizeConst= 4) ]
public int64[] spare;
}
This is the method decoration in the C code:
SendRequest(Request PTR req)
{
....
}
Here is my PInvoke declaration in C# for the API in C that uses these structures:
[DllImport("TheCDLL.dll", EntryPoint = "_Request@4", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void Request(ref Request req);
And here is my C# code that fills the structure and makes the API call:
Request req = new Request();
req.vers = 1;
req.toolID = 4000;
req.Client = 0;
req.cmd = 11;
req.objType = 1;;
req.id = 1;
req.msg.hWnd = Hwnd; // verified this is a valid window handle
req.msg.msg = 1327;
req.msg.wParam = 101;
req.msg.lParam = 0;
req.spare = new int[4];
req.buf = new string(' ', 260);
req.flags = 11;
Request(ref req);
The problem is, in the VB6 code the structure item 'result' gets filled with a value after I make the API call. In C#, 'result' is always just 0. I'm guessing I must have something wrong with the way I'm marshaling? I've read dozens of articles and have tried lots of different ways to get this working with no success. Any help is appreciated!
UPDATE: Now that I've updated the code based on the suggestions below (fixing types and cleaning up the code as originally written), I'm getting an exception:
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I've found that this happens as soon as I changed to 'public Int16 vers' from 'public int vers' in my Request structure. Thoughts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
UnmanagedType.Struct 实际上不适用于子结构;它适用于子结构。它使封送拆收器将相关对象作为 VARIANT 传递。这不是你想要的。尝试去掉除字符串(长度)、数组(长度)和枚举类型之外的所有 MarshalAs 语句(我在这里没有看到任何内容,但在我的 DLL 中它们是 UnmanagementType.I4),然后查看如果有效的话。
C# 结构中还有一些 C 版本中没有的额外内容,两个变量之间不匹配,并且给定的 C 函数不返回任何内容,但 C# extern 需要 IntPtr。这些确实需要匹配才能使一切顺利进行。 (我猜这只是网站的简化,但需要记住这一点。)
UnmanagedType.Struct actually isn't for substructures; it makes the Marshaler pass the related object as a VARIANT. That isn't what you want here. Try taking off all the MarshalAs statements except for strings (for the length), arrays (also for the length), and enumerated types (I don't see any here, but in my DLLs they've been UnmanagedType.I4) and see if that works.
There's also some extra things in your C# struct that aren't in your C version, a couple variables don't match between the two, and your given C function doesn't return anything but your C# extern is expecting an IntPtr. These do need to match for everything to go smoothly. (I'm guessing it's just simplification for the site, but it is something to keep in mind.)