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

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13565정성태2/23/2024122닷넷: 2219. .NET CLR2 보안 모델에서의 개별 System.Security.Permissions 제어
13564정성태2/22/2024456Windows: 259. Hyper-V Generation 1 유형의 VM을 Generation 2 유형으로 바꾸는 방법
13563정성태2/21/2024622디버깅 기술: 196. windbg - async/await 비동기인 경우 메모리 덤프 분석의 어려움
13562정성태2/21/2024581오류 유형: 896. ASP.NET - .NET Framework 기본 예제에서 System.Web에 대한 System.IO.FileNotFoundException 예외 발생
13561정성태2/20/20241001닷넷: 2218. C# - (예를 들어, Socket) 비동기 I/O에 대한 await 호출 시 CancellationToken을 이용한 취소파일 다운로드1
13560정성태2/19/20241083디버깅 기술: 195. windbg 분석 사례 - Semaphore 잠금으로 인한 Hang 현상 (닷넷)
13559정성태2/19/20241350오류 유형: 895. ASP.NET - System.Security.SecurityException: 'Requested registry access is not allowed.'
13558정성태2/18/20241155닷넷: 2217. C# - 최댓값이 1인 SemaphoreSlim 보다 Mutex 또는 lock(obj)를 선택하는 것이 나은 이유
13557정성태2/18/20241053Windows: 258. Task Scheduler의 Author 속성 값을 변경하는 방법
13556정성태2/17/20241091Windows: 257. Windows - Symbolic (hard/soft) Link 및 Junction 차이점
13555정성태2/15/20241166닷넷: 2216. C# - SemaphoreSlim 사용 시 주의점
13554정성태2/15/2024968VS.NET IDE: 189. Visual Studio - 닷넷 소스코드 디컴파일 찾기가 안 될 때
13553정성태2/14/20241086닷넷: 2215. windbg - thin/fat lock 없이 동작하는 Monitor.Wait + Pulse
13552정성태2/13/20241012닷넷: 2214. windbg - Monitor.Enter의 thin lock과 fat lock
13551정성태2/12/20241131닷넷: 2213. ASP.NET/Core 웹 응용 프로그램 - 2차 스레드의 예외로 인한 비정상 종료
13550정성태2/11/20241179Windows: 256. C# - Server socket이 닫히면 Accept 시켰던 자식 소켓이 닫힐까요?
13549정성태2/3/20241472개발 환경 구성: 706. C# - 컨테이너에서 실행하기 위한 (소켓) 콘솔 프로젝트 구성
13548정성태2/1/20241288개발 환경 구성: 705. "Docker Desktop for Windows" - ASP.NET Core 응용 프로그램의 소켓 주소 바인딩(IPv4/IPv6 loopback, Any)
13547정성태1/31/20241119개발 환경 구성: 704. Visual Studio - .NET 8 프로젝트부터 dockerfile에 추가된 "USER app" 설정
13546정성태1/30/20241057Windows: 255. (디버거의 영향 등으로) 대상 프로세스가 멈추면 Socket KeepAlive로 연결이 끊길까요?
13545정성태1/30/2024987닷넷: 2212. ASP.NET Core - 우선순위에 따른 HTTP/HTTPS 호스트:포트 바인딩 방법
13544정성태1/30/20241019오류 유형: 894. Microsoft.Data.SqlClient - Could not load file or assembly 'System.Security.Permissions, ...'
13543정성태1/30/2024995Windows: 254. Windows - 기본 사용 중인 5357 포트 비활성화는 방법
13542정성태1/30/2024961오류 유형: 893. Visual Studio - Web Application을 실행하지 못하는 IISExpress - 두 번째 이야기
13541정성태1/29/20241009VS.NET IDE: 188. launchSettings.json의 useSSL 옵션
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...