Cool code: eight lessons from COM experience

xiaoxiao2021-03-20  224

Original: Jeff Prosise This article is reproduced from Microsoft China Community: http://www.microsoft.com/china/msdn/library/windev/componentdev/cdwickedtoc.mspx Release Date: 5/20/2004 Update: 5/20/2004 Original source: WICKED CODE: Eight Lessons from the com school of hard knocks (Jeff Prosise) In daily work, I have seen many COM code written by different developers. I am surprised by many creative use of COM, there are some clever code that makes COM may not even think of Microsoft. Similarly, I saw some mistakes once again and again, so I can't help but be lazy. Many of these errors are related to threads and security, completely unprofit, and this is the most lack of two fields in COM documentation. If you don't have a careful plan, they are most likely to have two areas that may be to strengthen it. The "Cool Code" column of this month is different from most of the previous columns. It does not provide a cool code that can be used in your own application. Instead, it will tell the correct way and wrong way to implement COM-based applications. It will tell some lessons from difficult practices, and how to avoid falling into a trap that has made many COM developers. In the following space, you will read the eight programmers, these lessons come from their pain experience. Every story is true, but in order to protect the innocent, the name has been hidden. My purpose is to make you no longer repeat the mistakes of other COM programmers through these real COM stories. They may also help you find a place where there is a potential problem in the code written. No matter how the situation, I think you will get a pleasant reading experience.

Always call Coinitialize (EX) Do not pass the original interface pointer between the threads Remove the message loop unit model object must protect shared data cautious start user DCOM is not suitable for firewalls to use threads or asynchronous calls to avoid DCOM timeout setting too long shared objects It is not easy to contact me to always call Coinitialize (EX) a few months ago, I received a friend's email, he worked in a famous hardware company. His company has written a very complex COM-based application that uses many processes and local (processes) COM components. At the beginning, the application creates a COM object to serve various client threads running in multi-threaded units (MTAs). This object can also hosted to MTA, which means that the interface pointer can be freely exchanged between the client thread. In the test, my friend found that everything is good before the application is ready to close. Then, I don't know why, the call to Release (you must do this, to correctly release the client-side interface pointer) is locked. His question is: "Where is the problem?" Actually the answer is very simple. The application's developers are all right, only one exception, and this is very important: they do not call Coinitialize or CoinitializeEx in all client threads. One of the basic principles of modern COM is that each thread using COM should call Coinitialize or CoinitializeEx first to initialize COM. This principle is unable to avoid. In addition to other things, Coinitialize (EX) should put the thread into the unit and initialize the important per-thread status information (which is required for COM's correct operation). Calling Coinitialize (EX) Failed usually exhibits early COM API functions early in the application life period, the most common is to activate the request. But sometimes the problem is very hidden until everything is too late (for example, it is not returned to Release calls). When the development team adds Coinitialize (EX) to all threads that come into contact with COM, their problems are solved. Ironically, Microsoft is actually one of the COM programmers sometimes not calling Coinitialize (EX). Some documents contained in the Microsoft Knowledge Base that call Coinitialize (EX) is not required for MTA-based threads (for example, see Articles Q150777). Yes, in many cases, we can skip Coinitialize (EX) without problems. However, this should not, unless you know what you are doing, and you can absolutely do you will not be negatively affected. Calling Coinitialize (ex) is no harmful, so I suggest COM programmers always call it from a COM-related thread. Don't pass the original interface pointer between the threads. I consult one of the first COM projects involving a distributed application containing 100,000 lines of code, which is written by a large software company in the US West Coast. The application creates dozens of COM objects on multiple machines and calls these objects from the background thread that started from the client process. The development team encountered a problem, calling either disappeared without a trace, or fail without obvious reasons. The most amazing symptoms they give me: When a call cannot return, launch other applications that support COM on the same machine (including Microsoft Paint, etc.) will cause these applications to be locked.

