개발 중인 프로젝트에서 상용 컴퍼넌트를 이용하다보니 제작한 라이브러리 포함하여 dll이 30개 정도 되었다.

 

고객님께서는 dll이 너무 많아 복잡하다며 실행 파일 한개만을 원하셨다.

 

 

 

제프리 리쳐 형님과 같이 동적으로 어셈블리를 로드할까 했는데....좀 더 검색해보았다.

 

 

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {

    String resourceName = AssemblyLoadingAndReflection. +

        new AssemblyName(args.Name).Name + .dll;

    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))

    {

        Byte[] assemblyData = new Byte[stream.Length];

        stream.Read(assemblyData, 0, assemblyData.Length);

        return Assembly.Load(assemblyData);

    }

}; 

참조 : https://blogs.msdn.microsoft.com/microsoft_press/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition/

 

 

 

 

결국 Costumra.Fody 를 찾게 되었다.

 

자동으로 exe 안에 라이브러리를 넣어준다. 압축까지 지원해준다.

 

Nuget을 통하여 설치하면 "FodyWeavers.xml" 파일이 생성된다.

 

해당 xml 파일을 이용해 정교한 설정이 가능하다. (압축여부, Debug심볼첨부여부 등등...)

 

https://github.com/Fody/Costura

 

 

 

 

 

압축하였을 때는 .zip 확장자로 exe 파일에 삽입되어있다.

 

[압축하지 않은 상태]

 

 

 

[압축한 상태]

'.Net > C#' 카테고리의 다른 글

C# 리플렉션을 이용한 Delegate 넘기기  (0) 2016.05.11
C# Undo Redo 기능 구현하기  (3) 2015.10.20
C# 원문자 구하기  (0) 2015.10.05
SerialPort Read Blocking 시키기  (0) 2015.08.05
문자열 비교 테스트 (대소문자 무시)  (0) 2015.07.02

A.exe에서 B.dll을 컴파일시 참조하지 않고  동적으로 참조할 때,

 

B.dll에 있는 특정 클래스 메서드의 파라미터(Delegate)를 넘길때 방법입니다. 

 

리플렉션을 이용해서 대리자 개체를 생성 후 Invoke 파라미터에 넘겨주었습니다.

 

A.exe

 

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Reflection;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

 

namespace DelegateReflaction

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            InvokeTest();

        }

 

        private void InvokeTest()

        {

            string dllFilePath = @"D:\Work\Project\Test\DelegateReflaction\TestLibrary\bin\Debug\TestLibrary.dll";

            Assembly assembly = Assembly.LoadFile(dllFilePath);

 

            Type formType = assembly.GetType("TestLibrary.ChiForm");

 

            //Child 델리게이트 타입 가져오기

            Type deleType = assembly.GetTypes().Single(a => a.Name == "CallbackDelegate");

 

 

            //현재 클래스의 Callback 메서드 정보 가져오기

            MethodInfo callBackMethodInfo = this.GetType().GetMethod("Callback", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);

 

            //Child CallbackDelegate Delegate를 이용해서 Deleagte 생성

            Delegate callbackMethod = Delegate.CreateDelegate(deleType, this, callBackMethodInfo);

 

            //Child 폼 생성

            Form form = Activator.CreateInstance(formType) as Form;

 

            //Child DoWork 메서드 가져오기

            MethodInfo doworkMethodInfo = formType.GetMethod("DoWork");

 

            //파라미터 생성

            object[] parameters = new object[2];

            parameters[0] = "test~";

            parameters[1] = callbackMethod;

 

            //Child Dowork 메서드 호출

            doworkMethodInfo.Invoke(form, parameters);

 

            form.Show();

        }

 

        void Callback(string msg)

        {

            MessageBox.Show(msg);

        }

    }

}

  

 

 

B.dll

 

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

 

namespace TestLibrary

{

    public class ChiForm : Form

    {

        public delegate void CallbackDelegate(string status);

 

        public ChiForm()

        {

            InitializeComponent();

        }

 

        public void DoWork(string param, CallbackDelegate callback)

        {

            callback("status");

        }

 

        private void InitializeComponent()

        {

            this.SuspendLayout();

            //

            // ChiForm

            //

            this.ClientSize = new System.Drawing.Size(284, 262);

            this.Name = "ChiForm";

            this.Text = "ChildForm";

            this.ResumeLayout(false);

 

        }

    }

} 

 

 

프로젝트 (vs2015 작성) :  DelegateReflaction.zip

 

'.Net > C#' 카테고리의 다른 글

C# 실행파일 안에 DLL 넣기  (0) 2016.05.16
C# Undo Redo 기능 구현하기  (3) 2015.10.20
C# 원문자 구하기  (0) 2015.10.05
SerialPort Read Blocking 시키기  (0) 2015.08.05
문자열 비교 테스트 (대소문자 무시)  (0) 2015.07.02

Undo 기능에 쓰일 스택과 Redo 기능에 쓰일 스택을 이용해서 Undo, Redo 기능을 구현해보았다.

 

UndoRedoHistory 클래스에서는 특정 타입의 상태를 저장할 수 있고, Undo, Redo를 통해 상태 값을 가져온다.

 

 

/// <summary>

    /// Undo Redo 내역을 기록하는 클래스

    /// </summary>

    /// <typeparam name="T">기록할 타입</typeparam>

    public class UndoRedoHistory<T>

    {

        const int DefaultUndoCount = 10;

        RimitedStack<T> undoStack;

        RimitedStack<T> redoStack;

 

        /// <summary>

        /// Undo 기능을 사용할 수 있는지 여부를 가져온다.

        /// </summary>

        public bool IsCanUndo

        {

            get

            {

                //맨 초기 상태때문에 1보다 커야한다.

                return this.undoStack.Count > 1;

            }

        }

 

        /// <summary>

        /// Redo 기능을 사용할 수 있는지 여부를 가져온다.

        /// </summary>

        public bool IsCanRedo

        {

            get { return this.redoStack.Count > 0; }

        }

 

        public UndoRedoHistory()

            :this(DefaultUndoCount)

        {

 

        }

 

        public UndoRedoHistory(int defaultUndoCount)

        {

            undoStack = new RimitedStack<T>(defaultUndoCount);

            redoStack = new RimitedStack<T>(defaultUndoCount);

        }

 

        /// <summary>

        /// 이전 상태를 가져온다.

        /// </summary>

        /// <returns></returns>

        public T Undo()

        {

            T state = this.undoStack.Pop();

            this.redoStack.Push(state);

            return this.undoStack.Peek();

        }

 

        /// <summary>

        /// 이후 상태를 가져온다.

        /// </summary>

        /// <returns></returns>

        public T Redo()

        {

            T state = this.redoStack.Pop();

            this.undoStack.Push(state);

            return state;

        }

 

        /// <summary>

        /// 상태를 추가한다.

        /// </summary>

        /// <param name="state"></param>

        public void AddState(T state)

        {

            this.undoStack.Push(state);

            this.redoStack.Clear();

        }

 

        /// <summary>

        /// 상태를 모두 제거한다.

        /// </summary>

        internal void Clear()

        {

            this.undoStack.Clear();

            this.redoStack.Clear();

        }

    }

 

 

 

 

ReimetedStack은 개수 제한이 있는 스택이다.

 

개수 제한이 걸렸을 때, 맨 처음 삽입한 상태를 제거해야하므로, 내부 자료구조는 사실 Stack이 아닌 List로 구현해놓았다.

 

/// <summary>

/// 개수 제한이 있는 스택 클래스

/// </summary>

internal class LimitedStack<T>

{

    List<T> list = new List<T>();

    readonly int capacity;

    /// <summary>

    /// 개수를 가져온다.

    /// </summary>

    public int Count

    {

        get { return this.list.Count; }

    }

 

    /// <summary>

    /// 생성자

    /// </summary>

    /// <param name="capacity"></param>

    public LimitedStack(int capacity)

    {

        this.capacity = capacity;

    }

 

    /// <summary>

    /// 맨위의 개체를 반환하고 제거한다.

    /// </summary>

    /// <returns></returns>

    internal T Pop()

    {

        T t = this.list[0];

        this.list.RemoveAt(0);

        return t;

    }

 

    /// <summary>

    /// 개체를 맨위에 삽입한다.

    /// </summary>

    /// <param name="state"></param>

    internal void Push(T state)

    {

        this.list.Insert(0, state);

        if (this.list.Count > capacity)

        {

            this.list.RemoveAt(this.list.Count - 1);

        }

    }

 

    /// <summary>

    /// 맨위의 개체를 제거하지 않고 반환한다.

    /// </summary>

    /// <returns></returns>

    internal T Peek()

    {

        return this.list[0];

    }

 

    /// <summary>

    /// 개체를 모두 제거한다.

    /// </summary>

    internal void Clear()

    {

        this.list.Clear();

    }

}

 

 

 

 

아래는 텍스트 박스의 내용을 저장하는 기능을 구현한 데모 코드이다.

 

public partial class Form1 : Form

{

UndoRedoHistory<string> undoRedoHistory = new UndoRedoHistory<string>(10);

 

public Form1()

{

    InitializeComponent();

    SaveCurrentState();

    this.textBox1.KeyDown += TextBox1_KeyDown;

}

 

private void TextBox1_KeyDown(object sender, KeyEventArgs e)

{

    Keys keyData = e.KeyData;

    if ((keyData & Keys.Control) == Keys.Control)

    {

        if ((keyData & Keys.Z) == Keys.Z)

        {

            this.Undo();

        }

        else if ((keyData & Keys.Y) == Keys.Y)

        {

            this.Redo();

        }

    }

}

 

private void Redo()

{

    if (this.undoRedoHistory.IsCanRedo)

    {

        this.textBox1.Text = this.undoRedoHistory.Redo();

    }

}

 

private void Undo()

{

    if (this.undoRedoHistory.IsCanUndo)

    {

        this.textBox1.Text = this.undoRedoHistory.Undo();

    }

}

 

private void btnSave_Click(object sender, EventArgs e)

{

    this.SaveCurrentState();

}

 

/// <summary>

/// 현재 상태를 저장한다.

/// </summary>

private void SaveCurrentState()

{

    undoRedoHistory.AddState(this.textBox1.Text);

}

 

 

 

샘플 프로젝트 :KDYFramework.UndoRedo.zip

 

 

'.Net > C#' 카테고리의 다른 글

C# 실행파일 안에 DLL 넣기  (0) 2016.05.16
C# 리플렉션을 이용한 Delegate 넘기기  (0) 2016.05.11
C# 원문자 구하기  (0) 2015.10.05
SerialPort Read Blocking 시키기  (0) 2015.08.05
문자열 비교 테스트 (대소문자 무시)  (0) 2015.07.02

해당 숫자에해당하는 원문자를 반환하는 메서드입니다. 

 

 

/// <summary>

/// 해당 숫자에 해당하는 원문자열을 반환한다.

/// </summary>

/// <param name="number">변환하고자 하는 숫자</param>

/// <returns>변환된 원문자열</returns>

private string ConvertCombinumerals(int number)

{

    char c = ' ';

    if (number == 0)

    {

        c = (char)9450;

    }

    else if (number >= 1 && number <= 20)

    {

        c = (char)(9312 + (number - 1));

    }

    else if (number >= 21 && number <= 35)

    {

        c = (char)(12881 + (number - 21));

    }

    else if (number >= 36 && number <= 50)

    {

        c = (char)(12977 + (number - 36));

    }

    return c.ToString();

}

 

 

 

아래는 원문자에 해당하는 유니코드 값입니다.

 

⓪ 9450
① 9312
② 9313
③ 9314
④ 9315
⑤ 9316
⑥ 9317
⑦ 9318
⑧ 9319
⑨ 9320
⑩ 9321
⑪ 9322
⑫ 9323
⑬ 9324
⑭ 9325
⑮ 9326
⑯ 9327
⑰ 9328
⑱ 9329
⑲ 9330
⑳ 9331
㉑ 12881
㉒ 12882
㉓ 12883
㉔ 12884
㉕ 12885
㉖ 12886
㉗ 12887
㉘ 12888
㉙ 12889
㉚ 12890
㉛ 12891
㉜ 12892
㉝ 12893
㉞ 12894
㉟ 12895
㊱ 12977
㊲ 12978
㊳ 12979
㊴ 12980
㊵ 12981
㊶ 12982
㊷ 12983
㊸ 12984
㊹ 12985
㊺ 12986
㊻ 12987
㊼ 12988
㊽ 12989
㊾ 12990
㊿ 12991

닷넷의 SerialPort 클래스를 이용해 통신을 하다보니 문제가 발생하였다.

 

Read 메서드를 이용해 특정 바이트 수만큼 읽는 로직을 작성하였는데,

 

예를들어 4바이트를 Read하면 4바이트 이하여도 Read 메서드의 Blocking이 풀리는 현상이다.

 

사실 버그는 아니고......msdn을 살펴보니 수신 버퍼가 비어있지 않다면 해당  Read 메서드의 인자인  count 이하만큼

 

읽어버리는 것을 알았다.

 

아래와 같이 처리해주면 count만큼 수신할 수 있다.

 

/// <summary>

/// 데이터를 수신한다.

/// </summary>

/// <param name="buf">수신할 버퍼</param>

/// <param name="offset">데이터를 저장할 위치</param>

/// <param name="count">데이터 저장 바이트 수</param>

/// <returns></returns>

public int Receive(byte[] buf, int offset, int count)

{

    if (this.serialPort != null)

    {

        int bytesExpected = count, bytesRead = 0;

        while (bytesExpected > 0 && (bytesRead = serialPort.Read(buf, offset, bytesExpected)) > 0)

        {

            offset += bytesRead;

            bytesExpected -= bytesRead;

        }

        return count;

    }

    else

        return 0;

} 

 

소스

 

string src = "abcd";

string dest = "ABCD";

int maxIter = 10000;

int matchCount = 0;

 

Stopwatch sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.ToLower() == dest.ToLower())

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("Lower Equal " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.ToUpper() == dest.ToUpper())

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("Upper Equal " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.Equals(dest, StringComparison.InvariantCultureIgnoreCase))

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("StringComparison.Ordinal InvariantCultureIgnoreCase " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.Equals(dest, StringComparison.CurrentCultureIgnoreCase))

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("StringComparison.Ordinal CurrentCultureIgnoreCase " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.Equals(dest, StringComparison.OrdinalIgnoreCase))

        matchCount++;

}

 

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("StringComparison.Ordinal OrdinalIgnoreCase " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (string.Compare(src, dest, true) == 0)

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("string.Compare Ignore " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (string.CompareOrdinal(src.ToLower(), dest.ToLower()) == 0)

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("string.CompareOrdinal ToLower " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (string.CompareOrdinal(src.ToUpper(), dest.ToUpper()) == 0)

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("string.CompareOrdinal ToUpper " + sw.Elapsed.ToString());

 

 

 

결과

 

string.CompareOrdinal ToUpper 00:00:00.0020907
Upper Equal 00:00:00.0016855
StringComparison.Ordinal InvariantCultureIgnoreCase 00:00:00.0011733
StringComparison.Ordinal CurrentCultureIgnoreCase 00:00:00.0017487
StringComparison.Ordinal OrdinalIgnoreCase 00:00:00.0002009
string.Compare Ignore 00:00:00.0017312
string.CompareOrdinal ToLower 00:00:00.0017755
string.CompareOrdinal ToUpper 00:00:00.0020907 

 

특정 인덱스의 Cell의 Value값을 하나하나 가져오면 속도가 굉장히 느립니다.

 

범위를 선택하고 범위에 해당하는 값들을 한번에 가져와야 속도가 빠릅니다.

 

Worksheet 클래스의 UsedRange(MSDN링크) 속성을 이용해 값을 가져오면 상단의 사용하지 않았던 범위는 놓쳐서

 

특정 인덱스에 접근하기 힘듭니다.

 

왼쪽 상단의 Cell부터 사용한 우측 하단의 Cell까지의 범위의 데이터를 가져와서 작업을 진행해야합니다.

 

 

 

/// <summary>

/// 해당 WorkSheet의 전체 데이터를 가져온다.

/// </summary>

/// <param name="sheet"></param>

/// <returns></returns>

private object[,] GetTotalValue(Worksheet sheet)

{

    //사용중인 범위(한번도 사용하지 않은 범위는 포함되지 않음)

    Range usedRange = sheet.UsedRange;

    //마지막 Cell

    Range lastCell = usedRange.SpecialCells(XlCellType.xlCellTypeLastCell);

           

    //전체 범위 (왼쪽 상단의 Cell부터 사용한 맨마지막 범위까지)

    Range totalRange = sheet.get_Range(sheet.get_Range("A1"), lastCell);

 

    return (object[,])totalRange.get_Value();

} 

 

 

아래의 매크로를 이용해서 컨트롤들의 아이디를 확인하였습니다.

참조 : http://blog.naver.com/PostView.nhn?blogId=heesung2003&logNo=60196368045

 

Sub 컨트롤아이디()
For Each cb In CommandBars
    For Each ctl In cb.Controls
        Cells(Rows.Count, "A").End(3)(2).Resize(, 5) = Array(cb.Name, cb.NameLocal, cb.Visible, ctl.Caption, ctl.ID)
    Next
Next
End Sub 

 

 

컨트롤 아이디를 확인 후  아래와 같이 해당 컨트롤에 접근하여 이벤트를 등록하거나 해당 컨트롤을 숨길 수 있습니다.

 

 

Application app = Globals.ThisAddIn.Application;

 

CommandBarButton delRowBtn = app.CommandBars.FindControl(Id: 293) as CommandBarButton;

CommandBarButton insertRowBtn = app.CommandBars.FindControl(Id: 296) as CommandBarButton;

           

delRowBtn.Visible = true;

delRowBtn.Click += rowDelBtn_Click;

insertRowBtn.Click += insertRowBtn_Click;

 

 

엑셀2013 컨트롤 아이디.xlsx

 

리본 탭의 위치는 특정 컨트롤의 앞이나 뒤에 위치시킬 수 있습니다.

 

예를 들어 홈 탭에 앞에 넣거나 뒤에 넣을 수 있습니다.

 

탭에 대한 아이디는 아래의 경로의 문서를 받아서 확인할 수 있습니다.

 

Office 2013 Help Files: Office Fluent User Interface Control Identifiers
http://www.microsoft.com/en-in/download/details.aspx?id=36798


Office 2010 Help Files: Office Fluent User Interface Control Identifiers
http://www.microsoft.com/en-in/download/details.aspx?id=6627

 

 

 

 

홈탭에 바로 앞에 탭을 추가해보겠습니다.

 

디자이너 Position 속성에서 PositionType은 BeforeOfficeID, OfficeID는 엑셀에서 확인한 TabHome으로 설정합니다.

 

 

 

탭의 위치가 변경된것을 확인 하실 수 있습니다.

 

 

 

C# Excel2007 추가 기능을 사용해 아래의 기능을 구현해보았습니다.

 

1. 특정 셀 클릭시 다른 시트의 셀 선택
2. 상태바 컨트롤
3. 툴팁 띄우기
4. Form 띄우기
5. 특정영역만 사용하기 (색표시 & 잠금(패스워드: test))
6. 콤보박스 사용

 

 

 

 

프로젝트 파일(VS2010) :TestExcelAddIn.zip

 

 

참조 : C# Excel 2007 추가기능(AddIn) 만들기

+ Recent posts