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

... 151  152  153  154  155  156  157  158  159  [160]  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1045정성태5/24/201131746.NET Framework: 215. 닷넷 System.ComponentModel.LicenseManager를 이용한 라이선스 적용 [1]파일 다운로드1
1044정성태5/24/201132311오류 유형: 122. zlib 빌드 오류 - inflate.obj : error LNK2001: unresolved external symbol _inflate_fast
1043정성태5/24/201131246.NET Framework: 214. 무료 Linq Provider - DbLinq를 이용한 Firebird 접근파일 다운로드1
1042정성태5/23/201137590개발 환경 구성: 122. PHP 소스를 윈도우 환경에서 빌드하기
1041정성태5/22/201128514.NET Framework: 213. Linq To SQL - ALinq Provider를 이용하여 Firebird 사용파일 다운로드1
1040정성태5/21/201138861개발 환경 구성: 121. .NET 개발자가 처음 설치해 본 Apache + PHP [2]
1039정성태5/17/201131549.NET Framework: 212. Firebird 데이터베이스와 ADO.NET [2]파일 다운로드1
1038정성태5/16/201133516개발 환경 구성: 120. .NET 프로그래머에게도 유용한 Firebird 무료 데이터베이스 [2]
1037정성태5/11/201128352개발 환경 구성: 119. Visual Studio Professional 이하 버전에서도 TFS의 정적 코드 분석 정책 연동이 가능할까? [3]
1036정성태5/7/201194213오류 유형: 121. Access DB에 대한 32bit/64bit OLE DB Provider 관련 오류 [11]
1035정성태5/7/201128874오류 유형: 120. File cannot be opened. Ensure it is a valid Data Link file.
1034정성태5/2/201125953.NET Framework: 211. 파일 잠금 없이 .NET 어셈블리의 버전을 구하는 방법 [2]파일 다운로드1
1033정성태5/1/201131694웹: 19. IIS Express - appcmd.exe를 이용한 applicationHost.config 변경 [2]
1032정성태5/1/201128313웹: 18. IIS Express를 NT 서비스로 변경
1031정성태4/30/201129459웹: 17. IIS Express - "IIS Installed Versions Manager Interface"의 IIISExpressProcessUtility 구하는 방법 [1]파일 다운로드1
1030정성태4/30/201151767개발 환경 구성: 118. IIS Express - localhost 이외의 호스트 이름으로 접근하는 방법 [4]파일 다운로드1
1029정성태4/28/201140880개발 환경 구성: 117. XCopy에서 파일/디렉터리 확인 질문 없애기 [2]
1028정성태4/27/201138260오류 유형: 119. Visual Studio 2010 SP1 설치 후 Windows Phone 개발자 도구로 인한 재설치 문제 [3]
1027정성태4/25/201127433디버깅 기술: 40. 상황별 GetFunctionPointer 반환값 정리 - x86파일 다운로드1
1026정성태4/25/201145702디버깅 기술: 39. DebugDiag 1.1을 사용한 덤프 분석 [7]
1025정성태4/24/201127792개발 환경 구성: 116. IIS 7 관리자 - Active Directory Certification Authority로부터 SSL 사이트 인증서 받는 방법 [2]
1024정성태4/22/201129168오류 유형: 118. Windows 2008 서버에서 Event Viewer / PowerShell 실행 시 비정상 종료되는 문제 [1]
1023정성태4/20/201130011.NET Framework: 210. Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 [1]
1022정성태4/19/201125592디버깅 기술: 38. .NET Disassembly 창에서의 F11(Step-into) 키 동작파일 다운로드1
1021정성태4/18/201127866디버깅 기술: 37. .NET 4.0 응용 프로그램의 Main 함수에 BreakPoint 걸기
1020정성태4/18/201128488오류 유형: 117. Failed to find runtime DLL (mscorwks.dll), 0x80004005
... 151  152  153  154  155  156  157  158  159  [160]  161  162  163  164  165  ...