After checking their code, they violated a basic rule for Com concurrent, that is, if a thread is to share an interface pointer with another thread, it should first package the interface pointer. If necessary, the encapsulated interface pointer allows COM to create a new agent (and a new channel object, nodules the proxy and stubbing) to allow for adjustment from another unit. Do not pass the original interface pointer (one 32-bit address in memory) to another thread, will bypass the COM, and if the transmitted and received thread is in different units, there will be various adverse behavior. (In Windows 2000, since the two objects can share a unit, it is in different contexts, so if the thread is located in the same unit, you may be caught in a dilemma.) Typical symptoms include call failed and returned RPC_E_Wrong_thread_ERROR. Windows NT 4.0 and later You can use a pair of API functions called ComarshalinteRFACEADERFACEINSTREAM and COGETINTERFACEANDRELEASTREAM, and easily block interface pointers between threads. Suppose one of your applications (thread a) created a COM object, which relates to an IFOO interface pointer, and another thread in the same process (thread b) wants to call this object. When preparing to pass the interface pointer to thread B, thread A should be encapsulated, as shown below: ComarshalinterthreadInterfaceInstream (IID_ID_IFOO, PFOO, & PStream); After ComarshalinterthreadinterFaceInstream returns, thread B can safely cancel the interface pointer safely : IFoo * Pfoo;

COGETINTERFACEANDRELEASTREAM (Pstream, IID_IFOO, (Void **) & pfoo);

In these examples, the PFOO is an IFO interface pointer, and PStream is an ISTREAM interface pointer. CoM initializes the IStream interface pointer when calling ComarshalinterthreadInterfaceInstream, then uses and releases the interface pointer inside COGETISTERFACEANDRELEASTREAM. In fact, you usually use an event or other synchronization primitive to coordinate the behavior of these two threads - for example, let thread b know that the interface pointer is ready, can be canceled. Please note that the interface pointer does not have any problems in this way, because COM has sufficient intelligence, which is not to be sealed (or reinfert) pointers when it is not necessary to be sealed. If you do this, use COM, you can easily use COM. If COMARSHALINTERTHREADITERFACEINSTREAM and COGETINTERFACEANDRELEASSTREAM look too much, you can also deliver interface pointers between threads by placing the interface pointer in the global interface table (Git) and let other threads retrieve them there. The interface pointer retrieved from the git will be automatically sealed during being retrieved. For more information, see the documentation in IGLOBALINTERFACETABLE. Note that Git is only in Windows NT 4.0 Service Pack 4 and later. The STA thread requires another deadly defect in the application described in the message loop. See if you can point out. This special application is exactly written in MFC. At first, it used the MFC's AFXBEGINTHREAD function to launch a series of secondary threads. Each auxiliary thread either calls Coinitialize either call AFXoleinit (a function similar to Coinitialize in MFC) to initialize COM. Some auxiliary threads call CocreateInstance to create a COM object and send the returned interface pointer to other auxiliary threads. Calling objects from creating these objects will be very smooth, but the call from other threads is never returned. Do you know why? If you think the problem is related to the message loop (or missing message loop), then the answer is completely correct. The fact is indeed true. When a thread calls Coinitialize or AFXoleinit, it is placed in a single-line unit (STA). When COM creates a STA, it creates an attached hidden window. Method calls for objects in STA will be converted to messages and placed in a message queue associated with the STA. When the thread running in the STA is retrieved to the message that represents the method call, the window process of the hidden window will call the message conversion method. COM uses STA to perform call serialization. Objects in STA cannot receive more than one call once, because each call is passed to one and is the only thread running in the object cell. What if the STA-based thread cannot handle the message? What if it doesn't have a message loop? The cell method call for the object in this STA will no longer return; they will be put on the message queue. There is no message cycle in the MFC assist thread, so if you are hosting objects in these STAs to receive methods from the client of other units, the MFC auxiliary threads and STAs are not good. What is the meaning of this story? The STA thread requires a message loop unless you are sure they don't contain objects to call from other threads. Message loops can be simple: MSG MSG; While (GetMessage (& MSG, 0, 0, 0))

DispatchMessage (& MSG);

