DDE通信を使用してPDFファイルを印刷する 2010/02/06

Pocket

Adobe Readerをインストールしている場合、C#で開発したプログラムからPDFファイルを印刷する方法は2通りあります。

ひとつは、Adobeのコントロールに印刷対象のPDFファイルを表示して印刷する方法です。
これは以前、紹介しました。

もうひとつは、DDEを使用して印刷する方法があります。

DDEは、最近のプログラムではあまり見かけない古い技術的です。
C#ではDDE通信を直接サポートしておらず、DLLをImportして実装することになります。

今回は、Adobe SDKのサンプルプログラムのなかにC++で実装されたものがありますので、これを元に実装したいと思います。


サンプルプログラムでは、Adobe ReaderがインストールされているかレジストリをチェックしてAdobe Readerのプロセスを起動しているようです。

今回は別の方法でAcrobat Readerのプロセスを起動したいと思います。

さて、プロセスを起動した後の処理ですが以下のようになります。

  1. 実行するプロセスの起動
  2. プロセスのハンドル取得
  3. DDEのサービス初期化
  4. DDEサービスとのセッション接続
  5. DDEサービスを使用して印刷処理の実行
  6. DDEサービスとのセッションを切断
  7. DDEサービスの終了処理

各DDEの関数の機能については、MSDNの「Dynamic Data Exchange (DDE) マネージメント ライブラリ」を参照してください。

VC++とC#ではデータの型が異なります。
面倒ですが対応するものに置き換えて行く必要があります。
私は@ITの記事などを参照しながら実装しました。

また、定数がいくつか出て来ますが、一部はC言語のヘッダファイルに記述されているのものがあります。

C#では、C言語のヘッダファイルを読み込むことができないので自力で実装する必要があります。
今回は、ddeml.hの中から必要な部分を実装しています。

今回は、印刷の機能として「FilePrintSilent」を使用しています。
Adobe Readerに対してDDEで発行できるコマンドについてはAcrobat SDKのマニュアルの「Acrobat Interapplication Communication > Interapplication Communication API Reference > DDE Messages」を確認してください。
さて実装したプログラムは以下のようになりました。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;   // DLLを使用する際に必要。

namespace jp.knowledge_base.sample.PdfLib
{
    public class PdfDdePrint
    {
        // ====================================================================
        // クラス内で使用する定数
        // ====================================================================
        private const String ACROBAT = "AcroExch.Document";
        private const String ACRO_DDESERVER = "acroview";
        private const String ACRO_DDETOPIC = "control";

        private const int MAX_TIMEOUT = 3000;
        private const int STEP_SIZE = 200;
        private const String DDE_CMD_STRING_S = "[FilePrintSilent(\"";
        private const String DDE_CMD_STRING_E = "\")]";

        // ====================================================================
        // DDEML.hに記述されている定数
        // ====================================================================
        private const uint APPCMD_CLIENTONLY = 0x00000010;

        private const int XCLASS_FLAGS = 0x4000;
        private const int XTYP_EXECUTE = (0x0050 | XCLASS_FLAGS);

        private const int DMLERR_NO_ERROR = 0x0000;
        private const int DMLERR_ADVACKTIMEOUT = 0x4000;
        private const int DMLERR_BUSY = 0x4001;
        private const int DMLERR_DATAACKTIMEOUT = 0x4002;
        private const int DMLERR_DLL_NOT_INITIALIZED = 0x4003;
        private const int DMLERR_DLL_USAGE = 0x4004;
        private const int DMLERR_EXECACKTIMEOUT = 0x4005;
        private const int DMLERR_INVALIDPARAMETER = 0x4006;
        private const int DMLERR_LOW_MEMORY = 0x4007;
        private const int DMLERR_MEMORY_ERROR = 0x4008;
        private const int DMLERR_NOTPROCESSED = 0x4009;
        private const int DMLERR_NO_CONV_ESTABLISHED = 0x400A;
        private const int DMLERR_POKEACKTIMEOUT = 0x400B;
        private const int DMLERR_POSTMSG_FAILED = 0x400C;
        private const int DMLERR_REENTRANCY = 0x400D;
        private const int DMLERR_SERVER_DIED = 0x400E;
        private const int DMLERR_SYS_ERROR = 0x400F;
        private const int DMLERR_UNADVACKTIMEOUT = 0x4010;
        private const int DMLERR_UNFOUND_QUEUE_ID = 0x4011;

