본 게시글은 지난 내용에서 이어진다.
지난 시간에 앱을 만들기 위한 기초를 구현하였으니, 이번에는 실제 솔리드웍스 API를 사용하여
솔리드웍스 애드온을 만들어보자
이번에 해볼 것은 속성과 파일 명을 추출하고 속성 내용을 변경해보는 것을 구현해볼 것이다.
우선 완성 코드를 제공하고 코드 내용을 설명하는 방식으로 설명을 진행하겠다.
~목차
1. 준비 -1 프로그램 준비
2. 준비 - 2 파일 준비
3. 준비 - 3 프로그램 구동 확인
4. 코드 구조 살펴보기
5. 코드 기능 및 API 살펴보기
6. API 자습하기
예시용 솔리드웍스 파일
예시용 프로그램
본격적으로 시작하기에 앞서서 먼저 프로젝트를 만들 준비를 하자
본 프로그램은 MVVM 패턴에 따라 프로그램을 만들 예정이다.
다소 번거롭고 시간을 잡아먹긴 하지만, 추후 유지 보수를 위해 패턴을 도입해서 작성해보자
우선 파일 구조다.
보시다시피 View, ViewModel, Model로 나뉘었으며, 이 중 어느것에도 속하기 애매한 것은 Functions으로 분류할 것이다.
(단, 여기서는 Functions 폴더를 사실상 사용하지 않는다.)
각 파일의 MD는 모델, VM은 뷰모델, EW는 뷰의 약자로 사용했다.
RelayCommand의 경우, 거의 모든 뷰모델에서 사용할 파일이니 명명 규칙에서 예외로 하였으며
그 외 별도의 Model 파일은 없다.
(단, 모델로 분류할 수 있는 부분이 있으나 여기서는 그냥 뷰모델로 분류한다. 이유는 후술)
NewWindow은 지난번에 만든 솔리드웍스 왼쪽 창에 추가되는 창이고
이번에 새로 만든 것은 EW_MainFunction이다.
우선 지난번에 만든 파일을 보자
<UserControl x:Class="AddIn.Views.NewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AddIn.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Green">
<TextBlock Text="Example 솔리드웍스에 사용할 예제3" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Button Content="버튼!!" Click="Button_Click" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="100" Height="30" Margin="10"/>
</Grid>
</UserControl>
보시다시피 별 내용 없다.
다만 달라진 부분이 있다면 아무기능도 없었던 Button에 이벤트를 부여했다.
이제 버튼을 누르면 새로운 창이 뜨도록 이벤트를 부여했으며 이를 위한 비하인드 코드는 다음과 같다.
using System.Windows;
using System.Windows.Controls;
namespace AddIn.Views
{
/// <summary>
/// NewWindow.xaml에 대한 상호 작용 논리
/// </summary>
public partial class NewWindow : UserControl
{
public NewWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EW_MainFunction ew_MainFunction = new EW_MainFunction();
ew_MainFunction.Show();
}
}
}
코드를 보면 버튼을 누르면 EW_MainFunction이 뜨도록 했다.
이번엔 이번 내용의 주 무대인 EW_MainFunction 파일을 살펴보자
<Window x:Class="AddIn.Views.EW_MainFunction"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AddIn.Views"
xmlns:vm="clr-namespace:AddIn.ViewModels"
mc:Ignorable="d"
Width="800" Height="400">
<Window.DataContext>
<vm:VM_MainFunction/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Label Content="파일 명"/>
<Label Content="{Binding FileName}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<Button Margin="5" x:Name="btnRefresh" Width="50" Command="{Binding RefreshCommand}" Content="새로고침"/>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="사용자 속성"/>
<DataGrid x:Name="dgdCustomProperties" Grid.Row="1" Margin="5" AutoGenerateColumns="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding CustomProperties}">
<DataGrid.Columns>
<DataGridTextColumn Header="Custom Property Name" Binding="{Binding Name}" IsReadOnly="True"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="설정 속성"/>
<DataGrid x:Name="dgdConfigurationProperties" Grid.Row="1" Margin="5" AutoGenerateColumns="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding ConfigurationProperties}">
<DataGrid.Columns>
<DataGridTextColumn Header="Configuration Property Name" Binding="{Binding Name}" IsReadOnly="True"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
using System.Windows;
namespace AddIn.Views
{
public partial class EW_MainFunction : Window
{
public EW_MainFunction()
{
InitializeComponent();
}
}
}
xaml의 코드가 제법 길다.
본 코드를 작성할 때 주의할 사항은 코드의 첫부분이 UserControl이 아닌 Window로 시작한다는 것이다.
비주얼 스튜디오에서 xaml 파일을 생성할때 UserControl로 생성하고 헤더를 Window로 바꾸면 된다.
주의! 비하인드 코드에서도 UserControl을 Window로 바꿔주어야 한다.
![]() |
![]() |
뷰를 살펴보았으니 이번엔 뷰모델 코드를 살펴보자
using AddIn.Models;
using SolidWorks.Interop.sldworks;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace AddIn.ViewModels
{
public class VM_MainFunction : INotifyPropertyChanged
{
// 데이터 영역
private string _fileName;
public string FileName
{
get { return _fileName; }
set { _fileName = value; OnPropertyChanged(); }
}
private ObservableCollection<PropertyItem> _customProperties;
public ObservableCollection<PropertyItem> CustomProperties
{
get { return _customProperties; }
set { _customProperties = value; OnPropertyChanged(); }
}
private ObservableCollection<PropertyItem> _configurationProperties;
public ObservableCollection<PropertyItem> ConfigurationProperties
{
get { return _configurationProperties; }
set { _configurationProperties = value; OnPropertyChanged(); }
}
// 커멘드 영역
public ICommand RefreshCommand { get; }
// 생성자
public VM_MainFunction()
{
CustomProperties = new ObservableCollection<PropertyItem>();
ConfigurationProperties = new ObservableCollection<PropertyItem>();
RefreshCommand = new RelayCommand(GetProperties);
GetProperties();
}
// 속성 가져오기 (사용자, 설정 둘 다)
private void GetProperties()
{
SldWorks swApp = new SldWorks();
ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;
if (swModel != null)
{
FileName = swModel.GetTitle();
CustomProperties.Clear();
ConfigurationProperties.Clear();
// 커스텀 속성 가져오기
CustomPropertyManager customPropMgr = swModel.Extension.CustomPropertyManager[""];
string[] customPropNames = customPropMgr.GetNames();
foreach (string propName in customPropNames)
{
string valOut;
string resolvedValOut;
customPropMgr.Get2(propName, out valOut, out resolvedValOut);
CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
}
// 설정 속성 가져오기
ConfigurationManager configMgr = swModel.ConfigurationManager;
Configuration config = configMgr.ActiveConfiguration;
CustomPropertyManager configPropMgr = config.CustomPropertyManager;
string[] configPropNames = configPropMgr.GetNames();
foreach (string propName in configPropNames)
{
string valOut;
string resolvedValOut;
configPropMgr.Get2(propName, out valOut, out resolvedValOut);
ConfigurationProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = false });
}
}
else
{
FileName = "열린 파일이 없습니다.";
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class PropertyItem : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
private string _value;
public string Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
UpdateProperty();
}
}
public bool IsCustomProperty { get; set; }
private void UpdateProperty()
{
SldWorks swApp = new SldWorks();
ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;
if (swModel != null)
{
CustomPropertyManager propMgr;
if (IsCustomProperty)
{
propMgr = swModel.Extension.CustomPropertyManager[""];
}
else
{
ConfigurationManager configMgr = swModel.ConfigurationManager;
Configuration config = configMgr.ActiveConfiguration;
propMgr = config.CustomPropertyManager;
}
propMgr.Set2(Name, Value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
모든 기능이 이곳에 정의되어 있다.
PropertyItem의 경우, 일부 수정하여 모델에 작성해도 되지만 엄연히 UI와 직접 상호작용하도록 코드를 작성했기에 뷰 모델에 포함시키는 것이 맞다고 판단하여 뷰 모델에 작성했다.
물론 뷰와의 상호작용을 VM_MainFunction 에 다 때려박고 PropertyItem을 모델로 분류해도 된다.
다만 여기서는 뷰모델로 사용하겠다.
이제 Relay_Command를 보자
using System;
using System.Windows.Input;
namespace AddIn.Models
{
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
MVVM에서 바인딩을 위해 사실상 필수적으로 사용하는 코드
필요에 따라 얼마든지 RelayCommand 내용을 추가할 수 있다, 아래는 추가한 예시
public class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute)
{
_execute = execute;
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute((T)parameter);
public event EventHandler CanExecuteChanged;
}
이건 예시 코드일 뿐이므로 사용할 필요 없다.
만약 RelayCommand에 파라미터를 넣고자 한다면 이런식으로 추가한다는 예시일 뿐이다.
이번에는 이번 예시에서 사용할 파일을 둘러보자
파일 자체는 첨부해놓았으니 참조 바란다.
이번에 사용할 어셈블리 파일
어셈블리라고 하기도 뭐하지만 아무튼 3개의 부품 파일로 구성되어 있다.
이 파일의 속성을 확인해보자
사진상에는 버튼이 비활성화 되어 있지만 원래 창을 띄우면 저렇게 비활성화 된다.
아무튼 파일 내 사용자 정의와 설정 속성에는 내가 임의로 넣은 값이 들어가 있는 것을 볼 수 있다.
마지막으로 프로그램의 구동을 확인해보자
지난 시간에 알려준것 처럼 애드온을 추가하고 버튼을 눌러서 창을 띄워보자
창을 띄우는 순간, 파일명이 들어오고 사용자 속성과 설정 속성들이 추가된 것을 알 수 있다.
이제 이 내용을 편집해보자
내용을 이와 같이 편집했다.
(단, Value가 두곳에 표시되어 있는데 어느쪽을 고쳐도 알아서 상대쪽에 적용된다.)
이제 과연 잘 적용 되었는지 확인해보자
앞서 알려준대로 사용자 정의 값과 설정 속성에 들어가보니 값이 변해 있는 것을 알 수 있다.
이제 코드를 살펴보자
중요한건 뷰모델 파일 안에 다 있기 때문에 해당 내용만 살펴볼 것이다.
이미 한 번 올렸었지만, 가장 중요한 부분인 만큼 다시한번 살펴보자
using AddIn.Models;
using SolidWorks.Interop.sldworks;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace AddIn.ViewModels
{
public class VM_MainFunction : INotifyPropertyChanged
{
// 데이터 영역
private string _fileName;
public string FileName
{
get { return _fileName; }
set { _fileName = value; OnPropertyChanged(); }
}
private ObservableCollection<PropertyItem> _customProperties;
public ObservableCollection<PropertyItem> CustomProperties
{
get { return _customProperties; }
set { _customProperties = value; OnPropertyChanged(); }
}
private ObservableCollection<PropertyItem> _configurationProperties;
public ObservableCollection<PropertyItem> ConfigurationProperties
{
get { return _configurationProperties; }
set { _configurationProperties = value; OnPropertyChanged(); }
}
// 커멘드 영역
public ICommand RefreshCommand { get; }
// 생성자
public VM_MainFunction()
{
CustomProperties = new ObservableCollection<PropertyItem>();
ConfigurationProperties = new ObservableCollection<PropertyItem>();
RefreshCommand = new RelayCommand(GetProperties);
GetProperties();
}
// 속성 가져오기 (사용자, 설정 둘 다)
private void GetProperties()
{
SldWorks swApp = new SldWorks();
ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;
if (swModel != null)
{
FileName = swModel.GetTitle();
CustomProperties.Clear();
ConfigurationProperties.Clear();
// 커스텀 속성 가져오기
CustomPropertyManager customPropMgr = swModel.Extension.CustomPropertyManager[""];
string[] customPropNames = customPropMgr.GetNames();
foreach (string propName in customPropNames)
{
string valOut;
string resolvedValOut;
customPropMgr.Get2(propName, out valOut, out resolvedValOut);
CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
}
// 설정 속성 가져오기
ConfigurationManager configMgr = swModel.ConfigurationManager;
Configuration config = configMgr.ActiveConfiguration;
CustomPropertyManager configPropMgr = config.CustomPropertyManager;
string[] configPropNames = configPropMgr.GetNames();
foreach (string propName in configPropNames)
{
string valOut;
string resolvedValOut;
configPropMgr.Get2(propName, out valOut, out resolvedValOut);
ConfigurationProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = false });
}
}
else
{
FileName = "열린 파일이 없습니다.";
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class PropertyItem : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
private string _value;
public string Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
UpdateProperty();
}
}
public bool IsCustomProperty { get; set; }
private void UpdateProperty()
{
SldWorks swApp = new SldWorks();
ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;
if (swModel != null)
{
CustomPropertyManager propMgr;
if (IsCustomProperty)
{
propMgr = swModel.Extension.CustomPropertyManager[""];
}
else
{
ConfigurationManager configMgr = swModel.ConfigurationManager;
Configuration config = configMgr.ActiveConfiguration;
propMgr = config.CustomPropertyManager;
}
propMgr.Set2(Name, Value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
해당 코드에서 주서로 내용을 나누어놨는데, 그 내용을 살펴보자
// 데이터 영역
private string _fileName;
public string FileName
{
get { return _fileName; }
set { _fileName = value; OnPropertyChanged(); }
}
private ObservableCollection<PropertyItem> _customProperties;
public ObservableCollection<PropertyItem> CustomProperties
{
get { return _customProperties; }
set { _customProperties = value; OnPropertyChanged(); }
}
private ObservableCollection<PropertyItem> _configurationProperties;
public ObservableCollection<PropertyItem> ConfigurationProperties
{
get { return _configurationProperties; }
set { _configurationProperties = value; OnPropertyChanged(); }
}
이 부분은 사용할 데이터에 관한 부분이다.
예를 들어 FileName은 프로그램 상에 파일 이름이 표시될 공간에 사용할 데이터다.
이미지로 보여주자면 다음과 같다.
그런데 왜 FileName 변수가 _fileName까지 합쳐서 총 2번 선언되어 있는지 궁금할 수 있는데
_fileName은 클래스 내부에서만 접근이 가능하며 데이터가 실제로 저장되는 공간의 역할을 하며
FileName은 _fileName을 UI에 노출시키고 만약 어떠한 사유로든 값이 변경되었을때, 그 사실을 알리기 위한 역할이다.
즉 _fileName은 데이터를 보관하기 위한 곳이고 FileName은 데이터가 변경되었을때 그 사실을 알리기 위한 속성이다.
따라서, 프로그램 내부 (백엔드)에서 사용할 값과 외부(프론트엔드) 가 사용할 값 2개를 쓴다고 생각하면 된다.
자, 이제 CustomProperties와 ConfigurationProperties를 살펴보자
이것들의 값은 각각 사용자 속성 값과 설정 속성 값이다.
그런데 fileName과는 코드 선언 형태가 다른데
이는 fileName은 하나의 데이터만 저장되는 반면
CustomProperties와 ConfigurationProperties는 여러개의 데이터를 저장하기 때문이다.
당장 프로그램만 봐도 fileName에는 오직 현재 열린 파일의 이름 하나만 이름이 나타나있고
사용자 속성과 설정 속성에는 데이터가 여러개가 들어와 있는 것을 알 수 있다.
여기서 Property는 사용자 정의와 설정 속성으로 2개가 있기 때문에 2개를 만들었다.
커멘드 영역은 뷰모델에서 뷰에 이벤트를 전달하는 용도로 사용한다.
public ICommand RefreshCommand { get; }
// 생성자
public VM_MainFunction()
{
RefreshCommand = new RelayCommand(GetProperties);
// 나머지 생략
}
생성된 커멘드를 생성자에서 RelayCommand를 통해 전달해준다.
이때 전달되는 것은 GetProperties 함수다.
사실 생성자 내부를 보면 GetProperties()가 한번 더 있는것을 알 수 있는데
이는 창이 나타났을때 이벤트를 한 번 더 실행하라는 의미이다.
즉, GetProperties 이벤트는 창이 처음 열렸을 때 한번, 그리고 새로고침 버튼을 누를 때 마다 실행된다.
전달될 데이터에 대한 부분으로 매우 중요한 부분 중 하나다.
코드를 하나하나 살펴보자
우선 코드의 윗부분을 살펴보자
이 부분은 Name과 Value로 구성되어 있다.
앞서 설명한 바와 같이 _name은 프로그램 내부에 저장되는 값이며
때문에 Name은 _name의 값을 가져온다.
새로 값이 할당될 때는 할당된 값을 _name에 할당하되 OnPropertyChanged 이벤트 (값이 변경되었음을 알리는 함수)도 같이 작동시킨다.
Value의 경우도 비슷해보이나 OnPropertyChanged 함수 뿐만 아니라 UpdateProperty 함수도 같이 사용하는 것을 알 수 있다.
UpdateProperty 함수는 무엇일까?
이건 솔리드웍스에 값을 업데이트 할 것을 지시하는 부분이다.
그러나 이 단락에서는 코드의 구조를 살펴보는 것이 주 목적이었기 때문에
이 부분에 대해서는 다음 단락에 대해 다루도록 하겠다.
자, 가장 중요한 부분이다. API가 어떻게 작동하고 또 솔리드웍스 내의 데이터에 어떻게 접근하는지 알아보자
우선 GetProperties 함수다.
// 속성 가져오기 (사용자, 설정 둘 다)
private void GetProperties()
{
SldWorks swApp = new SldWorks();
ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;
if (swModel != null)
{
// 파일 이름 가져오기
FileName = swModel.GetTitle();
CustomProperties.Clear();
ConfigurationProperties.Clear();
// 커스텀 속성 가져오기
CustomPropertyManager customPropMgr = swModel.Extension.CustomPropertyManager[""];
string[] customPropNames = customPropMgr.GetNames();
foreach (string propName in customPropNames)
{
string valOut;
string resolvedValOut;
customPropMgr.Get2(propName, out valOut, out resolvedValOut);
CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
}
// 설정 속성 가져오기
ConfigurationManager configMgr = swModel.ConfigurationManager;
Configuration config = configMgr.ActiveConfiguration;
CustomPropertyManager configPropMgr = config.CustomPropertyManager;
string[] configPropNames = configPropMgr.GetNames();
foreach (string propName in configPropNames)
{
string valOut;
string resolvedValOut;
configPropMgr.Get2(propName, out valOut, out resolvedValOut);
ConfigurationProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = false });
}
}
else
{
FileName = "열린 파일이 없습니다.";
}
}
윗부분 부터 하나하나 살펴보자
SldWorks는 솔리드웍스 프로그램 자체를 의미한다.
SldWorks는 API 사용의 첫걸음이며, API에 접근하기 위해 가장 먼저 작성하게 되는 부분이다.
이 코드에서는 우선 솔리드웍스 프로그램 인터페이스를 새로 생성하고 있다.
이를 통해, 현재 열려있는 솔리드웍스와 직접 연결할 수 있다.
솔리드웍스에서 다루는 파일은 크게 부품, 어셈블리, 도면 이렇게 3개로 나뉘는데
이 3가지중 어떤것이라도 할당하기 위해서 ModelDoc2 키워드를 사용한다.
만약 어셈블리는 명시적으로 할당하고 싶다면 AssemblyDoc 키워드를, 부품을 명시적으로 할당하고 싶다면 PartDoc 키워드를, 도면을 명시적으로 할당하고 싶다면 DrawingDoc 키워드를 사용하면 된다.
자, 이제 ModelDoc2에 무엇을 할당할지 정해야 하는데 코드에서는 swApp.ActiveDoc 라고 되어있다.
이는 현재 솔리드웍스(swApp) 에서 활성화된 문서 (ActiveDoc)를 사용한다는 뜻이다.
앞에 (ModelDoc2) 가 있는데 이는 호환을 위해 데이터 타입을 변환해주기 위해 붙여둔 것이다.
(사실 없어도 큰 문제는 없지만 코드의 안정성을 위해 넣어준거다.)
여담
왜 ModelDoc2 키워드에 2가 붙었느냐고 생각할 수 있는데, 과거 ModelDoc는 과거 API 기능이 부실했던 때에 만들어진 것으로 여러가지 기능을 추가하면서 ModelDoc2 키워드를 사용하게 되었다. (단 ModelDoc 는 지금도 쓸 수는 있다.)
앞서 우리는 swModel에 "지금 열려져 있는 파일"을 할당바 있다.
그런데 만약 열려져 있는 파일이 없다면 swModel은 null이 될 것이고
이후의 모든 코드가 에러가 될 것이다.
이를 막기 위해 본 코드에서 swModel != null 조건을 추가하였다.
자 첫번째 기능이다.
열려져 있는 파일의 이름을 가져오자
FileName은 앞서 정의했던 그 변수들이다.
혹시나 해서 그 변수 코드를 다시 사진으로 넣으니 참조하기 바란다.
FileName = swModel.GetTitle();
swModel은 앞서 말했듯이 "지금 열려져있는 파일"을 뜻하고 GetTitle()메서드는 그 파일의 이름을 가져오는 기능을 수행한다.
자, 그런데 CustomProperties와 ConfigurationProperties에 대한 부분도 있다.
이 부분은 Clear()함수가 적용되어 있는데 이는 해당 내용을 모두 없앤다는 뜻이다.
여기서는 변수 내용을 모두 정리하고 새로 할당할 것이 때문에 없애는 코드를 넣어놨다.
파일이름을 가져오는 것은 너무 쉬웠으니 이번에는 속성 값들을 가져와보자
먼저 커스텀 속성이다.
ConfigurationManager 가 바로 커스텀 속성을 가져오기 위한 키워드가
역시나 이곳에서도 swModel을 사용해서 값을 할당하고 있다.
Extension은 확장 기능을 위한 부분이다.
즉 커스텀 속성은 확장기능에 속하기 때문에 해당 메서드를 통해서만 접근할 수 있다.
참고로 확장 기능 중에는 다른 이름으로 저장 (SaveAs)도 있다.(심지어 이 키워드는 SaveAs3까지 있다.)
왜 굳이 이렇게 해놨는지는 웍스 개발자들만이 알것이다.
또한 [""]는 커스텀 속성의 기본 구성을 가져오기 위해 사용한다.
(사실 왜 굳이 [""]를 사용해야 하도록 해놨는지는 잘 모르겠다.)
아무튼 "열려있는 파일"의 "사용자 속성"을 가져와서 customPropMgr에 할당하였으니 이것을 데이터 그리드에 표시해보자
string[] customPropNames = customPropMgr.GetNames();
사용자 정의 속성의 이름들을 가져와 문자열 배열로 할당한 다음
foreach (string propName in customPropNames)
{
string valOut;
string resolvedValOut;
customPropMgr.Get2(propName, out valOut, out resolvedValOut);
CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
}
그 이름들을 순회하면서 customPropMgr의 Get2메서드를 사용하여 값을 가져온다.
값을 가져온다음, 앞서 Clear 했던 CustomProperties에 새로운 요소로 추가한다.
이때 값과 벨류, IsCustomProperty 값이 추가된다.
그런데 CustomProperties는 앞서 View파일의 데이터 그리드에 바인딩 된 값이다.
즉, 여기에 값이 추가되면서 자동으로 데이터 그리드에 값이 올라간다.
설정 속성도 크게 다르지 않지만 사용자 정의 속성이랑은 조금 차이점이 보이는데 이는 다음과 같다.
ConfigurationManager 이것은 솔리드웍스 모델의 모든 구성을 관리하는 키워드이고
Configuration은 솔리드웍스 모델의 특정 구성을 나타내기 위한 클래스다.
그리고 위의 사용자 속성 처럼 이름을 가져와서 찾는 방식으로 값을 가져오고 ConfigurationProperties에 할당한다.
일단 여기까지가 데이터를 가져오고 할당하는 부분이다.
이제 다음 UpdateProperty()로 가보자
여기에도 어김없이 SldWorks 키워드와 ModelDoc2 키워드가 사용되고 있다.
IsCustomProperty는 변경된 값이 사용자 정의 값인지, 설정 속성 값인지를 구분하고 있고
실제 값을 할당하는 부분은 가장 아래 Set2 함수에 해당한다.
값을 가져올때 Get2 함수를 사용했으니 할당할 때는 Set2 함수를 사용하는 것이다.
이번 글을 작성하면서 솔직히 후반부로 갈 수록 알아듣기 어렵고 설명이 부족하다는 것을 느꼈을 탠데
인벤터 API와 솔리드웍스 API를 사용해보면서
개인적으로는 솔리드웍스 API가 다소 복잡하고 난잡하게 잡혀있으며 설명도 부실한 부분이 많아
답정너 식으로 이해하는 방법 밖에 없었다.
(뿐만 아니라 같거나 거의 비슷한 기능임에도 불구하고 버전만 다른 키워드도 많다. 예를 들어 ModelDoc만 해도 ModelDoc, ModelDoc2, IModelDoc, IModelDoc2 가 있다.)
그래도 이번에 본 게시글에서 언급한 부분 말고도 알고자 하는 부분이 있을 것이고
보다 다양한 기능을 API를 통해 사용하고 싶은 사람들을 위해 API 정보를 얻는 방법에 대해 간단히 서술하고 글을 마치겠다.
Welcome - 2023 - SOLIDWORKS API Help
이는 API 도움말 문서이다.
영어밖에 없기 때문에 브라우저에서 필요에 따라 번역을 하도록 하고
우선 우측 체크박스의 버전을 선택하자
버전을 확인했으면 다음은 키워드다.
예를 들어, 지금까지 사용했던 ModelDoc2 키워드를 검색해보자
검색했으면 좌측의 API Help를 눌러서 API 관련된 내용만 보자
그러면 해당 키워드의 인터페이스 내용이 나올탠데 이것을 보면 된다.
그런데 ModelDoc2를 검색했는데 IModelDoc2 가 나온 이유는 ModelDoc2 키워드의 기능을 IModelDoc2 키워드가 대체했기 때문이라고 한다.
그러니까 ModelDoc2 키워드는 사용은 할 수 있어도 지원은 끝났고 앞으로는 IModelDoc2 키워드를 쓰라는 뜻이다.
뭐 이렇게 힘들게 해놨을까
아무튼 검색해보면 이렇게 여러가지 내용이 있다.
본보기라고 번역된 부분은 예시를 말한다.
사용 방법과 예시등이 나와 있으니 해당 문서를 보고 공부하면 된다.
(그런데 사실 그마저도 불친절하기 그지없다. 그냥 아는 사람한테 물어보는게 더 빠를 수도 있다.)
.Net으로 솔리드웍스 앱 만들기 Chapter-1 (0) | 2024.09.15 |
---|---|
C# 공부정리 - 예외처리 - try-catch-finally, throw (1) | 2023.01.11 |
C# 공부정리 - 일반화, dynamic, List, Queue, Stack, Dictionary (0) | 2023.01.11 |
C# 공부정리 - 클래스 4 (다형성, virtual, override) (0) | 2023.01.04 |
C# 공부정리 - 클래스 3 (상속, base, is, as) (0) | 2023.01.03 |
본 게시글은 비주얼스튜디오 2022와 .NetFrameWork를 통해서 솔리드웍스 앱을 만드는 기본적인 과정을 서술한다.
1. 프로젝트 생성-1 참조 추가
2. 프로젝트 생성-2 프로젝트 작성
3. 레지스트리 등록
4. 강력한 키 (서명) 생성
5. 등록
6. 확인
부록
순서대로 진행한다.
또한 여기에 내가 만든 프로젝트 파일도 업로드 해놓았으나
내가 만든것을 다운로드 하기 보단 이 게시글을 하나하나 쫒아가는 것을 권장한다.
더보기 참조
비주얼 스튜디어에서 새 프로젝트를 만들자
검색창에서 클래스를 입력하고 나온 메뉴들 가운데 클래스 라이브러리 (.NET FrameWork)를 선택한다.
주의: 반드시 .NetFramework를 선택한다. 그냥 클래스 라이브러리로 할 경우 프로젝트 속성 목록이 다르게 나타난다.
적당히 원하는 곳에 프로젝트 생성
기본적으로 Class1 파일이 생성되어 있다.
그건 그냥 놔두고
오른쪽 메뉴에서 참조를 우클하고 참조 추가 선택
참조 관리자의 어셈블리 탭에서 위의 2개를 추가
이번엔 참조관리자의 찾아보기 탭에서 찾아보기 버튼 선택
위의 경로에서 파일 3개를 찾아서 추가
완료
생성된 xaml에 본인이 원하는 배치를 하도록 하자
비하인드 코드는 건드릴 필요 없다.
적당히 추가한 코드 내용은 아래와 같다.
<Grid Background="Green">
<TextBlock Text="Example 솔리드웍스에 사용할 예제3" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Button Content="버튼!!" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="100" Height="30"/>
</Grid>
다시 Class1.cs 파일로 돌아와서 코드를 입력한다.
public class MyAddIn : SwAddin
{
private ISldWorks _swApp;
private int _addinID;
private ITaskpaneView taskPaneView;
public bool ConnectToSW(object ThisSW, int Cookie)
{
_swApp = (ISldWorks)ThisSW;
_addinID = Cookie;
// Task Pane 추가
taskPaneView = (ITaskpaneView)_swApp.CreateTaskpaneView2("", "WPF로 만든 또다른 창!");
// WPF 유저 컨트롤을 ElementHost에 할당
var elementHost = new ElementHost
{
Child = new NewWindow(), // WPF 유저 컨트롤 설정
Dock = DockStyle.Fill // 크기를 Task Pane에 맞게 설정
};
// DisplayWindowFromHandle 메서드에 IntPtr로 바로 전달
taskPaneView.DisplayWindowFromHandle(elementHost.Handle.ToInt32());
return true;
}
public bool DisconnectFromSW()
{
if (taskPaneView != null)
{
taskPaneView.DeleteView();
}
return true;
}
}
그런데 이거대로 입력할 경우 사진 처럼 오타 판정이 있을 수 있다.
오타 판정이 나타난 곳을 클릭한 다음 Alt + Enter를 입력하자
위 사진대로 Alt+Enter를 통해 첫번째 메뉴를 클릭하면 코드의 최상단에 using이 자동으로 생성되는 것을 확인할 수 있다.
이제 GUID를 만들어보자
GUID 코드는 자신이 무작위로 생성해도 무방하지만
각 컴퓨터 안에는 이미 매우 다양한 GUID가 있고 이것과 중복되서는 안된다.
따라서 GUID 생성 툴을 사용해보자
아래 "결과"에 GUID가 생성되었으면 우측 상단의 복사를 클릭
그 다음 class MyAddIn위에 붙여넣기 하고
그 아래
[ComVisible(true)를 입력한다.]
여기까지 마쳤으면 빌드 진행
MyAddinKey는 생성할 서명 파일의 이름으로 사용자가 원하는 이름 지정
단, 확장자 .snk는 반드시 붙여야함
이제 프롬프트에서 벗어나 비주얼스튜디오로 복귀
아까처럼 프롬프트를 실행
이번엔 cd를 할 필요 없다.
이번 프롬프트에서 실행할 명령어는 아래와 같으나 주의사항이 몇가지 있음
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe "C:\Users\손민성\Desktop\업무_개발\2_진행\5_퀵메니저\프로그램\테스트프로그램\TestProgram3\AddIn\bin\Debug\AddIn.dll" /tlb /codebase
이 명령어는
- RegAsm 프로그램의 위치
- 내가 만든 프로그램의 위치
- /tlb
- /codebase
이 4개로 구성되어 있다.
앞부분이 RegAsm 프로그램의 위치
그 다음 초록색 부분이 내가 만든 dll 파일의 위치, 나머지는 옵션이다.
따라서 RegAsm 프로그램이 저 위치에 있는지 확인해야 한다.
(사용자마다 RegAsm 프로그램의 위치가 조금씩 다를 수 있기 때문)
이렇게 RegAsm.exe 파일의 위치를 찾을 수 있다.
이제 내 dll 파일의 위치를 찾아보자
이제 RegAsm.exe 파일의 위치는
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe
내가 만든 dll 파일의 위치는
"C:\Users\손민성\Desktop\업무_개발\2_진행\5_퀵메니저\프로그램\테스트프로그램\TestProgram3\AddIn\bin\Debug\AddIn.dll"
이렇게 찾아냈다.
이제
/tlb 과
/codebase 를 붙여서 (띄어쓰기로 나누어주는 것 잊지말것)
아까 그 명령어를 완성하자
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe "C:\Users\손민성\Desktop\업무_개발\2_진행\5_퀵메니저\프로그램\테스트프로그램\TestProgram3\AddIn\bin\Debug\AddIn.dll" /tlb /codebase
이제 모든 준비가 완성되었다.
솔리드웍스를 실행해서 제대로 추가되었는지 확인해보자
솔리드웍스를 실행하고 상단의 톱니바퀴 -> 애드인 -> 가장 아래로 스크롤
아까 레지스트리 등록할때 있었던 파일이 있다.
이것의 왼쪽의 체크박스만 체크하고 확인
이렇게 아까 만든 WPF 창이 나타나는 것을 볼 수 있다.
그런데 뭔가 좀 이상하게 나타났는데
창의 크기를 좌우로 조절해주면 알아서 맞춰진다.
이 글을 작성하면서 참고했던 영상이다.
아래 영상은 10년전의 것이며 영어로 작성되어 있기 때문에 최신 버전과는 약간의 차이가 있다.
Create and register SolidWoks C# and VB.NET AddIns in 3 Steps (youtube.com)
.Net으로 솔리드웍스 앱 만들기 Chapter-2 (0) | 2024.09.27 |
---|---|
C# 공부정리 - 예외처리 - try-catch-finally, throw (1) | 2023.01.11 |
C# 공부정리 - 일반화, dynamic, List, Queue, Stack, Dictionary (0) | 2023.01.11 |
C# 공부정리 - 클래스 4 (다형성, virtual, override) (0) | 2023.01.04 |
C# 공부정리 - 클래스 3 (상속, base, is, as) (0) | 2023.01.03 |
C언어에서 포인터는 메모리 주소를 저장하는 변수로 다른 변수에 사용하고 있는 메모리의 주소를 가리키는 변수다.
이것을 사용하면 메모리에 저장된 데이터를 직접 조작할 수 있는데
이를 통해 call by reference를 할 수 있다.
#include<stdio.h> // C의 헤더파일
int main()
{
const int num = 10; // 상수화할 때는 초기화를 동시에 해주어야 한다, 예를 들어 const int num; 이렇게 적으면 아무것도 할 수 없는 쓰레기 값이 되어버린다.
printf("num: %d\n", num);
int* p = # // 주소를 저장할 수 있는 포인터변수, &(주소연산자), 여기서 *는 연산자가 아닌 기호로 그저 포인터변수임을 나타낸다.
printf("p에 저장된 값: %p\n", p); // 주소를 저장할 때는 앞에 &를 붙여줘야 한다.
printf("p가 참조하는 값: %d\n", *p); // 여기서 p에 저장되는 값은 렘의 주소값이며 p가 참조하는 값은 10이다.
*p = 100; // 여기서 나온 *은 간접 참조연산자다. 포인터는 주소를 저장하므로
printf("p가 가리키는 곳의 데이터 값: %d\n", *p); // 여기서 p에 저장되는 값은 렘의 주소값이며 p가 참조하는 값은 10이다.
printf("p가 참조하는 값: %d\n", num); // 여기서 p에 저장되는 값은 렘의 주소값이며 p가 참조하는 값은 10이다.
printf("num: %d\n", num);
int num1 = 100;
int num2 = 200;
const int* pa = &num1;
printf("pa: %p\n", pa);
pa = &num2;
printf("변경 후 pa: %p\n", pa);
int* const pb = &num1; // 포인터 변수의 상수화
*pb = 70;
return 0;
}
// 출력값
num: 10
p에 저장된 값: 00000068006FF8C4
p가 참조하는 값: 10
p가 가리키는 곳의 데이터 값: 100
p가 참조하는 값: 100
num: 100
pa: 00000068006FF904
변경 후 pa: 00000068006FF924
최초 num 값은 10이고
p에는 num이 저장된 메모리 주소값이 저장된다.
이때, p의 값을 100으로 바꾸어주자 num의 값이 100으로 바뀌었다.
이는 별다른 절차 없이 메모리 주소에 직접 접근하기 때문에 메모리를 직접 관리할 수 있다는 이점이 있으며
이러한 특성상 성능도 더 빨라진다는 장점이 있다.
다만 메모리 구조에 대한 높은 이해를 필요로 하며 잘 못 사용하면 이전까진 경험해보지 못한 새로운 문제와 직면해야 하기 때문에 난이도가 상당히 높다.
포인터 함수를 통해, 저장된 변수를 직접 접근하여 수정할 수 있는데 이를 call by reference라 한다.
아래 코드는 call by value 방식이다.
void swap(int n1, int n2)
{
int temp;
temp = n1;
n1 = n2;
n2 = temp;
printf("호출 중 n1: %d \t n2: %d\n", n1, n2);
}
int main()
{
int n1 = 10, n2 = 20;
printf("호출 전 n1: %d \t n2: %d \n", n1, n2);
swap(n1, n2);
printf("호출 후 n1: %d \t n2: %d \n", n1, n2);
return 0;
}
//출력결과
호출 전 n1: 10 n2: 20
호출 중 n1: 20 n2: 10
호출 후 n1: 10 n2: 20
보시다시피 함수 내에서는 값이 바뀌었으나 main함수에서는 값이 바뀌지 않았다.
이는 main의 값이 swap에서 복사되어 실행되었으므로 어디까지나 swap내의 값이 바뀐 것이지
main의 값이 바뀐 것이 아니기 때문이다.
이번엔 call by reference를 해보자
void swap(int* pn1, int* pn2)
{
int temp;
temp = *pn1;
*pn1 = *pn2;
*pn2 = temp;
//printf("호출 중 n1: %d \t n2: %d\n", n1, n2);
}
int main()
{
int n1 = 10, n2 = 20;
printf("호출 전 n1: %d \t n2: %d \n", n1, n2);
swap(&n1, &n2);
printf("호출 후 n1: %d \t n2: %d \n", n1, n2);
return 0;
}
//출력결과
호출 전 n1: 10 n2: 20
호출 후 n1: 20 n2: 10
이번엔 swap에서 바뀐 값이 main에도 적용되어 있는 것을 알 수 있다.
이는 n1과 n2가 저장된 데이터 주소의 값을 받아와서 swap에서 해당 주소의 값을 바꾼 것이기 때문에
값의 변화가 main에도 그대로 적용된 것이다.
네임스페이스를 배웠다면 이번엔 using에 대해 알아보자
사실 using은 네임스페이스를 보다 쉽고 간결하게 사용하기 위한 키워드라고 할 수 있다.
당장 로그 출력만해도 로그 출력 코드를 입력할 때마다 std:: 를 매번 입력해주어야 하는데
이는 여간 불편한 작업이 아닐 수 없다.
using을 사용하여, 이를 획기적으로 줄여보자
#include<iostream>
using std::cin;
using std::cout;
using std::endl;
int main(void)
{
int num = 20;
cout << "Hello World!" << endl;
cout << "Hello" << "World!" << endl;
cout << num << endl;
}
using을 사용하여 로그 출력을 간결하게 하였다.
이제 std::를 생략해도 보다 쉽게 이름을 사용할 수 있다.
그런데, 명령별로 using을 사용 해주는 것이 번거롭게 느껴질 수 있다.
그럴때는 아래처럼 해보자
#include<iostream>
using namespace std;
int main(void)
{
int num = 20;
cout << "Hello World!" << endl;
cout << "Hello" << "World!" << endl;
cout << num << endl;
}
이는 즉, std라는 namespace의 모든 것을 가져오는 역할을 한다.
만약 로그를 출력할 일이 많이 있고 C언어 방식인 printf가 아닌 방식으로 코드를 짜고자 한다면
이렇게 std라는 namespace를 통째로 가져오는 것도 좋은 방법이라 생각한다.
가져온 네임스페이스에 별칭을 지정하여 보다 간단하게 사용할 수 있다.
예를 들어, 아래의 코드가 있다고 생각해보자
namespace AAA
{
namespace BBB
{
namespace CCC
{
int num = 4;
}
}
}
여기서 num을 가져오려면 AAA::BBB::CCC::num 이런식으로 작성해야 한다.
그러나 보기에 매우 불편하지 않은가?
때문에 네임스페이스를 가져올때 일종의 별칭을 지정해 줄 수 있다.
namespace ABC = AAA::BBB::CCC;
만약 namespace가 외부 파일이라면 using으로 작성해주면 된다.
using namespace ABC = AAA::BBB::CCC;
이제 ABC::num을 통해 CCC네임스페이스 안의 num 값에 접근할 수 있다.
C++ 공부정리 - 네임스페이스 (0) | 2023.03.22 |
---|---|
C++ 공부정리 - 오버로딩과 기본 값 (0) | 2023.03.22 |
C++ 공부정리 - 시작 (0) | 2023.03.21 |
프로그래밍을 하다보면 자주 쓰는 변수, 함수명이 있고 다른 사람들과 협업하다보면
이름이 겹치는 경우가 발생한다.
서로간 이름을 뭘로 할지 매번 합의할 수도 없는 노릇이기 때문에
이름이 같아도 아무런 문제가 발생하지 않도록 사용하는 것이 바로 namespace이다.
#include <iostream>
namespace BestComImpl
{
void SimpleFunc(void);
}
namespace ProgComImpl
{
void SimpleFunc(void);
}
int main()
{
BestComImpl::SimpleFunc();
ProgComImpl::SimpleFunc();
return 0;
}
void BestComImpl::SimpleFunc(void)
{
std::cout << "BestCom에서 정의한 함수" << std::endl;
}
void ProgComImpl::SimpleFunc(void)
{
std::cout << "ProgCom에서 정의한 함수" << std::endl;
}
BestComImpl과 ProgComImpl 총 두 개의 namespace가 있고 여기에는 이름과 파라미터가 같은 함수 2개가 있다.
namespace안에는 해당 함수가 선언만 되어 있을 뿐, 정의는 아랫줄에 위치한 것을 볼 수 있다.
보시다시피 두 함수는 이름과 파라미터가 동일하다.
때문에 완전히 같은 함수처럼 보인다.
그러나 범위연산자인 :: 의 왼쪽을 보면 네임스페이스가 다르게 되어 있는 것을 알 수 있다.
여기서 네임스페이스의 용도가 명확해진다.
변수나 함수 등을 하나로 묶어줌으로써, 변수명이나 함수명이 겹칠 리스크를 크게 줄여준다.
그럼 네임스페이스의 이름이 겹칠 수도 있지 않은가? 라고 생각할 수 있다.
그러나 네임스페이스는 그 안에 또 다른 네임스페이스를 품을 수 있다.
#include <iostream>
namespace AAA
{
int num = 2;
namespace BBB
{
int num = 3;
}
namespace CCC
{
int num = 4;
}
}
int main()
{
std::cout << AAA::num << std::endl;
std::cout << AAA::BBB::num << std::endl;
std::cout << AAA::CCC::num << std::endl;
return 0;
}
AAA안에 각각 BBB, CCC 네임스페이스가 존재하고 그 안에는 전부 num 이라는 이름을 가진 변수가 선언되었다.
그러나 main함수에서 출력할 때, 참조하는 네임스페이가 다르기 때문에 같은 이름의 변수를 가져와도, C++은 그것들을 ㅁ두 구분해 낸다.
때문에 네임스페이스만 잘 구성해놓아도 변수명이 겹치는 사례를 피할 수 있다.
여기까지 읽었으면 ::의 역할이 무엇인지 알았을 것이다.
그렇다면 로그를 표현할 때 쓰는 문법을 보자
std::cout를 사용하지 않는가?
이제 우린 이것이 어떤 표현인지 알게 되었다.
즉, std라는 네임스페이스 안의 cout를 실행하라는 의미이다.
즉, 우린 한 번도 본적없고 보이지 않지만, C++에서는 기본적으로 적용되어 있는 네임스페이스들이 있고
std는 그 네임스페이스들중 하나이며, std 안의 cout는 로그를 출력하는 함수라는 사실을 알 수 있다.
그렇다면 <<는 무엇일까?
이 또한 추후에 다루어 보도록 하겠다.
범위연산자 ::
C++ 공부정리 - using (0) | 2023.03.22 |
---|---|
C++ 공부정리 - 오버로딩과 기본 값 (0) | 2023.03.22 |
C++ 공부정리 - 시작 (0) | 2023.03.21 |
오버로딩은 쉽게 말해 함수의 다중 정의라고 할 수 있다.
보통 함수는 각자 다른 이름을 사용하지만 때론 같은 이름을 사용하기도 한다.
같은 이름을 사용하면 컴퓨터가 어떻게 구분할까? 싶은 생각이 들 것이다.
그러나 파라미터, 즉 입력에서 차이를 두면 충분히 구분해 낼 수 있다.
아래 코드는 func라는 함수를 오버로딩 한 것이다.
#include <iostream>
void func()
{
printf("func()\n");
}
void func(int a)
{
printf("func(a)\n");
}
void func(int a, int b)
{
printf("func(a, b)\n");
}
void func(char a)
{
printf("func(char)\n");
}
int main()
{
int n = 10, n1 = 12;
char ch = 'a';
func();
func(n);
func(n, n1);
func(ch);
return 0;
}
이 코드에는 아무런 문제가 없다.
func라는 이름의 함수가 4번이나 정의되었지만, 그 모든 함수가 각자 다른 타입, 또는 수의 입력을 받기 때문이다.
여기서 주의해야할 점은 '파라미터'가 다르면 오버로딩이 된다는 점에 있다.
리턴값의 형태는 오버로딩과 아무 상관이 없다.
즉 아래 코드는 불가능 하다.
char func(int a)
{
printf("func(a)\n");
return '0';
}
int func(int a)
{
printf("func(a, b)\n");
return 0;
}
모든 파라미터에는 기본값을 넣어줄 수 있다.
#include <iostream>
int Adder(int num1 = 1, int num2 = 2); // Adder 함수의 선언부, 함수에 기본값이 적용되어 있다.
int main(void)
{
std::cout << Adder() << std::endl;
std::cout << Adder(5) << std::endl;
std::cout << Adder(3, 5) << std::endl;
return 0;
}
int Adder(int num1, int num2) // Adder 함수의 정의부, 함수에 기본값이 없다.
{
return num1 + num2;
}
// 함수의 기본값은 함수의 선언부에 위치해야 한다.
// 디폴트 값은 반드시 오른쪽부터 채워줘야 한다. 즉, 아래와 같은 경우는 불가능 하다.
// int A(int num = 1, int num2){...}
함수를 호출했을 때, 파라미터에 입력이 없으면 에러가 발생하지만 디폴트값을 넣어줌으로써 이를 예방할 수 있다.
코드의 주석에도 나와있는 내용이지만 디폴트 값은 반드시 오른쪽부터 채워야 한다.
#include <iostream>
int Adder(int num1, int num2 = 2);
int main(void)
{
std::cout << Adder(5) << std::endl;
std::cout << Adder(3, 5) << std::endl;
return 0;
}
// 생략
위와 같이 오른쪽 부터 채워야 기본값이 없는 경우와 있는 경우에 대해서 대응할 수 있게 된다.
만약 Adder(int num1 = 2, int num2) 처럼 왼쪽에 기본값이 있다면
Adder(5) 라고 했을 때, 여기서 5가 갈 곳이 애매해진다는 문제가 생기기 때문이다.
때문에 기본값이 있는 변수는 반드시 파라미터의 오른쪽에 우선 배치해야 한다.
그런데 이렇게 기본값이 있으면 오버로딩에서 큰 문제가 발생할 것 같다.
아래 코드를 보자
#include <iostream>
int Adder(int num1, char num2='aa')
{
return 0;
}
int Adder(int num1, int num2=0)
{
return 1;
}
int main(void)
{
std::cout << Adder(5) << std::endl;
std::cout << Adder(3) << std::endl;
return 0;
}
바로 이런 경우, 이 경우, main 함수가 호출하는 Adder()이 무엇인지 알 수 없기 때문에 문법오류가 발생한다.
즉, 위의 코드는 아예 사용할 수 없는 코드다.
C++ 공부정리 - using (0) | 2023.03.22 |
---|---|
C++ 공부정리 - 네임스페이스 (0) | 2023.03.22 |
C++ 공부정리 - 시작 (0) | 2023.03.21 |
C++은 객체지향언어로써 절차지향 언어인 C와 많은 차이점을 가진다.
C++에 대한 학습을 시작하기 앞서서, 다른 언어들과의 차이점과 C언어와의 차이점을 따로 다루어보고 넘어가고자 한다.
지금은 몇 개 다루지 않지만, 앞으로도 계속 다루어볼 예정이다.
또한 이 C++은 프로그래밍 언어에 대한 특성을 어느정도 알고 있다는 전제하에 작성한다.
파이썬이나 자바스크립트에는 main함수가 없는데, 어디까지나 보이지 않는 것일 뿐 구조상으론 존재한다.
main 함수는 운영체제가 호출한다.
단, 새로운 표준에서는 C 또한 지역변수 위치에 제한을 두진 않는다.
그러나 많은 컴파일러가 이를 허용하지 않는다.
C++과 C의 차이점으로, 이 특징 때문에, C에서는 같은 이름을 가진 다른 함수를 쓰는 행위, 즉 오버로딩이 불가능하다.
하지만 C++에서는 파라미터에서 차이점을 두면 같은 이름의 함수도 사용할 수 있다.
함수를 구분하는 조건은 C에서는 함수 이름이 전부지만,
C++에선 함수 이름, 파라미터의 타입, 파라미터의 갯수로 구분한다.
단, 출력 타입(리턴값의 타입)은 함수 구분 조건이 아니다.
c++에는 다소 이질적인 문법으로 로그 출력을 작성한다.
참고로 그냥 C에서 하던 방식 그대로 해도 되지만 여기선 C++에서만 가능한 방식으로 작성한다.
#include <iostream>
int main() {
std::cout << "Hello World!" << std::endl;
return 0;
}
std::가 로그 관련명령인데 여기서 cout는 출력을 말한다.
그리고 endl이 줄 바꿈을 의미한다.
꺽쇠는 해당 방향으로 데이터를 넣어준다는 느낌으로 이해하면 편하다.
입력 또한 C에서 하던 방식대로 해도 된다.
하지만 여기서는 C++ 방식대로 실행한다.
아래 함수는 숫자 두 개를 입력 받아 서로 더한 값을 출력하는 함수다.
int main()
{
int val1;
std::cout << "첫 번째 숫자입력: ";
std::cin >> val1;
int val2;
std::cout << "두 번째 숫자입력: ";
std::cin >> val2;
int res = val1 + val2;
std::cout << "덧셈결과: " << res << std::endl;
return 0;
}
std의 cin를 사용하는데 꺽쇠의 방향이 반대다.
마치 입력받은 값을 해당 변수에 넣으라는 모양새다.
그런데 아무리 C언어에서 하던 방식대로 해도 된다곤 하지만 C++에서 로그 입력과 출력은 상당히 이질적이다.
나머지는 다 그렇다 쳐도 왜 :: 이나 ctd, cout 등을 사용하는건지 궁금할 수 있다.
하지만 이에 대한 내용은 추후에 다루어 보겠다.
C++ 공부정리 - using (0) | 2023.03.22 |
---|---|
C++ 공부정리 - 네임스페이스 (0) | 2023.03.22 |
C++ 공부정리 - 오버로딩과 기본 값 (0) | 2023.03.22 |