Another solution is to move the COM thread into the MTA (or moving to the neutral thread unit, i.e., NTA), where there is no message queue dependencies. The unit model object must protect the shared data. Another common problem that plasmunications COM developers is labeled THREADINGMODEL = APARTMENT. This specification tells COM, the instance of the object must only be created in STA. It also allows COM freely place these object instances in the STA of any host process. Suppose the client application has five STA threads, each thread uses CocreateInstance to create an instance of the same object. If the thread is based on STA, and the object is marked as threadingModel = APARTMENT, the five object instances will be created in the object's STA. Because each object instance is running on the thread of its STA, all five object instances can run in parallel. So far, everything is good. Now consider what happens if these object instances share data. Because objects are executed concurrent threads, two or more objects may attempt to access the same data at the same time. Unless all these accesss are read, it will be disaster. The problem may not appear quickly; they will appear in a closed-related error form, so it is difficult to diagnose and reproduce. This explains the reason why the following is: ThreadingModel = Apartment object should include code synchronously access to shared data unless you can determine that the client's client does not overlap the method of performing access. The problem is that too many COM developers believe that ThreadingModel = Apartment enables them to freely from code to write threads. The fact is not that - at least not exactly. ThreadingModel = Apartment does not mean that the object must be fully threaded. It represents a commitment to the COM, that is, the data shared by two or more object instances (or the data shared by this object and other objects) It is carried out in a way that thread is safe. The task of providing this thread security should be responsible by you, ie object implementors. Sharing data is a variety of sizes, but most of them appear in the form of static variables declared in a static member variable in the C class in a global variable. Even if it is the harmless statement, it will also be in STA: static int ncallcount = 0; nCallCount ;

Because all instances of this object will share a nCallCount instance, write the correct way to write these statements is as follows: static int ncallcount = 0;

InterlockInCrement (& nCallCount);

