반응형

C# Socket : 고정 구조체 만들기

알게된 배경

 C++로 작성된 서버 혹은 PC와 통신을 하기 위해서는 일반적으로 고정된 크기의 Head와 고정 혹은 가변의 Body로 나누어서 통신을 하는 경우가 많다. C/C++에서는 Struct를 고정된 크기로 할당하는 것이 가능한 반면 C#에서는 별도의 코딩이 필요로 하다.

 이는 기본적으로 C#에서는 배열을 new 키워드를 통해서 정의 및 초기화를 했기때문에 struct에서는 사용이 불가능하다(반면 클래스에서는 사용가능하다).


C/C++의 고정 Struct

 그냥 배열로 선언을 해주면 된다. 아래의 SHead의 크기는 대략 40바이트가 된다. C에서는 그냥 선언해주면 되지만, C++의 경우에도 컴파일에 따라서 추가 선언을 해줘야 한다. C++에 대해서 추후 링크를 추가한다.

// head.hpp
struct SHead
{
    char name[8];
    unsigned char headType;
    char hostname[31];
};



C# unsafe

지금은 폐지된 방법이다. 초기에 불편하자 급조한 티가 난다. 참조문서는 지금도 가이드라인에 있다. 폐지된 만큼 가이드 라인에 맞게 작성해도 에러로 표시된다. 폐지된 방법은 아니고 컴파일 옵션에서 안전하지 않은 코드 컴파일 설정을 바꿔서 실행할 수 있다. C 스타일의 코딩을 자주 해야 한다면, 컴파일 옵션을 풀고 작성하는 것이 코드가 더욱 간결하다. 하지만, 키워드에서 알 수 있듯이 안전하지 않다. (그리고 마소에서 쓰지 않도록 하려고 일부러 쓰기 어렵게 만들어 놓은 방법이기도 하다)


 unsafe가 위험한 이유는 GC가 unsafe로 선언된 곳은 메모리 해제를 하지 않는다.

namespace sock
{
    public struct unsafe SHead
    {
        public Byte fixed name[8];
        public Byte headType;
        public Byte fixed hostname[31];
    }
}




C# Marshal

권장하는 방법이다. 시그니처를 이용해서 배열의 크기를 정해주는 방식이다. 사용하기 위해서는 using System.Runtime.InteropServices; 를 선언하거나 전부 적어주면 된다. pack 옵션 값은 메모리 처리 방식을 할때 단위를 1바이트로 하겠다는 의미이다.

 이러한 키워드를 추가 하지 않으면, 시스템마다 차이는 있지만, 기본적으로는 8바이트를 기준으로 처리를 한다.

namespace sock
{
    [Structlayout(LayoutKind.Sequential, pack=1)]
    public struct SHead
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public Byte[] name;
        public Byte headType;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 31)]
        public Byte[] hostname;
    }
}



 여기서 C#에서 취급하는 특징을 보면 메모리에 배열처럼 연속으로 올릴 경우를 안전하지 않은 방법으로 취급하기 때문에 여러 번거로움이 있다. 이렇게 번거롭게 만든 이유는 가능하면 사용하지 말라는 의도가 있다는 것으로 보인다. 그래도 소켓 프로그램 쪽에서는 어쩔 수 없이 사용해야 하는 경우가 있다.

잡설

 따지고 보면 C#에서 제공하는 두가지 방법들 모두 해당 자료가 안전하지 않다는 것을 명시해주는 것이다. 속도 같은 효율도 떨어진다는 건 덤.. 이러한 이유로 C로 프로그램을 작성할 경우 시스템 이해도와 잘하는 사람과 못 하는 사람간의 차이가 심하다.


참조자료

가이드 라인 unsafe

가이드 라인 unsafe 모드 컴파일

MASN문서 Marshal Array

MSDN문서 LayoutKind

블로그 :고정크기 사용방법




반응형
반응형

C : 구조체를 네임스페이스처럼 사용하기

알게된 배경

리눅스 시스템에서 네트워크 프로그래밍을 공부할 때 참조한 책이 하필 C로 설명이 되어 있는 책이었다. 덕분에 C++과 달리 요즘에는 자료를 찾기 힘들기 때문에 필요한 정보를 처리하기 위해서 직접 함수를 만들어야 하는 경우가 많았다.

 함수가 많아지면 발생되는 문제는 역시 작명문제이다. 그러나 C는 네임스페이스나 클래스 기능이 제공되지 않기 때문에 구조체(struct)와 포인터의 조합으로 비슷하게 흉내내서 구현할 수 있다.

 그러나 타이핑 해야할 양이 급증하게 되므로 가능하면, C로 코딩해야 하는 상황을 최대한 벗어나길 바란다.


간략한 원리

구조체가 전역으로 선언이 되어 있어야 하며, 함수들은 포인터로 연결되어 있어야 한다. 그리고 이렇게 선언된 구조체는 외부 참조가 가능하도록 선언을 해야 네임스페이스 혹은 클래스처럼 사용할 수 있다.


구현 예시

예시로 문자열의 뒤쪽의 단어가 일치하는지 검사하는 함수를 구조체내의 함수로 구현한다. 확장자가 포함된 파일이름의 확장자 검사하는 용도로 사용될 수 있다.

헤더파일)

// ns_util.h
#pragma once

typedef enum{ false, true } bool;

struct ns_util_tools
{
    bool (*isHasBackword(const char* str, const char* backword);
};
// 아래의 struct가 존재하니 이 헤더파일을 인클루드 하면 사용할 수 있음
extern const struct ns_util_tools ns_util;

C파일)

// ns_util.c
#include <string.h>
#include "ns_util.h"
bool ns_util__isHasBackword(const char* str, const char* backword)
{
    int str_len, backword_len;
    int i;
    str_len = strlen(str);
    backword_len = strlen(str);
    for(i=0; i<backword_len; ++i)
    {
        if(str[str_len -i] != backword[backword_len -i])
            return false;
    }
    return true;
}
// 실제로 네임스페이스 처럼 사용될 구조체
const struct ns_util_tools ns_util = {
    .isHasBackword = ns_util__isHasBackword
};


다른파일에서 호출)

// main.c
#include <stdio.h>
#include "ns_util.h"

int main(int argc, char* argv[])
{
    char* str = "main.exe";
    if(ns_util.isHasBackword(str, ".exe"))
        printf("%s is exe file!!", str);
    else
        printf("%s is What is?", str);

    return 0;
}



 이로써 C로써도 충분히 객체지향으로 코딩이 가능하다는 것을 알 수 있다. 그렇지만, C로 프로젝트 진행하는 것은 가능하면 피하고 싶다.

반응형

+ Recent posts