        // ====================================================================
        // DdeInitializeで使用するCallBack関数のデータ型定義
        // ====================================================================
        private delegate String DDE_ProcessMessage();

        // ====================================================================
        // DDE処理で使用するDLLのImport
        // ====================================================================
        [DllImport("user32.dll", EntryPoint = "DdeInitialize", CharSet = CharSet.Unicode)]
        private extern static uint DdeInitialize(ref uint pidInst, DDE_ProcessMessage pfnCallback, uint afCmd, uint ulRes);

        [DllImport("user32.dll", EntryPoint = "DdeCreateStringHandle", CharSet = CharSet.Unicode)]
        private extern static IntPtr DdeCreateStringHandle(uint idInst, String psz, int iCodePage);

        [DllImport("user32.dll", EntryPoint = "DdeConnect", CharSet = CharSet.Unicode)]
        private extern static IntPtr DdeConnect(uint idInst, IntPtr hszService, IntPtr hszTopic, IntPtr pCC);

        [DllImport("user32.dll", EntryPoint = "DdeClientTransaction", CharSet = CharSet.Unicode)]
        private extern static long DdeClientTransaction(byte[] pData, int cbData, IntPtr hConv, IntPtr hszItem,
                                uint wFmt, uint wType, uint dwTimeout, ref uint pdwResult);

        [DllImport("user32.dll", EntryPoint = "DdeGetLastError", CharSet = CharSet.Unicode)]
        private extern static uint DdeGetLastError(uint idInst);

        [DllImport("user32.dll", EntryPoint = "DdeDisconnect", CharSet = CharSet.Unicode)]
        private extern static IntPtr DdeDisconnect(IntPtr hConv);

        [DllImport("user32.dll", EntryPoint = "DdeFreeStringHandle", CharSet = CharSet.Unicode)]
        private extern static IntPtr DdeFreeStringHandle(uint idInst, IntPtr hsz);

        [DllImport("user32.dll", EntryPoint = "DdeUninitialize", CharSet = CharSet.Unicode)]
        private extern static uint DdeUninitialize(uint idInst);

        // ====================================================================
        // クラス内で使用するインスタンス変数t
        // ====================================================================
        private IntPtr _hszServerName;
        private IntPtr _hszTopicName;
        private IntPtr _hConversation;
        private uint _id = 0;
        private object _acrobat;

        public void open()
        {
            // Acrobatのインスタンスを起動する。
            Type type = Type.GetTypeFromProgID(ACROBAT);
            _acrobat = Activator.CreateInstance(type);

            uint result = DdeInitialize(ref _id, pfnCallback(), APPCMD_CLIENTONLY, 0);
            if (DMLERR_NO_ERROR != result)
            {
                throw new Exception("DdeInitialize Error!!");
            }

            _hszServerName = DdeCreateStringHandle(_id, ACRO_DDESERVER, 0);
            _hszTopicName = DdeCreateStringHandle(_id, ACRO_DDETOPIC, 0);

            int sleep = 0;
            do
            {
                _hConversation = DdeConnect(_id, _hszServerName, _hszTopicName, IntPtr.Zero);
                if (0 != _hConversation.ToInt32() || sleep > MAX_TIMEOUT)
                {
                    break;
                }
                Thread.Sleep(sleep += STEP_SIZE);
            } while (true);

            switch (DdeGetLastError(_id))
            {
                case DMLERR_DLL_NOT_INITIALIZED:
                    throw new Exception("DMLERR_DLL_NOT_INITIALIZED");
                case DMLERR_INVALIDPARAMETER:
                    throw new Exception("DMLERR_INVALIDPARAMETER");
                case DMLERR_NO_CONV_ESTABLISHED:
                    throw new Exception("DMLERR_NO_CONV_ESTABLISHED");
            }

            if (0 == _hConversation.ToInt32())
            {
                throw new Exception("DdeConnect Error!!");
            }
        }

