⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ipc.htm

📁 对于学习很有帮助
💻 HTM
📖 第 1 页 / 共 3 页
字号:
    <li>Mailslots</li>
    <li>Pipes</li>
    <li>RPC</li>
    <li>Windows Sockets</li>
    <li>WM_COPYDATA</li>
  </ul>
</blockquote>

<h3>剪贴簿(Clipboard)</h3>

<blockquote>
  <p>人,其实是最佳的 IPC 机制,十分的聪明也十分的有弹性。</p>
  <p>剪贴簿几乎是专为人类而设的标准资料交换中心。它最大的特色除了使用者导向之外,任何应用程式都允许改写其内容,同时它是可以跨越机器边界,交换的范围不仅限於单机内的各个行程。</p>
  <p>由於它是纯使用者导向,使用剪贴簿的程式有一项传统是值得遵守的:如果不是基於使用者的操作,程式不应该主动去异动剪贴簿的内容;同样的道理,我们也不应该假设剪贴簿中有我们程式想要的资料,哪怕是不久前才刚放进去的,因为,使用者可能已经清除或改变其内容了。</p>
  <p>剪贴簿几乎可以容纳任何的资料,除了标准支援的CF_TEXT、CF_BITMAP...等资料格式,我们可以自行注册登记其他格式的资料。但由於它的使用者导向,也由於任何程式都可以改写其内容,除非使用者愿意,不然坦白来说不太适合行程间的资料交换。这也使得应用设计IPC时,剪贴簿成为每支应用程式都标准支援但却也都适可而止的IPC机制。我们应该再多看看其他的资料交换方法。</p>
</blockquote>

<h3>File Mapping</h3>

<blockquote>
  <p>在早期MS-DOS时代还没有现在这麽多 IPC机制可供利用时,使用磁碟档案来交换资料可说是一般应用程式的唯一选择。时至今日,档案不仅没有从IPC领域中消失,反而是更加发扬光大了,然而观念上早已不纯粹界定在档案系统的实体档案。的确,资料位於何处的份际如今是越来越模糊了,虚拟的记忆体实际上是档案,虚拟的档案结果是记忆体。</p>
  <p>Win32 API 中有一个好玩的东西叫做File-mapping;基本的观念是开启一个档案并将之对映到某一块记忆体,有趣的是,虽然程式是针对这块记忆体操作,实际上改变的却是档案。<br>
  更好玩的是你不必真的在硬碟 
  开一个实体档案,而是使用分页置换档(paging file)的一块空间权充当作档案。这个虚拟的档案空间(或者你要说是记忆体)可以为行程间共享,通常我们管它一个特别的名字叫 
  Share-memory,共享记忆体。</p>
  <p>由於它的确不是真正的档案,行程间不仅省去特定磁碟目录档案等约定,也毋须在意谁是最後走的要负责删除档案,当然啦,即使当机不会留下一些垃圾档案。彼此分享的是正好是同一块记忆体,资料一旦写入,这项改变也立即反应到别的行程。</p>
  <p>使用ShareMemory的大致步骤如下所述 <ul>
    <li>呼叫CreateFileMapping() API函数建立File-mapping核心物件.</li>
  </ul>
  <blockquote>
    <p>CreateFileMapping()函数的第一个引数原本应该是CreateFile()开档所得的档案物件Handle,若是传入$FFFFFFFF则是以分页置换档(paging 
    file)的一部划作共享记忆体。函数的最後一个引数是这块区域的叁考名称,行程间彼此将根据此一相同的识别名称叁考同一块共享记忆体。</p>
    <p>FHandle := CreateFileMapping(<br>
    &nbsp;&nbsp;&nbsp; $FFFFFFFF, // Shared memory File,Handle 传入 $FFFFFFFF<br>
    &nbsp;&nbsp;&nbsp; nil, // 不设安全属性<br>
    &nbsp;&nbsp;&nbsp; PAGE_READWRITE, // 
    存取模式设定为可读写以便行程交换资料<br>
    &nbsp;&nbsp;&nbsp; 0, // 使用 paging file 时一般将之设为零<br>
    &nbsp;&nbsp;&nbsp; Size, // 共享记忆体的大小<br>
    &nbsp;&nbsp;&nbsp; pchar(name)); // 
    其他的行程将以此名称叁考到这块共享记忆体</p>
  </blockquote>
  <ul>
    <li>由於各个行程各有其逻辑定址空间,在正式存取这块共享记忆体之前,我们得将其全部或部分映射回行程本身的位址空间中。呼叫MapViewOfFile()的用意即是在此,该函数将传回mapped 
      view 「视野」的起头(就是指标啦),接下来的就是用这个指标存取记忆体了。</li>
  </ul>
  <blockquote>
    <p>FFileView := MapViewOfFile(<br>
    &nbsp;&nbsp;&nbsp; Fhandle, // File-mapping object 的 Handle 值<br>
    &nbsp;&nbsp;&nbsp; FILE_MAP_ALL_ACCESS, // 设为 FILE_MAP_ALL_ACCESS 开放存取<br>
    &nbsp;&nbsp;&nbsp; 0, // 模式以便顺利存取共享记忆体<br>
    &nbsp;&nbsp;&nbsp; 0,<br>
    &nbsp;&nbsp;&nbsp; Size); // 预备映射回来的 byte 数</p>
  </blockquote>
  <ul>
    <li>最後,别忘了使用UnmapViewOfFile()归还指标并呼叫CloseHandle()释放File-mapping核心物件。</li>
  </ul>
  <p>碍於篇辐,完整的程式码请读者叁阅ShareMem目录的 DemoSMem专案。另外,为了方便使用,这些CreateFileMapping(),MapViewOfFile()等函数已经包装进TSharedMem这个类别。</p>
</blockquote>

<h3>Mutex</h3>

<blockquote>
  <p>Shared memory的示范专案DemoSMem留下诸多悬疑待解,或许你也正有相同的疑问:既然两个行程都利用这块记忆体,那我们怎麽知道什麽时候资料改变了?此外,如何防止行程同时读写资料?</p>
  <p>的确,行程通讯既是两个以上的个体,协调是必然存在的负担,要避免两个行程同时使用关键资源,Mutex(互斥器)的使用是你必备的技术。</p>
  <p>从字面上解释,互斥意思是同一时间唯一;换句话说,同一时间最多只许握有Mutex的执行绪(Thread)有权使用关键资源,其他的执行绪若要使用只有等待。嗯! 
  在Mutex与Event这两节我将暂时改口为执行绪,事实上这才是真正的CPU排程单位,由於每个行程至少有个Thread(主执行绪),这样的称呼应该是与本文行程通讯的主旨不相违背的。</p>
  <p>就像是注册讯息,共享记忆体一样(甚至稍後的Event,MailSlot,Pipe都是),在我们取得核心物件的Handle前,都是以「名称」叁考的,产生一个Mutex的API函数是:CreateMutex(), 
  以下范例采自本文所附的ChienIPC程式单元 </p>
  <p>constructor TMutex.Create(const name: string);<br>
  begin<br>
  &nbsp;&nbsp;&nbsp; FHandle := CreateMutex(<br>
  &nbsp;&nbsp;&nbsp; Nil, // 安全防护属性, 暂时传入nil采用预设值<br>
  &nbsp;&nbsp;&nbsp; False, // 执行绪是否一开始就握有 mutex 的所有权<br>
  &nbsp;&nbsp;&nbsp; pchar(name)); // Mutex核心物件的名称<br>
  &nbsp;&nbsp;&nbsp; if FHandle = 0 then Abort;<br>
  end;</p>
  <p>好极了,现在我们有了一个Mutex,该怎麽使用呢? 
  我用一个情节来说明:如果一群人在一起开会,每个人桌子前面各摆着一支麦克风,为了让大家听清楚彼此说什麽,这些麦克风暂时都是关的,规定只有主席可以透过中央控制系统开启回路。要说话的得先举手表示:「我要我要」,如果没有别人举手也没人正在发言,主席便打开开关将发言权交给他,然後这个人的手放下开始讲话。此时若是其他人也要讲话,根据规则得先举手,在别人讲完交出发言权前只有继续举手等待的份。当然,排队的人,可以选择手一直举着;或者他只打算等三分钟,手酸了就放下来。</p>
  <p>执行绪要求拥有Mutex的方法是呼叫WaitForSingleObject()(我要我要,举手等待),此时程式将暂停(Blocking)在这列。倘若此时正好没有别的执行绪拥有Mutex 
  (没人讲话),系统会短暂的将Mutex设为Signaled(激发状态),使得WaitForSingleObject()正常返回,同时,系统也会将这个Mutex的所有权交给这个执行绪,然後程式继续执行,握有Mutex所有权者开始使用关键资源,并尽快在事後以ReleaseMutex()交出Mutex拥有权。</p>
  <p>关於程式实入这部分请您叁阅DemoSMem范例程式的读取与写入程式,同样的,有关Mutex的API函数也已包装进TMutex类别方便你的使用。</p>
</blockquote>

<h3>Event</h3>

<blockquote>
  <p>讨论过行程之间以Mutex协调避让的技术之後,Shared memory的示范专案DemoSMem尚留下一个悬疑待解:既然两个行程都利用这块记忆体,那我们怎麽知道什麽时候资料被改变了呢? 
  以一个回圈定期不断去抓资料回来比对不仅程式写起来累人,执行效率也很低落。</p>
  <p>当然,回到一开始提出的方法,写入资料的行程用讯息一一个别通知其他合作夥伴是可以行得通,不过,事情该有更好的解决之道才是。Win32的核心物件中有一种叫Event(事件)物件,方便我们在某一事件发生时设定其状态以便叁与通讯的行程注意到某一件重要事情的发生。</p>
  <p>产生一个Event物件的方法是呼叫CreateEvent() API函数:</p>
  <p>HANDLE CreateEvent(<br>
  &nbsp;&nbsp;&nbsp; LPSECURITY_ATTRIBUTES lpEventAttributes, <br>
  &nbsp;&nbsp;&nbsp; BOOL bManualReset, // flag for manual-reset event<br>
  &nbsp;&nbsp;&nbsp; BOOL bInitialState,// flag for initial state<br>
  &nbsp;&nbsp;&nbsp; LPCTSTR lpName // address of event-object name<br>
  );</p>
  <p>同样的,最後一个引数是执行绪在取得Event Handle前叁考同一Event物件的识别名称,如果相同名称的Event物件稍早已经产生而且叁用次数尚未归零消灭,并不会多产生一个Event物件,系统只单纯的将其叁用次数加一,执行绪彼此得以叁考到同一个物件。第三个引数用来设定Event物件的初值是否为Signaled(激发状态) 
  。第二个引数用来设定事件的激发状态是手动或自动;所谓手动与自动的分别在於事件的状态变成Signaled(激发状态)时,要由系统自动帮我们重设回非激发状态,或者由程式自行以ResetEvent()将事件设成非激发状态。</p>
  <p>观察DemoSMem的作法是这样的:当某一个行程修改了Shared memory的内容时,该行程以SetEvent() 
  API 函数将Event物件的状态设为Signaled(激发状态),叁与行程通讯的各支程式在开跑之初,除了以相同的识别名称建立(叁用)Event物件之外,还特别分派另一个Thread专司侦测特定Event物件激发状态的任务,一旦物件激发了,表示一定某一个行程修改了Shared 
  memory的资料,此时我们知道该是重新读取资料内容的时候了。</p>
  <p>呼! 终於将Shared memory的范例程式DemoSMem讲完了,下图是它执行的画面,彼此看来是亳无关联,但是经由共同分享的记忆体与Mutex,Event两种同步协调技术,彼此正在密切交换意见。</p>
  <p><br>
  图: DemoSMem执行情形</p>
</blockquote>

<h3>MailSlot</h3>

<blockquote>
  <p>执行DemoSMem时如果让你有广播的感觉,接下来要说的MailSlot会让你更有广播的感觉,而且它是可以跨越机器边界向网路广播的。从字面上看来,这像是与寄信有关的通讯机制,实际上它的行为也的确与其名称相符合。MailSlot就像是你的信箱,只要知道地址,任何人都可以寄信给你,不过,只有你才可以打开信箱读信。</p>
  <p>MailSlot是一种由系统维护的虚拟档案,建立并拥有Mailslot的行程扮演Server.的角色,其他的行程包含MailSlot 
  Server本身的行程均可以开启MailSlot写入讯息,不过,只有MailSlot Server可以读取资料的内容。这是个单一Server多个Client的机制,同时,资料只允许由Client对Server单向传送。</p>
  <p>我想你可能也习惯了,要产生一个MailSlot物件大概也需要一个识别名称吧! 
  :p 说不定连CreateMailSlot()函数名称都猜得一字不差。不过,这次的名称可不像先前那样可以随便高兴取什麽就取什麽的,它具有以下的固定格式:</p>
  <p><a href="file://ServerName/mailslot/[path]name">\\ServerName\mailslot\[path]name</a></p>
  <p>我第一次看到时心想: 天哪! 这该怎麽填呀? 
  边举例边说明会比较容易懂 </p>
  <p>\\.\mailslot\MyMailSlotName MailSlot的识别名称一定从「\\」双倒斜线开始。接下来的是机器的名称或组群网域的名称,这 
  的「.」句号代表的是行程所在的那部机器。再来是「\mailslot」,对於MailSlot,一定是这个单字照抄就是了。最後则是你自订的MailSlot名字。先前提到MailSlot实际上是特殊的虚拟档案,所以,要当它是档名应该也是说得通的。</p>
  <p>的确,援引我们对於档案系统的概念,MailSlot的识别名称就像路径档名一样,可以经过适当的阶层加分类管理,例如: 
  \\.\mailslot\Account\Note。最後再看一个例子: \\*\mailslot\MyMailSlotName,其中「*」指的是群组内的所有机器。</p>
  <p>说得够多了,让我们动手做做看吧! 首先是建立MailSlot Server的例子,取自本文所附的ChienIPC这个程式单元 
  </p>
  <p>procedure TMailSlotServer.Open;<br>
  var<br>
  &nbsp;&nbsp;&nbsp; ASlotName: AnsiString;<br>
  begin<br>
  &nbsp;&nbsp;&nbsp; if FActive then Exit;<br>
  &nbsp;&nbsp;&nbsp; // 构成 Mailslot 识别名称<br>
  &nbsp;&nbsp;&nbsp; ASlotName := '\\' + FServerName + '\mailslot\' + FSlotName;<br>
  &nbsp;&nbsp;&nbsp; FHandle := CreateMailslot(<br>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pchar(ASlotName), // MailSlot 识别名称<br>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0, // 
  讯息长度的最大值,设为零表示不限<br>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MAILSLOT_WAIT_FOREVER, // read time-out<br>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nil); // 
  安全属性,先暂时采用预设值<br>
  &nbsp;&nbsp;&nbsp; if FHandle = INVALID_HANDLE_VALUE then<br>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FActive := False<br>
  &nbsp;&nbsp;&nbsp; else<br>
  &nbsp;&nbsp;&nbsp; begin<br>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FActive := True;<br>
  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FWaitThread.Resume;<br>
  &nbsp;&nbsp;&nbsp; end;<br>
  end;</p>
  <p>再强调一次,只有MailSlolt Server才可以读取资料,读取的方法是先以GetMailslotInfo()侦测讯息的长度与数量,然後以回圈逐一配置记忆体并以ReadFile()读出资料(别忘了MailSlot也是档案),以下是一则范例:</p>
  <p>procedure TMailSlotServer.ReadFromMailSlot;<br>
  var<br>
  &nbsp;&nbsp;&nbsp; NextSize: DWORD;<br>
  &nbsp;&nbsp;&nbsp; MessageCount: DWORD;<br>
  &nbsp;&nbsp;&nbsp; Result: BOOL;<br>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -