Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)
(시리즈 글이 16개 있습니다.)
.NET Framework: 112. How to Interop DISPPARAMS
; https://www.sysnet.pe.kr/2/0/617

.NET Framework: 137. C#에서 Union 구조체 다루기
; https://www.sysnet.pe.kr/2/0/728

.NET Framework: 141. Win32 Interop - 크기가 정해지지 않은 배열을 C++에서 C#으로 전달하는 경우
; https://www.sysnet.pe.kr/2/0/737

.NET Framework: 168. [in,out] 배열을 C#에서 C/C++로 넘기는 방법
; https://www.sysnet.pe.kr/2/0/810

.NET Framework: 169. [in, out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/811

.NET Framework: 183. 구조체 포인터 인자에 대한 P/Invoke 정의
; https://www.sysnet.pe.kr/2/0/912

.NET Framework: 472. C/C++과 C# 사이의 메모리 할당/해제 방법
; https://www.sysnet.pe.kr/2/0/1784

.NET Framework: 620. C#에서 C/C++ 함수로 콜백 함수를 전달하는 예제 코드
; https://www.sysnet.pe.kr/2/0/11099

.NET Framework: 627. C++로 만든 DLL을 C#에서 사용하기
; https://www.sysnet.pe.kr/2/0/11111

.NET Framework: 686. C# - string 배열을 담은 구조체를 직렬화하는 방법
; https://www.sysnet.pe.kr/2/0/11319

.NET Framework: 757. 포인터 형 매개 변수를 갖는 C++ DLL의 함수를 C#에서 호출하는 방법
; https://www.sysnet.pe.kr/2/0/11533

.NET Framework: 978. C# - GUID 타입 전용의 UnmanagedType.LPStruct
; https://www.sysnet.pe.kr/2/0/12444

C/C++: 158. Visual C++ - IDL 구문 중 "unsigned long"을 인식하지 못하는 #import
; https://www.sysnet.pe.kr/2/0/13128

.NET Framework: 2058. [in,out] 배열을 C#에서 C/C++로 넘기는 방법 - 세 번째 이야기
; https://www.sysnet.pe.kr/2/0/13141

.NET Framework: 2083. C# - C++과의 연동을 위한 구조체의 fixed 배열 필드 사용 (2)
; https://www.sysnet.pe.kr/2/0/13205

닷넷: 2152. Win32 Interop - C/C++ DLL로부터 이중 포인터 버퍼를 C#으로 받는 예제
; https://www.sysnet.pe.kr/2/0/13429





[in, out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기


지난 글에 이어서.

[in, out] 배열을 C#에서 C/C++로 넘기는 방법
; https://www.sysnet.pe.kr/2/0/810

마이크로소프트가 원래 의도한 바는 아니었겠지만, tlbimp.exe의 기능을 보정할 수 있는 방법을 별도로 제공하고 있습니다.

.NET Framework Developer's Guide
- Customizing Runtime Callable Wrappers
; https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/e753eftz(v=vs.100)

위의 글에 포함된 그림이 재미있습니다. ^^

[그림 1: RCW DLL 생성 방법]
how_to_customize_rcw_1.gif

RCW를 생성하는 3가지 방법 중에서 중간 그림이 의미가 있는데, 일단 한번 tlbimp.exe에 의해서 생성된 DLL을 역어셈블한 뒤, 원하는 대로 마샬링 정보를 바꾸고 다시 ilasm을 이용해서 DLL을 생성하고 있습니다. 오호... 이 정도면 훌륭한 대안이죠. ^^




이것을 이용해서 지난번 예제를 개선해 보겠습니다.

IDL은 원래 정의된 그대로 사용하고,

[
	object,
	uuid(1A38076B-3D6D-4F20-8B4D-C72EF6AE1204),
	dual,
	nonextensible,
	helpstring("IMyTest Interface"),
	pointer_default(unique)
]
interface IMyTest : IDispatch
{
	[id(0x3003), helpstring("method PrepareBuf")] 
	HRESULT PrepareBuf([in, out, size_is(bufLength)] __int64 buffer [], [in] int bufLength);
};

마샬링 정보가 올바르진 않겠지만 일단 tlbimp.exe(또는 Visual Studio의 DLL 참조)를 이용하여 interop DLL을 생성합니다.

tlbimp testatl.dll /out:interop.testatl.dll

ildasm으로 DLL을 역어셈블하면 해당 메서드가 다음과 같이 선언된 것을 볼 수 있습니다.

ildasm interop.testatl.dll /out:interop.testatl.il

.method public hidebysig newslot virtual 
      instance void  PrepareBuf([in][out] int64& buffer,
                                 [in] int32 bufLength) runtime managed internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 03 30 00 00 00 00 )   // ...0....
.override interop.testatl.IMyTest::PrepareBuf
} // end of method MyTestClass::PrepareBuf

여기에서 "[in][out] int64& buffer" 구문을 "[in][out] int64[] marshal([]) buffer"과 같이 바꿔줍니다. (또 한 군데 더 정의되어 있기 때문에 그 부분도 마저 바꿔줍니다.) 이렇게 변경하고 다시 ilasm으로 어셈블해주면, C#에서 정상적으로 long []으로 사용할 수 있습니다.

ilasm interop.testatl.il /dll

public virtual void PrepareBuf(long[] buffer, int bufLength);

우와~~~ ^^ 깔끔하죠! (첨부된 파일은 위의 예제와 지난번 글의 예제를 함께 테스트한 프로젝트입니다.)

그러고 보니, 이걸 하면서 예전에 쓴 글이 하나 생각났습니다.

COM 개체의 이벤트를 구독하는 코드 제작
; https://www.sysnet.pe.kr/2/0/589

위의 방법 역시 interop DLL 코드가 잘못 생성된 경우인데, ildasm/ilasm 조합으로 해결할 수도 있었지 않았을까 싶네요. ^^



[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/19/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2009-12-20 01시53분
[김종혁] [id(1), helpstring("method Test")] HRESULT Test([in] double* arr);

위 COM 함수를 tlbimp 를 통해 il 로 변환하면 float64& 로 변환되죠.

1. il 변환없이 C# 에서 아래와 같은 코드를 작성하고 테스트를 하면,

double[] dValues = { 1.1, 2.2, 3.3 };
objCOM.Test(ref dValues[0]);

.NET 1.1 에서는 정상 출력됩니다. 그러나 2.0, 3.0 에서는 첫번째 값만 제대로 출력이 됩니다.

2. il 을 Test([in] float64[] marshal([]) arr) 로 변환 후 아래와 같이 실행을 해도

objCOM.Test(dValues);

.NET 1.1 에서만 정상 출력될 뿐 결과는 동일합니다.

3. double* 는 4바이트 정수형 타입으로 처리될 것이기에,

il 을 Test([in] int32 arr) 로 변환 후 아래와 같이 실행을 하면

IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(dValues, 0);
objCOM.Test(ptr.ToInt32());

결과는 동일합니다. 어떤 테스트를 해도 오직 .NET 1.1 에서만 정상입니다.


제 PC에는 VS 6.0, 2003, 2005, 2008 이 설치되어 있습니다.

혹시 어떤 문제인지 아신다면 염치불구하고 도움 부탁드립니다.
[guest]
2009-12-20 02시02분
[김종혁] 댓글이 삭제/수정이 안되네요, ㅎㅎ

시삽님 코드를 2008 에서 실행하면 정상입니다. 그러나 2005로 실행하면 PrepareBuf1 는 모두 0 으로 출력됩니다. 동일 문제 같습니다만,,
[guest]
2009-12-20 03시34분
[김종혁] 정정합니다. 2008에서는 정상 작동하네요. 2008의 Target 이 .NET Framework 2.0 으로 설정되어 있는데, 결국 2005와 차이가 없어야 할텐데요.
[guest]
2009-12-20 10시18분
문제를 정리해보겠습니다. ^^

1. 이 글에 첨부된 Interop.zip을 다운로드 받는다.
2. .\TestATL\Debug\TestATL.dll을 regsvr32로 레지스트리에 등록한다.
3. Visual Studio 2005에서 C# 프로젝트를 만든다.
4. .\ConsoleApplication1\ConsoleApplication1\bin\Debug\interop.testatl.dll을 참조한다.
5. 새로 만든 프로젝트의 Program.cs에 .\ConsoleApplication1\ConsoleApplication1\Program.cs에 준하는 코드를 작성하고 빌드한다.

위와 같이 했는데, 결과는 오동작이라는 것인가요? 하지만, 3번 단계의 작업을 Visual Studio 2008로 하면 정상동작한다는 것이고?

맞나요? ^^
kevin25
2009-12-20 11시07분
[김종혁] 제가 너무 두서 없었죠? 죄송합니다.

시삽님께서 작성하신 내용은 제가 현재 진행중인 프로젝트 때문에 전부터 고민하던 문제였고, MSDN 에서 동일한 내용의 글을 참고하여 테스트를 해보았으나 2005 에서만 실패를 했던 경우입니다.

복잡도를 줄이기 위해 아주 간단히 [in] double* 를 인자로 받아 그대로 출력하는 COM을 C++로 작성을 하고 2003, 2005, 2008 에서 테스트를 진행하면 유독 2005 에서만 실패를 합니다.

잘 아시다시피, COM 에 C#의 배열을 전달하는 방법은 시삽님의 글과 제 댓글처럼 몇 가지 있을 수가 있는데요. 2005에서만 실패하는 이유가 뭘까요. 첫번째 인덱스의 값만 정상 출력이 되고 나머지 값은 Gabage 가 출력됩니다.

질문하셨던 것처럼 시삽님의 소스도 마찬가지 현상이 발생하였습니다. 2008은 정상 출력이 되나, 2005에서는 전부 0 이 출력이 됩니다. COM 에서 대입한 값이 마샬링 과정에서 누락되는 듯 합니다.

2008의 환경은 .NET Framework 2.0 로 설정되어 있으며, 이는 런타임 환경은 .NET Framework 2.0 을 사용한다는 얘기인데, 결국 차이는 컴파일러 뿐입니다. 2005를 주로 사용하기에 모르고 있었던 사실인데 2008에서 Target을 2.0 으로 두더라도 컴파일러는 3.5 버전의 csc.exe 를 사용하더군요.

주말 동안 구글에서 정신없었는데, 제 문제에 흥미를 가져주신 것만으로도 정말 감사합니다.
[guest]
2009-12-21 09시53분
현재 이 글에 첨부 파일로 "Interop2005.zip"을 추가했습니다.
그걸 다운로드 한 후, Interop2005.sln 파일을 VS2005에서 열고, TestATL 프로젝트를 빌드한 다음 ConsoleApplication1.exe를 실행해 보세요.

제가 해보니, Visual Studio 2005에서도 정상적으로 동작했습니다.

참고로, IL 코드 변환한 DLL은 .\bin\debug\interop.testatl.dll로 미리 생성해 두었고 현재의 ConsoleApplication1 프로젝트에서는 그 DLL을 참조하도록 지정되어 있습니다.
kevin25
2009-12-21 08시41분
[김종혁] 정답을 찾았습니다. 시삽님의 2005 프로젝트를 그대로 실행하면 성공, 제가 만든 프로젝트로 실행하면 오류.

소스를 비교하던 중, [STAThread] 누락 발견 !!!

2005와 2008 콘솔 애플리케이션에는 [STAThread] 를 자동으로 추가하지 않는다는 것을 생각 못했네요.

In the .NET Framework version 2.0, new threads are initialized as ApartmentState..::.MTA if their apartment state has not been set before they are started. The main application thread is initialized to ApartmentState..::.MTA by default. (MSDN 발췌)

2008에서 되었다 안되었다했던 이유도 명확해 지네요. 윈도우 애플리케이션으로 테스트를 했으면 이렇게 고민하지 않았을텐데 말이죠. 그래도 의문인 것은 왜 배열형태의 케이스에서만 위와문제가 발생하는 걸까요,

시삽님 코드 아니였으면 오늘도 방황하고 있었을 겁니다. 다시 한번 감사의 말씀 드립니다.
[guest]
2009-12-21 09시22분
STA가 아닌 경우에도 정상적으로 마샬링을 하고 싶다면, [프로젝트명]PS.csproj를 빌드해서 산출된 dll을 등록해 주시면 됩니다. 또는 ATL COM 개체 생성시에 아예 Merge Proxy인가 하는 옵션을 선택해주시는 것도 좋은 방법이고.
kevin25

... 91  92  [93]  94  95  96  97  98  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11610정성태7/15/201817392Graphics: 5. Unity로 실습하는 Shader (3) - 고로 셰이딩(gouraud shading) + 퐁 모델(Phong model) + Texture
11609정성태7/15/201820405Graphics: 4. Unity로 실습하는 Shader (2) - 고로 셰이딩(gouraud shading) + 퐁 모델(Phong model)
11608정성태7/15/201824964Graphics: 3. Unity로 실습하는 Shader (1) - 컬러 반전 및 상하/좌우 뒤집기
11607정성태7/14/201825296Graphics: 2. Unity로 실습하는 Shader [1]
11606정성태7/13/201825937사물인터넷: 19. PC에 연결해 동작하는 자신만의 USB 장치 만들어 보기파일 다운로드1
11605정성태7/13/201821740사물인터넷: 18. New NodeMCU v3 아두이노 호환 보드의 내장 LED 및 입력 핀 사용법 [1]파일 다운로드1
11604정성태7/12/201820862Math: 47. GeoGebra 기하 (24) - 정다각형파일 다운로드1
11603정성태7/12/201816921Math: 46. GeoGebra 기하 (23) - sqrt(n) 제곱근파일 다운로드1
11602정성태7/11/201817090Math: 45. GeoGebra 기하 (22) - 반전기하학의 원에 관한 반사변환파일 다운로드1
11601정성태7/11/201819855Math: 44. GeoGebra 기하 (21) - 반전기하학의 직선 및 원에 관한 반사변환파일 다운로드1
11600정성태7/10/201818313Math: 43. GeoGebra 기하 (20) - 세 점을 지나는 원파일 다운로드1
11599정성태7/10/201817623Math: 42. GeoGebra 기하 (19) - 두 원의 안과 밖으로 접하는 직선파일 다운로드1
11598정성태7/10/201819502Windows: 147. 시스템 복구 디스크를 USB 디스크에 만드는 방법
11597정성태7/10/201821611사물인터넷: 17. Thinary Electronic - ATmega328PB 아두이노 호환 보드의 개발 환경 구성
11596정성태7/10/201819547기타: 72. 과거의 용어 설명 - OWIN
11595정성태7/10/201825303사물인터넷: 16. New NodeMCU v3 아두이노 호환 보드의 기본 개발 환경 구성
11594정성태7/8/201819688Math: 41. GeoGebra 기하 (18) - 원의 중심 및 접선파일 다운로드1
11593정성태7/8/201818710Math: 40. GeoGebra 기하 (17) - 각의 복사파일 다운로드1
11591정성태7/7/201818045Math: 39. GeoGebra 기하 (16) - 삼각형의 방심과 방접원파일 다운로드1
11590정성태7/7/201817602Math: 38. GeoGebra 기하 (15) - 삼각형의 수심파일 다운로드1
11589정성태7/7/201817874.NET Framework: 787. object로 형변환된 인스턴스를 원래의 타입 인자로 제네릭 메서드를 호출하는 방법 [2]파일 다운로드1
11588정성태7/7/201819346디버깅 기술: 116. windbg 분석 사례 - ASP.NET 웹 응용 프로그램의 CPU 100% 현상 (3)
11587정성태7/5/201818948.NET Framework: 786. ASP.NET - HttpCookieCollection을 다중 스레드에서 사용할 경우 무한 루프 현상
11586정성태7/5/201818130Math: 37. GeoGebra 기하 (14) - 삼각형의 무게 중심파일 다운로드1
11585정성태7/5/201818304Math: 36. GeoGebra 기하 (13) - 삼각형의 외심과 외접하는 원파일 다운로드1
11584정성태7/5/201818286Math: 35. GeoGebra 기하 (12) - 삼각형의 내심과 내접하는 원파일 다운로드1
... 91  92  [93]  94  95  96  97  98  99  100  101  102  103  104  105  ...