Tlbimp生成COM互操作dll时unsigned char*转byte,C#侧如何正确取值?
问题原因
根源是原始COM的IDL定义不规范:
GetAttrValue的pReturnValue参数是调用方预先分配的缓冲区指针,但IDL中没有添加[size_is]标记说明缓冲区长度,Tlbimp工具默认将unsigned char*类型的retval识别为单个字节值,因此生成了错误的方法签名。无需修改COM服务端,仅调整C#侧代码即可解决问题。
具体解决方案
建议手动重定义COM接口,替换自动生成的互操作程序集,步骤如下:
- 移除C#项目中已引用的
Interop.ComService.dll - 用OleView工具打开原COM服务dll,查询目标COM接口的IID、COM类的CLSID,以及接口内所有方法的定义顺序
- 在C#项目中手动导入COM接口,代码示例如下:
using System.Runtime.InteropServices; using System.Text; // 替换为实际查询到的COM接口GUID [ComImport, Guid("替换为你的接口IID"), InterfaceType(ComInterfaceType.InterfaceIsDual)] public interface IComService { // 按IDL定义的顺序声明接口内的所有其他方法,顺序不能出错 // ...... // 重定义GetAttrValue方法,两种写法可选: // 写法1:直接传入预分配的字节数组,适合固定缓冲区长度的场景 void GetAttrValue([MarshalAs(UnmanagedType.BStr)] string serviceName, [Out, MarshalAs(UnmanagedType.LPArray, SizeConst = 100)] byte[] returnValue); // 写法2:用IntPtr传缓冲区,可同时获取HRESULT返回值,灵活性更高 // [PreserveSig] // int GetAttrValue([MarshalAs(UnmanagedType.BStr)] string serviceName, IntPtr returnValue); } // 替换为实际查询到的COM类GUID [ComImport, Guid("替换为你的COM类CLSID")] public class ComServiceInstance { }
- 调用方法示例:
// 写法1调用示例 var service = new ComServiceInstance() as IComService; byte[] buffer = new byte[100]; // 和原MFC端的缓冲区大小保持一致 service.GetAttrValue("目标属性名", buffer); // 如果返回的是字符串,直接转码即可 string result = Encoding.ASCII.GetString(buffer).TrimEnd('\0'); /* 写法2调用示例 var service = new ComServiceInstance() as IComService; IntPtr bufferPtr = Marshal.AllocHGlobal(100); try { int hr = service.GetAttrValue("目标属性名", bufferPtr); if (hr >= 0) // HRESULT成功判断 { byte[] buffer = new byte[100]; Marshal.Copy(bufferPtr, buffer, 0, 100); string result = Encoding.ASCII.GetString(buffer).TrimEnd('\0'); } } finally { Marshal.FreeHGlobal(bufferPtr); } */
注意事项
- 手动声明的接口方法顺序必须和IDL中定义的顺序完全一致,否则会出现调用错位
- 缓冲区大小需要和原MFC端使用的大小保持一致,避免内存越界
内容的提问来源于stack exchange,提问作者Kerwen
相关产品推荐
相关产品推荐