Note: You can use critical regions, interlock functions, or any way you want, but don't forget to access STA-based object-sharing data to synchronize! Carefully start the user is still a problem, so many COM developers have suffered. Last spring, a company was burst with me, and their developers built a distributed application using COM, where the client process runs on the network workstation connected to the remote server's Singleton object. During the test, they have encountered some very strange behavior. In a test scenario, the client calls to CocreateInstanceex allows them to connect to the Singleton object. In another scene, the same call to COCREATEINSTANCEEX generates multiple object instances and multiple server processes, so that the client cannot connect to the same object instance, thereby actually affecting the application. In these two scenarios, hardware and software are identical. This problem seems to be related to safety. When the COM Service Control Manager (SCM) that is processed remotely, it assigns an identifier to the process when starting a process on another machine. Unless otherwise specified, the identifier it is to start the user's identity. In other words, the identity assigned to the server process is the same as the identity of the client process that starts it. In this case, if the BOB logs in machine A and connects the Singleton object on the machine B using the CoCreateInstanceEx, and Alice is also in the machine C, which will start two different server processes (at least two different WinStation). ), In fact, make the client unable to connect with the Singleton semantic to the shared object instance. The reason why the two test scenarios will produce a large phase of the trunks, whose reasons are in a scene (that can work), all testers use a special account set to test with the same person's identity. In another scene, testers use their ordinary user account login. When two or more client processes have the same identity, they can be successfully connected to the server process configured to assume the launch user identifier. However, if the client has a different identifier, the SCM uses multiple server processes (each unique client ID) to assign the identity of different object instances. Figure 1 After the user account in DCMCNFG finds a problem, it is very simple to solve: Configure the COM server, let it use a specific user account rather than assume that the user's identity is assumed. One way to accomplish this is to run DCOMCNFG (Microsoft's DCOM Configuration Tool) on the server machine and change "Launching User" to "THIS USER" (see Figure 1). If you prefer to make changes (possibly from the installer), add a RunAs value in the COM server entry of the HKEY_CLASSES_ROOT / AppID section of the host registry (see Figure 2). Figure 2 Adding a RunAs Value To the Registry You also need to use LSAStorePrivateData to store the password of the RunAs account as a LSA key, and use LsaAdDaccounts to ensure that the account has "Logon as Batch Job" permissions. (For examples of specific operations, see the DCOMPERM example in Platform SDK. Please pay special attention to the function named SetrunasPassword and SetaccounTrights.

DCOM is not suitable for a Firewall for a common problem with DCOM characteristics and functions is: "Can it work across the Internet?" DCOM can work well with the Internet, as long as it is configured to use TCP or UDP, and initiate it by granting anyone And access, you can configure the server to allow an anonymous method to call. After all, Internet is a huge IP network. But contradictions, if you will change an existing DCOM app (good work in the company's internal network or intranet) to work across the Internet, it is very likely that it is very miserable. What can be caused? Firewall. The relationship between DCOM is born with the firewall is like the relationship between oil and water. One reason is that the SCM communication of the COM uses port 135 and SCM communication on other machines. The firewall limits the ports and protocols it can use, which may reject traffic passed through port 135. But a bigger problem is that in order to avoid application conflicts with a socket, pipelines, and other IPC mechanisms, DCOM does not fix the specific range of ports, which is the opposite, which is selected at runtime. By default, it can use any ports from 1,024 to 65, 535. Allowing a DCOM application to open port 135 and ports 1,024-65,535 for the protocol to use for DCOM. (By default, Windows NT 4.0 is a UDP protocol. Windows 2000 is a TCP protocol.) However, this is much better than removing all firewalls. In this regard, your company's IT personnel may wish. Another safer and more realistic solution is to limit the port range used by DCOM and only open a small range of ports for DCOM traffic. According to the principle of practice, you should assign a port for each server process, export the connection to the remote COM client (not a port of each interface pointer or one port of each object, but a server process). Configuring DCOM to use TCP instead of UDP is a good way, especially when the server performs a callback on its client. The DCOM is used to remotely connect the protocol to be configured via the registry. On Windows 2000 and Windows NT 4.0 Service Pack 4 or later, you can use DCMCNFG to apply these configuration changes. The following is a way to configure DCOM to work through firewall. Figure 3 Select the protocol

On the server (the machine to host the remote object after the firewall), the DCOM is configured to use TCP as its selected protocol, as shown in FIG. On the server, restrict the port range of the DCOM will use. Remember to allocate at least one port for each server process. The example in Figure 4 limits DCOM as ports 8, 192 to 8, 195. Open the port you select in step 2 so that the TCP traffic can pass through the firewall. At the same time, open port 135. Figure 4 Select the port to perform these steps, and DCOM can work well across firewalls. If you prefer, SP4 and later allow you to specify endpoints for separate COM servers. For more information, please read the excellent papers of Michael Nelson About DCOM and firewalls, which can be found on the MSDN Online site (see http://msdn.microsoft.com/library/en-us/dndcom/html/msdn_dcomfirewall. ASP). It should also be noted that by installing Internet Information Services (IIS) on the server and uses COM Internet Services (CIS) to use CIS to provide CIS to provide CIS to provide CIS to provide CIS to provide compatible with firewall DCOM. For more information on this topic, please refer to http://msdn.microsoft.com/library/en-us/dndcom/html/cis.asp. Use threads or asynchronous calls to avoid DCOM timeout setting too long, always ask me how long the timeout setting when DCOM cannot complete the remote instantiation request or method call. Typical scenes are as follows: The client calls CocreateInstanceex to instantiate an object on the remote machine, but this machine is temporarily offline. On Windows NT 4.0, the activation request will not fail immediately, and the DCOM may take a minute or more to return the failure of HRESULT. DCOM may also take a long time to fail to call the way to call the remote object that has no longer existed or its host has offline. If possible, how should developers avoid these longer timeout settings? To answer this question, a few words are unclear. DCOM is highly dependent on the underlying network protocol and the RPC subsystem. There is no magical setting to let you limit the duration of the DCOM timeout setting. However, I often use two techniques to avoid longer overtime settings. In Windows 2000, you can use asynchronous method calls to release the call thread when you are running in the COM channel. (For the introduction of asynchronous methods, please refer to "Windows 2000: Asynchronous Method Calls Eliminate the Wait For Com Clients and Servers Alike" in MSDN Magazine, 2000. If the asynchronous call does not return within a reasonable time, you can use IcancelMethodCalls :: CancelMethodCalls :: Cancel on the invocation of the invocation call. Windows NT 4.0 does not support asynchronous method calls, even in Windows 2000, asynchronous activation requests are not supported. How to solve it? Call the remote object from the background thread (or Instantiate the request of the object). Make the main thread block on the event object and specify the timeout setting value to reflect the length of time you want to wait. When the call is returned, let the background thread set the event.

Suppose the main thread uses WaitForsingleObject blocking. When WaitForsingleObject returns, the return value can tell you, return because method calls or activation requests return, or because you specified in the WaitForsingleObject call, the timeout setting expires in the waitforsingleObject call. You cannot unannounced calls in Windows NT 4.0, but at least the main thread can freely perform your own tasks. The following code demonstrates how Windows NT 4.0-based clients can call objects from the background thread. /// PLACING a Method Call from A Background Thread

/

Handle g_hevent;

ISTREAM * G_PSTREAM;

// Thread A

g_hevent = CreateEvent (NULL, FALSE, FALSE, NULL);

ComarshalinterthreadInterfaceInstream (IID_IFOO, PFOO, & g_pstream);

DWORD DWTHREADID;

CreateThread (NULL, 0, Threadfunc, Null, 0, & DWTHREADID);

DWORD DW = WaitforsingleObject (g_hevent, 5000);

IF (dw == wait_timeout) {

// Call Timed Out

}

Else {

// Call Completed

}

...

// Thread B

IFoo * pfoo;

COGETINTERFACEANDRELEASTREAM (g_pstream, IID_ifoo, (void **) & pfoo);

Pfoo-> bar (); // make the call!

SetEvent (g_hevent);

CloseHandle (g_hevent); In this example, thread a encloses an IFO port pointer and starts thread B. Thread B cancels the interface pointer and calls iFoo :: bar. Regardless of the time spent returned to the call, thread A will not block more than 5 seconds because it passes 5,000 (microseconds) in the second parameter of WaitForsingleObject. This is not a good way, but if "no matter what happens in the other end of the line, thread a will not hang", it is important to endure this trouble. Sharing objects are not easy to judge from the emails I received and issued at the meeting, and a problem that many COM programmers who will play two or more clients is connected to an object instance. To answer this question, it is easy to write a long story (or a booklet), but in fact, as long as it explains the connection with the existing object, it is not easy to automate, it is sufficient. COM provides a large number of ways to create objects, including very popular CoCreateInstance (EX) functions. However, COM lacks a generic naming service that allows the name or GUID to identify the object instance. And it does not provide a built-in way to create an object, then identify it as a call to the interface pointer. Is this impossible to connect multiple clients with a single object instance? of course not. There are five ways to achieve this. In these resource links, you can find more information or even sample code to guide your operation. Please note that these technologies can not be interchangeable in general; usually, environmental factors will decide which method (if any) is applicable to hand: Singleton object Singleton object is only an object that is only instantiated. There may be 10 clients to call CocreateInstance to "create" Singleton objects, but in fact, they are all receiving interface pointers to the same object. The ATL COM class can be converted to Singleton by adding a Declare_ClassFactory_singleton statement in its class. File Name Object If an object has implemented IPERSISTFILE, use the file name object in the Run Object (ROT) (which encapsulates the file name of the IPERSISTFILE :: LOAD method of the object), then the client can Use the file name object to connect an existing instance of the object. In fact, the file name object allows the file name to name the object instance, and the object can store their persistence data in these file names. They can even work across the machine. ComarshalInterface and Counmarshalinterface Save the COM client for the interface pointer to share these interface pointers as long as they are willing to block pointers. COM is willing to provide an optimization of threads to other threads in the same process (see lesson 2), but if the client thread belongs to other processes, Comarshalinterface and CounmarshalinterFace are key ways to implement interface sharing. For discussion and sample code, please refer to MSJ in my Cool code column in March 1999. Custom Class Objects All "Create" COM objects are accompanied by independent objects, called class objects, and their role is an instance of the so-called COM object. Most class objects are implemented in an interface called IClassFactory, including a method of creating an object instance called CreateInstance.

转载请注明原文地址:https://www.9cbs.com/read-130396.html

New Post(0)