调用有状态非托管 C++来自 ASP.NET Web 服务的类
我继承了一个已完成一半的应用程序,该应用程序似乎使用了一个我不确定能否可靠工作的模型。
它是一个 ASP.NET Web 服务,每次调用时都会加载一个非托管 C++ .DLL
[DllImport ( "kernel32.dll" , EntryPoint = "LoadLibraryA" )]
public static extern int LoadLibrary( string lpLibFileName );
,然后对其进行多次调用,例如,
[DllImport(@"MyUnamanagedDLL.dll")]
public static extern string DoStuff( );
在非托管 C++ .dll 中,它使用单例来保存调用之间的状态。这样一来,它只需初始化一次并从磁盘和数据库加载一堆缓慢的内容,而不是在每个 Web 服务调用上加载,因为这太慢了。
因此,每次对非托管 .dll 的调用首先调用 Getinstance(),如果实例为 null,则它会初始化它并重新加载所有内容,如果不是,则假定已准备就绪。
FreeLibrary 不会每次在 Web 服务中被调用,因为我认为这会导致非托管类每次都必须重新初始化。
这个模型到底可靠吗?你能确保如果web服务关闭,非托管状态会被正确清理吗?您能否确保在 loadlibrary 调用之间可靠地获得有效的单例实例?
I've inherited a half completed application that seems to use a model that I'm not sure can reliably work.
It is a ASP.NET webservice that on each call loads a unmanaged C++ .DLL using
[DllImport ( "kernel32.dll" , EntryPoint = "LoadLibraryA" )]
public static extern int LoadLibrary( string lpLibFileName );
and then makes a number of calls to it e.g.
[DllImport(@"MyUnamanagedDLL.dll")]
public static extern string DoStuff( );
In the unmanaged C++ .dll it is using a singleton to hold state between calls. This is so that it only has to initialise once and load a bunch of slow stuff from disk and database rather than on each webservice call as this is too slow.
So each call into the unmanaged .dll first calls Getinstance() if the instance is null it initialises it and reloads everything, if not it assumes its ready to go.
FreeLibrary is not being called each time in the webservice as I assume this would cause the unmanaged class to have to be re-initialised each time.
Is this model at all reliable? Can you ensure that if the webservice is shut down that the unmanaged state is cleaned up properly? Can you ensure you will relaible get a valid singleton instance between loadlibrary calls?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
答案取决于几件事;首先也是最重要的:什么样的状态?如果您正在研究内核从根本上负责的事情——内存、文件句柄、HWND——那么您可以期望内核在卸载库时进行清理,无论何时(“在黄金链接器中,我经常故意省略析构函数,因为许多数据结构都是为程序的生命周期;在这种情况下,析构函数只会减慢程序退出速度”;是的,我知道黄金链接器不能在 Windows 上运行,但原则仍然适用)。
如果您谈论的是内核无法保证的内容,那么我建议提供一个 DllMain 函数来处理 PROCESS_DETACH 消息,以处理任何所需的卸载。
简单的情况是:
更困难的情况将涉及创建或清理时的竞争条件:
这些是经典的竞争条件,解决方案是确保检查/创建步骤(和检查/清理)是原子的。不要花哨。使用原子引用计数或
互斥体
es对于记录;我不喜欢这种架构(图书馆里的单身人士)。相反,我会推荐一个将相关状态存储在对象中的库。库 API 可以是类似 C 的(导出函数、CreateXxxObject()/DestroyXxxObject() 函数,它们返回指向不透明
struct
的指针,类似于 APR),或类似 C++。总有一天,单例模型无法解决问题。然而,我只是回答所提出的问题,而不是说“首先,放弃你的计划,......”
The answer depends on a few things; first and foremost: what kind of state? If you're looking at the kind of things that the kernel is fundamentally responsible for -- memory, file handles, HWNDs -- then you can expect the kernel to clean up when the library is unloaded, whenever that is ("[I]n the gold linker I often deliberately omitted destructors, because many of the data structures live for the life the program; in such a case, destructors serve only to slow down program exit"; yes, I know the gold linker doesn't run on Windows, but the principle still applies).
If you're talking about something that's not guaranteed by the kernel, then I would recommend providing a
DllMain
function that handles thePROCESS_DETACH
message to handle any needed unloading.The easy cases are:
The more difficult case would involve either race conditions on creation or cleanup:
These are classic race conditions, and the solution is to make sure the check/create step (and check/cleanup) are atomic. Don't get fancy. Use atomic reference counting or
Mutex
esFor the record; I'm not a fan of this kind of architecture (singletons in the library). I would instead recommend a library where the state in question is stored in objects. The library API can be C-like (exported functions, CreateXxxObject()/DestroyXxxObject() functions that return pointers to opaque
struct
s, similar to the APR), or C++-like. There will come a time when the singleton model doesn't cut it.However, I'm only answering the question asked, not saying "first, throw out your plans, ..."
我不明白为什么你需要通过非托管 dll 加载东西。如果您不知道它的作用以及它是如何做到的,我理解,但是您总是不得不怀疑非托管 dll 内的对象是否具有析构函数和正确的内存管理。
如果您确实知道 dll 实现的功能,并且用 C# 编写它并不需要花费太多精力,那么您可以使用 ASP.NET 的会话状态在每个会话的调用之间存储数据。
在 .NET 2.0 中,global.asax 默认情况下不会添加到 Web 服务应用程序中,但这并不妨碍您手动添加它。毕竟,Web 服务应用程序仍然是一个 Web 应用程序...如果没有 global.asax,每个服务调用都会在服务调用期间产生一个唯一的 sessionid,添加它将使您能够在其间保留内容。
在 global.asax 的 Session_Start 方法中,您可以添加执行慢速数据检索的代码,等等(相当于非托管 dll...)。您还可以将该数据存储在会话本身上,或者在 global.asax 中创建一个静态变量来保存数据(就像非托管 dll 中的单例一样)...
I don't see why you need to load stuff via unmanaged dlls. If you don't know what it does and how it does it I understand, however you always have to doubt if the objects inside the unmanaged dlls have destructors and proper memory management.
If you do know what the dll achieves and it is not too much effort to write it in C# you can use ASP.NET's session state to store data in between calls for each session.
In .NET 2.0 the global.asax is not added by default to web service applictions, however that doesn't prevent you from adding it manually. After all a web service application is still a web application... Without the global.asax each service call will result in a unique sessionid for the duration of the service call, adding it will allow you to persist stuff in between.
In the Session_Start method of the global.asax you can add the code that does the slow data retrieval, and so forth (the equivalent of the unmanaged dll...). You can also store that data either on the session itself or create a static variables in the global.asax that will hold the data (just like the singleton in the unmanaged dll)...
几年前,我在使用无法升级的遗留非托管 DLL 时遇到了类似的问题。问题是在 Web 服务器内使用 P/Invoke 不想玩(我从来没有完全弄清楚为什么;我最好的猜测是这是一个安全问题)。我必须做的是将 DLL 的调用包装在 Windows 服务中,然后使用 WCF 远程调用来伪造互操作性。不幸的是,我无法再访问该代码,否则我会为您提供更好的示例。我希望这能为您指明正确的方向。
I faced a similar problem a few years back working with a legacy unmanaged DLL which could not be upgraded. The issue is that using P/Invoke inside the web server doesn't want play, (I never quite figured out why; my best guess was that it was a security issue). What I had to do was wrap the calls to the DLL in a Windows Service and then use a WCF remoting call to fake the interoperability. Unfortunately, I no longer have access to that code or I would offer you a better sample. I hope this can point you in the right direction.
我最近开发了一个非常类似的项目。就我而言,有几种可用的体系结构,一种使用 COM+,另一种使用 p/Invoke。我选择 p/Invoke 方法是因为简单。很多人直接告诉我这是一个错误的方法,包括微软!最终,我能够使用 p/Invoke 架构,而不会遇到很多麻烦,但实现这一点并不容易。我被迫解决托管和非托管数据结构之间的差异、传递堆与堆栈、内存分配、覆盖缓冲区/堆栈问题、ASP.Net 安全问题,以及最重要的是托管和非托管代码之间的体系结构差异。最终,脑痛让我成为了一个更好的程序员。
回答是否可以使用 p/Invoke 从托管代码中重入调用非托管跨语言 DLL 的问题?是的,可以。但;它必须被设计为以可重入的方式维持其状态。这意味着非托管 DLL 必须跟踪它分配的内存,并在每次从 Web 服务调用它时释放该内存。它不得释放或分配属于另一个 Web 服务调用的内存。
I recently developed a very similar project. In my case there were several architectures available to me, one using COM+ and the other using p/Invoke. I chose the p/Invoke method because due to simplicity. I had many people tell me outright that this was an incorrect approach, including Microsoft! In the end, I was able to use p/Invoke architecture without a lot of headache, but getting there was not easy. I was forced to address the differences between managed and unmanaged data structures, passing on the heap versus the stack, memory allocation, overwriting buffer/stack issues, ASP.Net security issues, and above all, the architectural differences between managed and unmanaged code. In the end the brain ache left me a much better programmer.
In answer to the question of can an unmanaged, cross language DLL be called re-entrantly from managed code using p/Invoke? Yes it can. BUT; it must be architected to maintain its state in a reentrant manner. This means that the unmanaged DLL must keep track of the memory it allocates, and release that memory each time it’s called from your web service. It must not release or allocate memory that belongs to another web service call.