Click here to Skip to main content
14,174,039 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

7.3K views
27 bookmarked
Posted 22 Apr 2019
Licenced CPOL

Update Checker For Software Update

, 14 May 2019
Rate this:
Please Sign up or sign in to vote.
This is a program to check out there is a updated files on the update server. If there's one, this program executes the update process.

Introduction

This is a program to check out there are updated files on the update server. The processing about how to update files is posted before, look at the post named "Simple FTP Uploader Using Zip File For Software Update". If there's one, this program executes the update process.

First, reading XML named as "UpdateList.xml" which is in the FTP server and compare the update datetime string value with the updatetime strings in the log file named as "SWUpdate.log". If there is no matched string, start downloading the zip file which included all update files.

Second, unarchiving it in the executive path of your program.

Third, executing the "Updater.exe" program and sending the caller program's name and update datetime string value which you read .

Fourth, the "Updater" program will terminate the caller program and overwrite the files from current ones to updated ones.

Fifth, the "Updater" program will force to restart the caller program and send the update datetime string to the caller program.

Lastly, in starting the caller program, it is putting an update datetime string into an updated log file. and runs itself.

Background

If you read the post named "Simple FTP Uploader Using Zip File For Software Update", first, you know updated files are in a zip file named "UpdateArchive.zip" and "UpdateList.xml" file. Each file's relative path is already mentioned in this XML file.

We will make a DLL project which is doing what I mentioned above from first to third.

Then, we will make a caller program using the function which is made in the DLL.

In the next step, we will make an updater project.

Using the Code

This code below is the source in DLL project.

//
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.IO.Pipes;
using System.Net;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;

namespace UpdateLib
{
    public class SWUpdateManager
    {
        private static SWUpdateManager instance = null;
        private static object syncRoot = new Object();

        public static SWUpdateManager GetInstance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)    // for multi threading
                            instance = new SWUpdateManager();
                    }
                }
                return instance;
            }
        }

        // This is to show a caller program a information what's the lasted.
        // It is not necessary if you don't want. 
        private String mLastUpdateDateTime;
        public String LastUpdateDateTime 
        {
            get
            {
                if (String.IsNullOrEmpty(mLastUpdateDateTime))
                {
                    String strUpdateLogFilePath = String.Format
                      ("{0}\\SWUpdate\\SWUpdate.log", Application.StartupPath);
                    try
                    {
                        String[] strLines = File.ReadAllLines(strUpdateLogFilePath);
                        for (int i = strLines.Length - 1; i >= 0; i--)
                        {
                            if (!String.IsNullOrEmpty(strLines[i]))
                            {
                                mLastUpdateDateTime = strLines[i];
                                break;
                            }
                        }
                    }
                    catch (Exception){ }
                }

                return mLastUpdateDateTime;
            } 
            set
            {
                mLastUpdateDateTime = value;
            }
        }

        // you can only process one at a time.
        private ManualResetEvent mEventCanReceive;
        private PipeClient mPipeClient;

        // this is a thread checking out whether updated file is uploaded.
        private Thread mThreadSWUpdate;

        // singleton pattern's constructor must be hidden.
        private SWUpdateManager()
        {
            mEventCanReceive = new ManualResetEvent(true);
            mPipeClient = null;
            mThreadSWUpdate = null;
        }

        // the caller program will call this function.
        public void StartCheckNewVersion()
        {
            // If there's another thread ran before, stop it.
            StopCheckNewVersion();

            ThreadStart ts = new ThreadStart(threadMain);
            mThreadSWUpdate = new Thread(ts);
            mThreadSWUpdate.Name = "Checking Update Version Thread";
            mThreadSWUpdate.Start();
        }

        // if the caller program is closing, it will call this function.
        public void StopCheckNewVersion()
        {
            if (mThreadSWUpdate != null)
            {
                mThreadSWUpdate.Abort();
                mThreadSWUpdate = null;
            }
        }

        private int LastIndexFromBytes(List<byte> srcBuffer, byte[] patternArray)
        {
            int idxResult = -1;

            int nLen = -1;
            if (patternArray != null)
            {
                nLen = patternArray.Length;

                if (nLen > 0 &&
                    srcBuffer.Count >= nLen)
                {
                    // reverse search
                    for (int i = srcBuffer.Count - nLen; i >= 0 ; i--)
                    {
                        byte[] byteExtractFromBuffer = srcBuffer.Skip(i).Take(nLen).ToArray();

                        if (byteExtractFromBuffer.SequenceEqual(patternArray))
                        {
                            // finded
                            idxResult = i;
                            break;
                        }
                    }
                }
            }

            return idxResult;
        }

        public void ServerUpdateFileCheck()
        {
            if (mEventCanReceive.WaitOne(1))
            {
                try
                {
                    String strUrl = "[ftp server address]";
                    String strDirectory = [key directory what I mentioned in my previous post];
                    String strFullDirectory = "";

                    if (String.IsNullOrEmpty(strUrl) || String.IsNullOrEmpty(strDirectory))
                        return;

                    strUrl = strUrl.Replace("\\", "/");
                    if (!strUrl.EndsWith("/")) strUrl += "/";

                    strDirectory = strDirectory.Replace("\\", "/");
                    if (!strDirectory.EndsWith("/")) strDirectory += "/";

                    strFullDirectory = String.Format("{0}SWUpdate/{1}", strUrl, strDirectory);

                    byte[] byteOrderMarkUtf8 = Encoding.UTF8.GetPreamble();
                    String strResultData = "";
                    using (WebClient webClient = new WebClient())
                    {
                        webClient.Headers.Add("user-agent", 
                           "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; 
                            .NET CLR 1.0.3705;)");

                        Stream base_stream = webClient.OpenRead(
                            String.Format("{0}UpdateList.xml", strFullDirectory));

                        StreamReader reader = new StreamReader(base_stream, Encoding.UTF8);
                        strResultData = reader.ReadToEnd();

                        // remove utf8 bom
                        List<byte> lstBytesResultData = Encoding.UTF8.GetBytes
                                                        (strResultData).ToList();
                        int nStartIdx = IndexFromBytes(lstBytesResultData, byteOrderMarkUtf8);
                        if (nStartIdx != -1)
                        {
                            byte[] bytesResultData = lstBytesResultData.Skip
                                   (nStartIdx + byteOrderMarkUtf8.Length).ToArray();
                            strResultData = Encoding.UTF8.GetString(bytesResultData);
                        }
                    }

                    if (!String.IsNullOrEmpty(strResultData))
                    {
                        XmlDocument xDoc = new XmlDocument();
                        xDoc.LoadXml(strResultData);

                        if (xDoc != null)
                        {
                            XmlNode xRootNode = xDoc.DocumentElement;
                            XmlNamespaceManager ns = new XmlNamespaceManager(xDoc.NameTable);

                            if (xRootNode.Attributes != null && 
                                    xRootNode.Attributes["update"] != null)
                            {
                                String strRoot = Application.StartupPath;
                                if (!strRoot.EndsWith("\\")) strRoot += "\\";

                                String strUpdate = xRootNode.Attributes["update"].Value;
                                String strExtractRootPath = 
                                    String.Format("{0}\\SWUpdate\\{1}", strRoot, strUpdate);

                                bool shouldUpdate = true;
                                String strUpdateLogFilePath = 
                                    String.Format("{0}\\SWUpdate\\SWUpdate.log", 
                                    Application.StartupPath);

                                try
                                {
                                    String strline;
                                    using (StreamReader sr = 
                                    new StreamReader(strUpdateLogFilePath, Encoding.UTF8))
                                    {
                                        while ((strline = sr.ReadLine()) != null)
                                        {
                                            if (strline.Equals(strUpdate, 
                                                StringComparison.OrdinalIgnoreCase))
                                            {
                                                shouldUpdate = false;
                                                break;
                                            }
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Debug.WriteLine(ex.ToString());
                                }

                                if (shouldUpdate)
                                {
                                    if (Directory.Exists(strExtractRootPath))
                                    {
                                        // remove existed directory
                                        DirectoryInfo dir = new DirectoryInfo
                                                            (strExtractRootPath);
                                        FileInfo[] files = dir.GetFiles("*.*", 
                                                       SearchOption.AllDirectories);

                                        foreach (System.IO.FileInfo file in files)
                                            file.Attributes = FileAttributes.Normal;

                                        Directory.Delete(strExtractRootPath, true);
                                    }

                                    // create directory
                                    Directory.CreateDirectory(strExtractRootPath);

                                    // start downloading
                                    WebClient downloadWebClient = new WebClient();
                                    downloadWebClient.Headers.Add("user-agent", 
                                          "Mozilla/4.0 (compatible; MSIE 6.0; 
                                           Windows NT 5.2; .NET CLR 1.0.3705;)");
                                    downloadWebClient.DownloadFileCompleted += 
                                           webClient_DownloadFileCompleted;

                                    String strDownloadFilePath = String.Format
                                       ("{0}\\UpdateArchive.zip", strExtractRootPath);

                                    downloadWebClient.QueryString.Add("updateTime", strUpdate);
                                    downloadWebClient.DownloadFileAsync(
                                        new Uri(String.Format("{0}UpdateArchive.zip", 
                                                strFullDirectory)),
                                        strDownloadFilePath
                                    );
                                }
                                else
                                {
                                    Debug.WriteLine("There's no file to update");
                                }
                            }
                        }
                        else
                        {
                            Debug.WriteLine("Xml fail");
                        }
                    }
                    CheckFileDate(DateTime.Now);

                    Debug.WriteLine("Complete checking");
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e.ToString());
                }
            }
            else
            {
                Debug.WriteLine("Wait until previous work's done");
            }
        }

        // updater.exe program execution.
        private void UpdateProc(String strExtractTime)
        {
            try
            {
                String strFilePath = String.Format("{0}\\{1}.exe", 
                                     Application.StartupPath, "Updater");

                Process.Start(strFilePath);
                Thread.Sleep(1000);

                mPipeClient = new PipeClient();

                Process pc = Process.GetCurrentProcess();
                String strFileName = pc.ProcessName;
                int nProcId = pc.Id;

                String strPipeMsg = String.Format("UPDATE||{0}||{1}||{2}", 
                                    strFileName, nProcId, strExtractTime);
                mPipeClient.Send(
                    strPipeMsg, "Updater");
            }
            catch (IOException ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }
        
        private void webClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            Debug.WriteLine("Completing downloading");

            try
            {
                String strRoot = Application.StartupPath;

                String strExtractTime = 
                   ((System.Net.WebClient)(sender)).QueryString["updateTime"];
                String strUpdateArchivePath = String.Format(
                    "{0}\\SWUpdate\\{1}\\UpdateArchive.zip",
                    strRoot,
                    strExtractTime);
                String strExtractRootPath = Path.GetDirectoryName(strUpdateArchivePath);

                String strNewFilesPath = strExtractRootPath + "\\NEW";
                if (!Directory.Exists(strNewFilesPath)) 
                        Directory.CreateDirectory(strNewFilesPath);

                String strBackupFilesPath = strExtractRootPath + "\\BACKUP";

                ArrayList aryArchiveList = new ArrayList();

                mEventCanReceive.Reset();
                using (ZipArchive archive = ZipFile.Open
                       (strUpdateArchivePath, ZipArchiveMode.Update))
                {
                    // Unarchive in the "NEW" folder
                    archive.ExtractToDirectory(strNewFilesPath);

                    foreach (ZipArchiveEntry file in archive.Entries)
                        aryArchiveList.Add(file.FullName);
                }
                mEventCanReceive.Set();

                // "Backup previous files in the "BACKUP" folder
                foreach (String strfile in aryArchiveList)
                {
                    string curFilePath = Path.Combine(strRoot, strfile);
                    if (File.Exists(curFilePath))
                    {
                        string copyToPath = String.Format("{0}\\{1}", 
                                            strBackupFilesPath, strfile);
                        string copyToDirPath = Path.GetDirectoryName(copyToPath);
                        if (!Directory.Exists(copyToDirPath)) 
                                 Directory.CreateDirectory(copyToDirPath);

                        File.Copy(curFilePath, copyToPath);
                    }
                }

                // start update
                UpdateProc(strExtractTime);
            }
            catch(Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }

        private void threadMain()
        {
            Thread.Sleep(5000);

            Debug.WriteLine(String.Format("'{0}' Start", Thread.CurrentThread.Name));

            bool bAbort = false;
            while (!bAbort)
            {
                try
                {
                    ServerUpdateFileCheck();

                    // every 30 seconds
                    for (int i = 0; i < 30; i++) Thread.Sleep(60 * 1000);
                }
                catch (ThreadAbortException)
                {
                    bAbort = true;
                    Thread.ResetAbort();
                }
                catch (System.Exception ex)
                {
                    Debug.WriteLine(ex.ToString());

                    for (int i = 0; i < 60; i++) Thread.Sleep(60 * 1000);
                }
            }

            Debug.WriteLine(String.Format("'{0}' Ended", Thread.CurrentThread.Name));
        }

        private void CheckFileDate(DateTime curDate)
        {
            String strCurDir = String.Format("{0}\\SWUpdate\\", Application.StartupPath);
            if (!Directory.Exists(strCurDir)) Directory.CreateDirectory(strCurDir);

            String[] dirPaths = Directory.GetDirectories(strCurDir);
            if (dirPaths.Length <= 0) return;

            foreach (String strDir in dirPaths)
            {
                DirectoryInfo dir = new DirectoryInfo(strDir);
                
                DateTime fileDt;
                if (DateTime.TryParseExact(dir.Name, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out fileDt))
                {
                    TimeSpan ts = curDate.Subtract(fileDt);
                    if ((int)ts.TotalDays > 1)
                    {
                        // Removing directory downloading before
                        
                        try
                        {
                            // If the file is set to readonly, we must remove that priviliges to remove
                            FileInfo[] files = dir.GetFiles("*.*", SearchOption.AllDirectories);

                            foreach (System.IO.FileInfo file in files)
                                file.Attributes = FileAttributes.Normal;
                            
                            // remove
                            Directory.Delete(strDir, true);
                        }
                        catch (Exception e)
                        {
                            Debug.WriteLine(e.ToString());
                        }
                    }
                }
            }
        }
    }

    public class PipeClient
    {
        public void Send(string SendStr, string PipeName, int TimeOut = 1000)
        {
            try
            {
                NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", PipeName, PipeDirection.Out, PipeOptions.Asynchronous);
                pipeStream.Connect(TimeOut);

                Debug.WriteLine(String.Format("'{0}' connected", PipeName));

                byte[] _buffer = Encoding.UTF8.GetBytes(SendStr);
                pipeStream.BeginWrite(_buffer, 0, _buffer.Length, AsyncSend, pipeStream);
            }
            catch (Exception oEX)
            {
                Debug.WriteLine(oEX.ToString());
            }
        }

        private void AsyncSend(IAsyncResult iar)
        {
            try
            {
                NamedPipeClientStream pipeStream = (NamedPipeClientStream)iar.AsyncState;

                pipeStream.EndWrite(iar);
                pipeStream.Flush();
                pipeStream.Close();
                pipeStream.Dispose();
            }
            catch (Exception oEX)
            {
                Debug.WriteLine(oEX.ToString());
            }
        }
    }
}

//

Then, we should make a winform program and call functions when it roads and is closing.

//
using UpdateLib;

private void FormMain_Load(object sender, EventArgs e)
{
    SWUpdateManager swManager = SWUpdateManager.GetInstance;
    swManager.StartCheckNewVersion();
}

private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
    SWUpdateManager swManager = SWUpdateManager.GetInstance;
    swManager.StopCheckNewVersion();
}
//

You know, you should "Add reference..." and add the DLL project in your winform program.

In the next step, we should make an updater program like this:

//
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace UpdaterPg
{
    public delegate void PipeMessageDelegate(string message);

    public partial class MainWindow : Window
    {
        private static MainWindow instanceMain = null;
        public static void ShowMsg(String strMsg, bool isClear = false)
        {
            if (instanceMain != null && instanceMain._contentLoaded)
            {
                Action action = delegate()
                {
                    if (isClear)
                        instanceMain.txtMsg.Text = "";

                    instanceMain.txtMsg.Text += 
                        String.Format("{0}\t{1}\r\n", DateTime.Now.ToString(), strMsg);
                };
                instanceMain.Dispatcher.Invoke(DispatcherPriority.Normal, action);
            }
        }
        
        private PipeServer mPipeServer;
        public MainWindow()
        {
            InitializeComponent();
            instanceMain = this;
            
            // creation pipe server
            mPipeServer = new PipeServer();
            mPipeServer.PipeMessage += new DelegateMessage(PipesMessageHandler);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // start pipe server
            mPipeServer.Listen("Updater");
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            mPipeServer.PipeMessage -= new DelegateMessage(PipesMessageHandler);
        }

        private void PipesMessageHandler(string message)
        {
            try
            {
                Debug.WriteLine(String.Format("'{0}' receive", message));

                String strMessege = message.Replace("\0", "");
                String[] strProcessInfo = strMessege.Split(new String[] { "||" }, 
                                          StringSplitOptions.RemoveEmptyEntries);
                if (strProcessInfo != null &&
                    strProcessInfo.Length >= 4)
                {
                    if (strProcessInfo[0].Equals
                            ("UPDATE", StringComparison.OrdinalIgnoreCase))
                        StartUpdateProcess(strProcessInfo);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }

        private void StartUpdateProcess(String[] strProcessInfo)
        {
            String strRoot = 
                System.IO.Path.GetDirectoryName
                       (Process.GetCurrentProcess().MainModule.FileName);

            String strProcName = strProcessInfo[1];
            String strProcId = strProcessInfo[2];

            String strExtractTime = strProcessInfo[3];
            String strExtractDir = String.Format
                       ("{0}\\SWUpdate\\{1}", strRoot, strExtractTime);

            int nProcId;
            if (Int32.TryParse(strProcId, out nProcId))
            {
                try
                {
                    this.Dispatcher.BeginInvoke
                          (new Action(() => { this.WindowState = WindowState.Normal; }));

                    ShowMsg(
                        String.Format("start('{0}') update", strProcName), true);

                    #region kill caller program
                    Process findProc = Process.GetProcessById(nProcId);
                    if (findProc != null)
                    {
                        findProc.Kill();
                        Thread.Sleep(1000);
                    }
                    #endregion

                    String strNewPatchDir = String.Format("{0}\\NEW", strExtractDir);
                    String strCopyToDir = String.Format("{0}\\", strRoot);
                    Copy(strNewPatchDir, strCopyToDir);

                    Debug.WriteLine(String.Format("copy from '{0}' to '{1}'", 
                                    strNewPatchDir, strCopyToDir));
                    Thread.Sleep(1000);

                    #region restart caller program
                    String strFilePath = String.Format("{0}\\{1}.exe", strRoot, strProcName);
                    
                    Debug.WriteLine(String.Format("restart '{0}'", strFilePath));

                    ProcessStartInfo startInfo = new ProcessStartInfo(strFilePath);
                    startInfo.Arguments = String.Format(
                        "MSG||{0}||Program restart due to be updated",
                        strExtractTime);
                    Process.Start(startInfo);

                    ShowMsg(
                        String.Format("Complete ('{0}') update! 
                                       Restart program.", strProcName));

                    Thread.Sleep(500);
                    this.Dispatcher.BeginInvoke(new Action(() => 
                           { this.WindowState = WindowState.Minimized; }));

                    #endregion
                }
                catch (Exception e)
                {
                    ShowMsg(e.ToString());
                }
            }
            else
            {
                Debug.WriteLine(String.Format("Fail process Id : {0}", nProcId));
            }
        }

        private void Copy(string sourceDir, string targetDir)
        {
            if (!Directory.Exists(targetDir))
            {
                Directory.CreateDirectory(targetDir);
                Debug.WriteLine(String.Format("'{0}' folder created", targetDir));
            }

            foreach (var file in Directory.GetFiles(sourceDir))
            {
                File.Copy(file, System.IO.Path.Combine
                     (targetDir, System.IO.Path.GetFileName(file)), true);

                Debug.WriteLine(String.Format("copy from '{0}' to '{1}'", file, targetDir));
            }

            // recursive copy
            foreach (var directory in Directory.GetDirectories(sourceDir))
                Copy(directory, System.IO.Path.Combine(targetDir, 
                                 System.IO.Path.GetFileName(directory)));
        }
    }
}

//

As you've already seen, this project is made by WPF solution. It doesn't matter if you want to make a winform, you just do it.

Finally, when you follow this far, there is only one step to complete. Look at it like this:

//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

using UpdateLib;

namespace CallerProgram
{
    static class Program
    {
        [STAThread]
        static void Main(String[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            if(args != null)
            {
                  foreach(string strArg in args)
                  {
                       if (strArg.StartsWith("MSG||"))
                       {
                           String[] strUpdates = strArg.Split
                            (new String[] { "||" }, StringSplitOptions.RemoveEmptyEntries);

                           String strUpdateTime = "";
                           String strUpdateMsg = "";
                           if(strUpdates.Length >= 2)
                           {
                                strUpdateTime = strUpdates[1];
                                strUpdateMsg = strUpdates[2];

                                String strUpdateLogFilePath = 
                                    String.Format("{0}\\SWUpdate\\SWUpdate.log", 
                                    Application.StartupPath);

                                try
                                {
                                    FileStream ufs = new FileStream
                                       (strUpdateLogFilePath, FileMode.OpenOrCreate, 
                                        FileAccess.Write);
                                    StreamWriter sw = new StreamWriter(ufs, Encoding.UTF8);
                                    sw.BaseStream.Seek(0, SeekOrigin.End);
                                    sw.WriteLine(strUpdateTime);

                                    sw.Close();
                                    sw.Dispose();

                                    ufs.Close();
                                    ufs.Dispose();
                                 } 
                                 catch(Exception ex)
                                 {
                                     Debug.WriteLine(ex.ToString());
                                  }
                                  
                                  SWUpdateManager swManager = SWUpdateManager.GetInstance;
                                  swManager.LastUpdateDateTime = strUpdateTime;
                            }
                            else
                            {
                                Debug.WriteLine(strArg);
                            }
                        }
                        else
                        {
                           Debug.WriteLine(strArg);
                        }
                 }
            }

            Application.Run(new FormMain());
        }
    }
}

//

This code must be in your main function in the caller program.

History

  • 4/22/2019: First update
  • 4/23/2019: Add the codes removing utf-8 bom
  • 5/9/2019: Add a missing function named 'CheckFileDate'
  • 5/15/2019: Add a missing class named 'PipeClient'

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Bloody Chicken
Software Developer (Senior) Wiseneosco
Korea (Republic of) Korea (Republic of)
You know I can't speak English well but I'm learning. If there anything wrong in my article. Understand me. Smile | :) I'm so thankful if you could correct this.
Anyway, I'm a software programmer in Korea. I have been doing development about 10 years.
I majored in Computer Science and Engineering. I'm using c# mainly. Bye!

You may also be interested in...

Pro

Comments and Discussions

 
Questiondesign flaws Pin
sx200817-May-19 8:10
membersx200817-May-19 8:10 
QuestionPipeclient()! Pin
CsLacy10-May-19 3:09
memberCsLacy10-May-19 3:09 
AnswerRe: Pipeclient()! Pin
Bloody Chicken14-May-19 23:24
professionalBloody Chicken14-May-19 23:24 
QuestionMissing something Pin
sestoenner30-Apr-19 5:43
membersestoenner30-Apr-19 5:43 
AnswerRe: Missing something Pin
Bloody Chicken8-May-19 19:56
professionalBloody Chicken8-May-19 19:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.190524.3 | Last Updated 15 May 2019
Article Copyright 2019 by Bloody Chicken
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid