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

... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
12036정성태10/14/201925289.NET Framework: 866. C# - 고성능이 필요한 환경에서 GC가 발생하지 않는 네이티브 힙 사용파일 다운로드1
12035정성태10/13/201919526개발 환경 구성: 461. C# 8.0의 #nulable 관련 특성을 .NET Framework 프로젝트에서 사용하는 방법 [2]파일 다운로드1
12034정성태10/12/201918845개발 환경 구성: 460. .NET Core 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 [1]
12033정성태10/11/201923032개발 환경 구성: 459. .NET Framework 프로젝트에서 C# 8.0/9.0 컴파일러를 사용하는 방법
12032정성태10/8/201919182.NET Framework: 865. .NET Core 2.2/3.0 웹 프로젝트를 IIS에서 호스팅(Inproc, out-of-proc)하는 방법 - AspNetCoreModuleV2 소개
12031정성태10/7/201916429오류 유형: 569. Azure Site Extension 업그레이드 시 "System.IO.IOException: There is not enough space on the disk" 예외 발생
12030정성태10/5/201923228.NET Framework: 864. .NET Conf 2019 Korea - "닷넷 17년의 변화 정리 및 닷넷 코어 3.0" 발표 자료 [1]파일 다운로드1
12029정성태9/27/201924068제니퍼 .NET: 29. Jennifersoft provides a trial promotion on its APM solution such as JENNIFER, PHP, and .NET in 2019 and shares the examples of their application.
12028정성태9/26/201918998.NET Framework: 863. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상을 해결하기 위한 시도파일 다운로드1
12027정성태9/26/201914768오류 유형: 568. Consider app.config remapping of assembly "..." from Version "..." [...] to Version "..." [...] to solve conflict and get rid of warning.
12026정성태9/26/201920201.NET Framework: 862. C# - Active Directory의 LDAP 경로 및 정보 조회
12025정성태9/25/201918485제니퍼 .NET: 28. APM 솔루션 제니퍼, PHP, .NET 무료 사용 프로모션 2019 및 적용 사례 (8) [1]
12024정성태9/20/201920392.NET Framework: 861. HttpClient와 HttpClientHandler의 관계 [2]
12023정성태9/18/201920868.NET Framework: 860. ServicePointManager.DefaultConnectionLimit와 HttpClient의 관계파일 다운로드1
12022정성태9/12/201924822개발 환경 구성: 458. C# 8.0 (Preview) 신규 문법을 위한 개발 환경 구성 [3]
12021정성태9/12/201940624도서: 시작하세요! C# 8.0 프로그래밍 [4]
12020정성태9/11/201923815VC++: 134. SYSTEMTIME 값 기준으로 특정 시간이 지났는지를 판단하는 함수
12019정성태9/11/201917370Linux: 23. .NET Core + 리눅스 환경에서 Environment.CurrentDirectory 접근 시 주의 사항
12018정성태9/11/201916160오류 유형: 567. IIS - Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive. (D:\lowSite4\web.config line 11)
12017정성태9/11/201919964오류 유형: 566. 비주얼 스튜디오 - Failed to register URL "http://localhost:6879/" for site "..." application "/". Error description: Access is denied. (0x80070005)
12016정성태9/5/201919987오류 유형: 565. git fetch - warning: 'C:\ProgramData/Git/config' has a dubious owner: '(unknown)'.
12015정성태9/3/201925353개발 환경 구성: 457. 윈도우 응용 프로그램의 Socket 연결 시 time-out 시간 제어
12014정성태9/3/201919081개발 환경 구성: 456. 명령행에서 AWS, Azure 등의 원격 저장소에 파일 관리하는 방법 - cyberduck/duck 소개
12013정성태8/28/201921998개발 환경 구성: 455. 윈도우에서 (테스트) 인증서 파일 만드는 방법 [3]
12012정성태8/28/201926577.NET Framework: 859. C# - HttpListener를 이용한 HTTPS 통신 방법
12011정성태8/27/201926175사물인터넷: 57. C# - Rapsberry Pi Zero W와 PC 간 Bluetooth 통신 예제 코드파일 다운로드1
... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...