        public void execute(String fileName){
            uint dwResult = 0;

            String cmdBuf = DDE_CMD_STRING_S + fileName + DDE_CMD_STRING_E;
            byte[] ddeCmdBuf = Encoding.Unicode.GetBytes(cmdBuf);

            long result = DdeClientTransaction(ddeCmdBuf, ddeCmdBuf.Length, _hConversation,
                            IntPtr.Zero, 1, XTYP_EXECUTE, 30000, ref dwResult);

            switch (DdeGetLastError(_id)){
                case DMLERR_ADVACKTIMEOUT:
                    throw new Exception("DMLERR_ADVACKTIMEOUT");
                case DMLERR_BUSY:
                    throw new Exception("DMLERR_BUSY");
                case DMLERR_DATAACKTIMEOUT:
                    throw new Exception("DMLERR_DATAACKTIMEOUT");
                case DMLERR_DLL_NOT_INITIALIZED:
                    throw new Exception("DMLERR_DLL_NOT_INITIALIZED");
                case DMLERR_DLL_USAGE:
                    throw new Exception("DMLERR_DLL_USAGE");
                case DMLERR_EXECACKTIMEOUT:
                    throw new Exception("DMLERR_EXECACKTIMEOUT");
                case DMLERR_INVALIDPARAMETER:
                    throw new Exception("DMLERR_INVALIDPARAMETER");
                case DMLERR_LOW_MEMORY:
                    throw new Exception("DMLERR_LOW_MEMORY");
                case DMLERR_MEMORY_ERROR:
                    throw new Exception("DMLERR_MEMORY_ERROR");
                case DMLERR_NOTPROCESSED:
                    throw new Exception("DMLERR_NOTPROCESSED");
                case DMLERR_NO_CONV_ESTABLISHED:
                    throw new Exception("DMLERR_NO_CONV_ESTABLISHED");
                case DMLERR_POKEACKTIMEOUT:
                    throw new Exception("DMLERR_POKEACKTIMEOUT");
                case DMLERR_POSTMSG_FAILED:
                    throw new Exception("DMLERR_POSTMSG_FAILED");
                case DMLERR_REENTRANCY:
                    throw new Exception("DMLERR_REENTRANCY");
                case DMLERR_SERVER_DIED:
                    throw new Exception("DMLERR_SERVER_DIED");
                case DMLERR_SYS_ERROR:
                    throw new Exception("DMLERR_SYS_ERROR");
                case DMLERR_UNADVACKTIMEOUT:
                    throw new Exception("DMLERR_UNADVACKTIMEOUT");
                case DMLERR_UNFOUND_QUEUE_ID:
                    throw new Exception("DMLERR_UNFOUND_QUEUE_ID");
            }
        }

        public void close()
        {
            DdeDisconnect(_hConversation);
            DdeFreeStringHandle(_id, _hszServerName);
            DdeFreeStringHandle(_id, _hszTopicName);
            DdeUninitialize(_id);
        }

        private DDE_ProcessMessage pfnCallback()
        {
            return null;
        }
    }
}

このように呼び出して使用することができます。

        private void btnPrint2_Click(object sender, EventArgs e)
        {
            try
            {
                PdfDdePrint ddeOpen = new PdfDdePrint();
                ddeOpen.open();
                ddeOpen.execute(txtFileName.Text);
                ddeOpen.close();
            }
            catch (Exception ex)
            {
                MessageBox.Show("DDE処理で例外発生 : " + ex.Message);
            }
        }

あとは、(通常使用する)プリンタの切り替えや、プリンタのステータスチェックなどを実装するとAdobe Readerを起動せず任意のPDFファイルを印刷できるようになります。

 

Trackback URL

Leave a Reply