Program Language/C#

[C#] C++ 구조체 사용하기

야곰야곰+책벌레 2022. 9. 16. 14:52
728x90
반응형

C#에서 C++의 Lib를 사용해야 하는데, 데이터의 양이 많아서 구조체를 사용해야 했다. 구글링 해보니 정말 많은 글들이 있었고 비슷비슷하면서도 조금씩 달랐다. 옵션들은 사용하지 않아도 되는 것들도 있었고 (성능 향상을 위한 옵션) 아무래도 처음 해보다 보니 쉽지 않았다.

 

우선 C#과 C++에서 동일하게 사용하는 int, char, double과 같은 변수들은 그냥 사용해도 되지만 대부분의 것들은 마샬링이라는 것을 통하여 사용해야 한다. C++에서 우선 구조체를 선언한다.

#pragma pack(push, 1)
typedef struct MOTION_PARAM2_T
{
	int axisCount;
	int axisList[MotionMax_Axes];

	double position[MotionMax_Axes];
	double velocity[MotionMax_Axes];
	double acceleration[MotionMax_Axes];
	double deceleration[MotionMax_Axes];
	double jerkPercent[MotionMax_Axes];
} MotionParam2;
#pragma pack(pop)

#pragrma pack(push, 1)과 pop은 구조체의 정렬 크기를 정하는 구문이다. 1 이외에도 4나 8 등이 있다. 설정하지 않아도 C#에서 사용할 수 있으나 우선 맞추기 위해서 설정한다.

extern "C" __declspec(dllexport)
int MoveInc(_In_ wchar_t* devicename, _In_ int motion, _In_ MotionParam2* param)
{
	...
}

그리고 C#에서 사용할 수 있도록 implicit linking을 설정해 준다. _In_ 은 사용하지 않아도 된다. SAL(source-code annotation language)이라고 하는데, 나도 공부하지 않아서 아직 잘 모른다. 한쪽 방향으로만 사용할 수 있도록 _In_, _Out_ 등이 있는 것으로 안다.

 

C#에서 사용하려면 C++에서 선언한 구조체와 동일한 선언이 필요하다.

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MOTION_PARAM_T
        {
            public int axisCount;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I4, SizeConst = 64)]
            public int[] axisList;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R8, SizeConst = 64)]
            public double[] position;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R8, SizeConst = 64)]
            public double[] velocity;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R8, SizeConst = 64)]
            public double[] acceleration;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R8, SizeConst = 64)]
            public double[] deceleration;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R8, SizeConst = 64)]
            public double[] jerkPercent;
        }

StructLayout은 구조체임을 얘기해주는 것이고 굳이 설정해 주지 않아도 되는 것 같지만 C++과 함께 정렬 사이즈를 1로 정하기 위해 사용하였다. C++의 배열을 표현하기 위해서는 

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I4, SizeConst = 64)]

이와 같은 구문이 필요하다. 중간에 ArraySubType은 선언하지 않아도 되지만 잘 모르기 때문에 모두 작성해 두었다.

[DllImport("soDispatcher.dll")]
public static extern int Open([MarshalAs(UnmanagedType.LPWStr)] string devicename, int devicetype);
[DllImport("soDispatcher.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int MoveInc([MarshalAs(UnmanagedType.LPWStr)] string devicename, int motion, ref MOTION_PARAM_T param);

C++ 라이브러리는 위와 같이 호출할 수 있고, string의 경우 마샬링이 필요하다. C#에서는 포인터를 사용하지 않는 것을 원칙으로 하고 사용하기 위해서 unsafe를 사용하기도 하지만 보통 ref로 지정하면 된다.

            MOTION_PARAM_T motionParam = new MOTION_PARAM_T();
            motionParam.axisList = new int[64];
            motionParam.position = new double[64];
            motionParam.velocity = new double[64];
            motionParam.acceleration = new double[64];
            motionParam.deceleration = new double[64];
            motionParam.jerkPercent = new double[64];

            motionParam.axisCount = 1;
            motionParam.axisList[0] = 1;
            motionParam.position[0] = 100 * 1000.0;
            motionParam.velocity[0] = 100 * 1000.0;
            motionParam.acceleration[0] = 100 * 1000.0;
            motionParam.deceleration[0] = 100 * 1000.0;
            motionParam.jerkPercent[0] = 100;
            
            MoveInc("AJIN", 0, ref motionParam);

구조체는 선언을 한 뒤 배열 또한 하나하나 메모리를 선언해 줘야 한다. 물론 구조체 초기화에 자동으로 할당되게 만들어도 될 것 같다.

 

여기서 하루를 꼬박 날린 이유는 long을 사용했기 때문이다. 어떻게 된 일인지 long을 사용하면 메모리의 크기가 C#과 C++ 이 맞지 않는 것 같았다. 마샬링 하여 타입을 I8로 정해도 마찬가지였다. long을 int로 바꾸고 나서야 비로소 해결되었다. 이유는 잘 알지 못하겠지만, 우선 long은 사용하지 않는 걸로 하기로 했다.

728x90
반응형