C# 4.0 动态对象和 WinAPI 接口,如 IShellItem(无需在 C# 源代码中定义它们)
是否可以(使用 C# 4.0 中的新动态关键字)使用接口(如 IShellItem 或其他 WinAPI 接口),而无需在 C# 源代码中定义它们?或者至少不定义接口成员?
我正在尝试类似的操作:
const string IShellItemGuid = "43826D1E-E718-42EE-BC55-A1E261C37BFE";
Guid guid = new Guid(IShellItemGuid);
dynamic nativeShellItem = null;
// Create and initializes a Shell item object (IShellItem) from a parsing name
SHCreateItemFromParsingName(@"M:\TEST.TXT", IntPtr.Zero, guid, out nativeShellItem);
if (nativeShellItem != null)
{
MessageBox.Show(nativeShellItem.GetDisplayName(0));
}
其中
[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
static extern void SHCreateItemFromParsingName(
[In][MarshalAs(UnmanagedType.LPWStr)] string pszPath,
[In] IntPtr pbc,
[In][MarshalAs(UnmanagedType.LPStruct)] Guid iIdIShellItem,
[Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out dynamic iShellItem);
SHCreateItemFromParsingName 工作正常,如果文件存在,我将得到一个对象(如果文件不存在,我将得到一个正确的错误消息),但是,尝试调用 < em>nativeShellItem.GetDisplayName 给我一个运行时异常:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException 未处理:“System.__ComObject”不包含“GetDisplayName”的定义
尽管 IShellItem 有一个名为 GetDisplayName 的方法,并且我的 SHCreateItemFromParsingName 返回的 >nativeShellItem 动态对象应该实现此接口。
Is it possible (with the new dynamic keyword in C# 4.0) to use interfaces (like IShellItem or other WinAPI interfaces) without defining them in my C# source code? Or at least not define the interface members?
I'm trying something like:
const string IShellItemGuid = "43826D1E-E718-42EE-BC55-A1E261C37BFE";
Guid guid = new Guid(IShellItemGuid);
dynamic nativeShellItem = null;
// Create and initializes a Shell item object (IShellItem) from a parsing name
SHCreateItemFromParsingName(@"M:\TEST.TXT", IntPtr.Zero, guid, out nativeShellItem);
if (nativeShellItem != null)
{
MessageBox.Show(nativeShellItem.GetDisplayName(0));
}
where
[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
static extern void SHCreateItemFromParsingName(
[In][MarshalAs(UnmanagedType.LPWStr)] string pszPath,
[In] IntPtr pbc,
[In][MarshalAs(UnmanagedType.LPStruct)] Guid iIdIShellItem,
[Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out dynamic iShellItem);
The SHCreateItemFromParsingName works fine and I'm getting an object if the file exists (and a correct error message if the file does not exist), however, trying to call nativeShellItem.GetDisplayName gives me a runtime exception:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was unhandled: 'System.__ComObject' does not contain a definition for 'GetDisplayName'
although IShellItem has a method called GetDisplayName, and my nativeShellItem dynamic object returned by SHCreateItemFromParsingName should implement this interface.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
是的,这不起作用,DLR 没有关于 COM 对象的可用类型信息。这是 shell 编程中一个臭名昭著的问题,它使用派生自 IUnknown 的接口,而不是 IDispatch,因此不支持后期绑定。并且没有可用的类型库可以让 .NET 轻松地为接口类型生成互操作库。
IShellItem在ShObjIdl.idl中声明,它由cpp_quote()填充。这会挫败任何使用 midl.exe 生成类型库的尝试,只能创建 .h 文件。顺便说一句,已经提供了 ShObjIdl.h。只有 C++ 编译器可以使用它。
从技术上讲,可以使用 [ComImport]、[Guid] 和 [PreserveSig] 属性在 C# 中重新声明接口。您必须非常小心地这样做,幸运的是 IShellItem 直接从 IUnknown 派生,因此您可以避开多重继承子弹。无济于事的是接口方法使用不会自动封送的本机 C 类型。值得注意的是,GetDisplayName() 采用 LPWSTR,即指向 Unicode 字符串的原始指针。不是 BSTR(自动化兼容字符串类型)。这需要您处理 C# 代码中的不安全指针。最好的办法是声明它 IntPtr,使用 Marshal.AllocCoTaskMem() 分配一块内存,并在使用 Marshal.PtrToStringUni() 调用后自行封送字符串。
哎呀,shell 编程完全就是个皮塔饼。谷歌一下这个,这样你就可以复制/粘贴一些已知有效的东西。如果结果为空,使用 C++/CLI 以便您可以使用 ShObjIdl.h 绝对是更好的方法。祝你好运。
Yes, this cannot work, the DLR has no type info available about the COM object. This is a notorious problem in shell programming, it uses interfaces that are derived from IUnknown, not IDispatch so late binding isn't supported. And there is no type library available for them that lets .NET easily generate an interop library for the interface types.
IShellItem is declared in ShObjIdl.idl, it is filled with cpp_quote(). Which defeats any attempt to use midl.exe to generate a type library, only a .h file can be created. Already provided btw, ShObjIdl.h. Only a C++ compiler can use it.
Re-declaring the interface in C# is technically possible with the [ComImport], [Guid] and [PreserveSig] attributes. You have to do so very carefully, luckily IShellItem derives directly from IUnknown so you can dodge the multiple inheritance bullet. What doesn't help is that the interface method uses native C types that don't marshal automatically. Notably GetDisplayName() takes an LPWSTR, a raw pointer to a Unicode string. Not a BSTR, the automation compatible string type. That requires you to mess with unsafe pointers in your C# code. Best thing to do is to declare it IntPtr, allocate a chunk of memory with Marshal.AllocCoTaskMem() and marshal the string yourself after the call with Marshal.PtrToStringUni().
Yuck, shell programming is a complete pita. Google the heck out of this so you can copy/paste something that's known to work. If that comes up empty, using C++/CLI so you can use ShObjIdl.h is definitely a better way. Good luck with it.